feat: modelo Log y CRUD en fn_operations
Tipo Log con niveles debug/info/warn/error, source, entity_id y execution_id opcionales. Migración 003_logs.sql y funciones InsertLog, GetLog, ListLogs con filtros combinables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -814,3 +814,92 @@ func scanAssertionResults(rows *sql.Rows) ([]AssertionResult, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// --- Log CRUD ---
|
||||
|
||||
// InsertLog inserts a log entry.
|
||||
func (db *DB) InsertLog(l *Log) error {
|
||||
if l.CreatedAt.IsZero() {
|
||||
l.CreatedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
_, err := db.conn.Exec(`
|
||||
INSERT INTO logs (id, level, source, entity_id, execution_id, message, metadata, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
l.ID, string(l.Level), l.Source, l.EntityID, l.ExecutionID,
|
||||
l.Message, marshalJSON(l.Metadata), l.CreatedAt.Format(time.RFC3339),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLog returns a log entry by ID.
|
||||
func (db *DB) GetLog(id string) (*Log, error) {
|
||||
row := db.conn.QueryRow(`
|
||||
SELECT id, level, source, entity_id, execution_id, message, metadata, created_at
|
||||
FROM logs WHERE id = ?`, id)
|
||||
|
||||
var l Log
|
||||
var metadataJSON, createdAt string
|
||||
err := row.Scan(&l.ID, &l.Level, &l.Source, &l.EntityID, &l.ExecutionID,
|
||||
&l.Message, &metadataJSON, &createdAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scanning log: %w", err)
|
||||
}
|
||||
l.Metadata = unmarshalJSON(metadataJSON)
|
||||
l.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// ListLogs returns logs filtered by level, source, entity, and/or execution.
|
||||
func (db *DB) ListLogs(level LogLevel, source, entityID, executionID string, limit int) ([]Log, error) {
|
||||
where := []string{}
|
||||
args := []any{}
|
||||
if level != "" {
|
||||
where = append(where, "level = ?")
|
||||
args = append(args, string(level))
|
||||
}
|
||||
if source != "" {
|
||||
where = append(where, "source = ?")
|
||||
args = append(args, source)
|
||||
}
|
||||
if entityID != "" {
|
||||
where = append(where, "entity_id = ?")
|
||||
args = append(args, entityID)
|
||||
}
|
||||
if executionID != "" {
|
||||
where = append(where, "execution_id = ?")
|
||||
args = append(args, executionID)
|
||||
}
|
||||
|
||||
q := `SELECT id, level, source, entity_id, execution_id, message, metadata, created_at FROM logs`
|
||||
if len(where) > 0 {
|
||||
q += " WHERE " + strings.Join(where, " AND ")
|
||||
}
|
||||
q += " ORDER BY created_at DESC"
|
||||
if limit > 0 {
|
||||
q += fmt.Sprintf(" LIMIT %d", limit)
|
||||
}
|
||||
|
||||
rows, err := db.conn.Query(q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []Log
|
||||
for rows.Next() {
|
||||
var l Log
|
||||
var metadataJSON, createdAt string
|
||||
if err := rows.Scan(&l.ID, &l.Level, &l.Source, &l.EntityID, &l.ExecutionID,
|
||||
&l.Message, &metadataJSON, &createdAt); err != nil {
|
||||
return nil, fmt.Errorf("scanning log: %w", err)
|
||||
}
|
||||
l.Metadata = unmarshalJSON(metadataJSON)
|
||||
l.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
|
||||
result = append(result, l)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user