auto(0129): agents_dashboard — secret_store_cpp_infra + CMakeLists register #4
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
level TEXT NOT NULL DEFAULT 'info' CHECK(level IN ('debug','info','warn','error')),
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
entity_id TEXT NOT NULL DEFAULT '',
|
||||
execution_id TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL,
|
||||
metadata TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_logs_level ON logs(level);
|
||||
CREATE INDEX idx_logs_source ON logs(source);
|
||||
CREATE INDEX idx_logs_created_at ON logs(created_at);
|
||||
@@ -145,6 +145,28 @@ type AssertionResult struct {
|
||||
EvaluatedAt time.Time `json:"evaluated_at"`
|
||||
}
|
||||
|
||||
// LogLevel represents the severity of a log entry.
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
LogDebug LogLevel = "debug"
|
||||
LogInfo LogLevel = "info"
|
||||
LogWarn LogLevel = "warn"
|
||||
LogError LogLevel = "error"
|
||||
)
|
||||
|
||||
// Log is a free-form operational event within a project context.
|
||||
type Log struct {
|
||||
ID string `json:"id"`
|
||||
Level LogLevel `json:"level"`
|
||||
Source string `json:"source"` // who: agent, pipeline, reactive_loop, function name...
|
||||
EntityID string `json:"entity_id"` // optional context
|
||||
ExecutionID string `json:"execution_id"` // optional context
|
||||
Message string `json:"message"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TypeSnapshot is an immutable copy of a registry type at point of use.
|
||||
type TypeSnapshot struct {
|
||||
ID string `json:"id"`
|
||||
|
||||
@@ -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