243 lines
4.9 KiB
Go
243 lines
4.9 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/storage"
|
|
)
|
|
|
|
type LogRepository struct {
|
|
mu sync.RWMutex
|
|
DB *sql.DB
|
|
}
|
|
|
|
func NewLogRepository() *LogRepository {
|
|
return &LogRepository{}
|
|
}
|
|
|
|
func (r *LogRepository) SetDB(db *sql.DB) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.DB = db
|
|
}
|
|
|
|
func (r *LogRepository) IsConnected() bool {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return r.DB != nil
|
|
}
|
|
|
|
func (r *LogRepository) getDB() *sql.DB {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return r.DB
|
|
}
|
|
|
|
func (r *LogRepository) Init(ctx context.Context) error {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
_, err := db.ExecContext(ctx, storage.CreateLogsTable)
|
|
return err
|
|
}
|
|
|
|
func (r *LogRepository) Insert(ctx context.Context, log storage.LogEntry) error {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
_, err := db.ExecContext(ctx, `
|
|
INSERT INTO logs (timestamp, level, service, agent, message)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
`, log.Timestamp, log.Level, log.Service, log.Agent, log.Message)
|
|
return err
|
|
}
|
|
|
|
func (r *LogRepository) InsertBatch(ctx context.Context, logs []storage.LogEntry) error {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
if len(logs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Build multi-row INSERT statement
|
|
query := "INSERT INTO logs (timestamp, level, service, agent, message) VALUES "
|
|
args := make([]interface{}, 0, len(logs)*5)
|
|
for i, log := range logs {
|
|
if i > 0 {
|
|
query += ", "
|
|
}
|
|
query += fmt.Sprintf("($%d, $%d, $%d, $%d, $%d)",
|
|
i*5+1, i*5+2, i*5+3, i*5+4, i*5+5)
|
|
args = append(args, log.Timestamp, log.Level, log.Service, log.Agent, log.Message)
|
|
}
|
|
|
|
_, err := db.ExecContext(ctx, query, args...)
|
|
return err
|
|
}
|
|
|
|
type LogFilter struct {
|
|
Level string
|
|
Service string
|
|
Agent string
|
|
DateFrom time.Time
|
|
DateTo time.Time
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
func (r *LogRepository) Search(ctx context.Context, filter LogFilter) ([]storage.LogEntry, error) {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return []storage.LogEntry{}, nil
|
|
}
|
|
|
|
query := "SELECT timestamp, level, service, agent, message FROM logs WHERE 1=1"
|
|
args := make([]interface{}, 0)
|
|
argIdx := 1
|
|
|
|
if filter.Level != "" {
|
|
query += " AND level = $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.Level)
|
|
argIdx++
|
|
}
|
|
|
|
if filter.Service != "" {
|
|
query += " AND service = $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.Service)
|
|
argIdx++
|
|
}
|
|
|
|
if filter.Agent != "" {
|
|
query += " AND agent = $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.Agent)
|
|
argIdx++
|
|
}
|
|
|
|
if !filter.DateFrom.IsZero() {
|
|
query += " AND timestamp >= $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.DateFrom)
|
|
argIdx++
|
|
}
|
|
|
|
if !filter.DateTo.IsZero() {
|
|
query += " AND timestamp <= $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.DateTo)
|
|
argIdx++
|
|
}
|
|
|
|
query += " ORDER BY timestamp DESC"
|
|
|
|
if filter.Limit > 0 {
|
|
query += " LIMIT $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.Limit)
|
|
argIdx++
|
|
} else {
|
|
query += " LIMIT 100"
|
|
}
|
|
|
|
if filter.Offset > 0 {
|
|
query += " OFFSET $" + string(rune('0'+argIdx))
|
|
args = append(args, filter.Offset)
|
|
}
|
|
|
|
rows, err := db.QueryContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
logs := make([]storage.LogEntry, 0)
|
|
for rows.Next() {
|
|
var log storage.LogEntry
|
|
if err := rows.Scan(
|
|
&log.Timestamp,
|
|
&log.Level,
|
|
&log.Service,
|
|
&log.Agent,
|
|
&log.Message,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
logs = append(logs, log)
|
|
}
|
|
|
|
return logs, rows.Err()
|
|
}
|
|
|
|
func (r *LogRepository) GetDistinctServices(ctx context.Context) ([]string, error) {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return []string{}, nil
|
|
}
|
|
rows, err := db.QueryContext(ctx, "SELECT DISTINCT service FROM logs ORDER BY service")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
services := make([]string, 0)
|
|
for rows.Next() {
|
|
var service string
|
|
if err := rows.Scan(&service); err != nil {
|
|
return nil, err
|
|
}
|
|
services = append(services, service)
|
|
}
|
|
|
|
return services, rows.Err()
|
|
}
|
|
|
|
func (r *LogRepository) GetDistinctAgents(ctx context.Context) ([]string, error) {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return []string{}, nil
|
|
}
|
|
rows, err := db.QueryContext(ctx, "SELECT DISTINCT agent FROM logs ORDER BY agent")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
agents := make([]string, 0)
|
|
for rows.Next() {
|
|
var agent string
|
|
if err := rows.Scan(&agent); err != nil {
|
|
return nil, err
|
|
}
|
|
agents = append(agents, agent)
|
|
}
|
|
|
|
return agents, rows.Err()
|
|
}
|
|
|
|
func (r *LogRepository) GetDistinctLevels(ctx context.Context) ([]string, error) {
|
|
db := r.getDB()
|
|
if db == nil {
|
|
return []string{}, nil
|
|
}
|
|
rows, err := db.QueryContext(ctx, "SELECT DISTINCT level FROM logs ORDER BY level")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
levels := make([]string, 0)
|
|
for rows.Next() {
|
|
var level string
|
|
if err := rows.Scan(&level); err != nil {
|
|
return nil, err
|
|
}
|
|
levels = append(levels, level)
|
|
}
|
|
|
|
return levels, rows.Err()
|
|
}
|