feat: add log collection and viewing system

Log Collectors (backend/internal/collectors/logs/):
- LogEntry model with level, source, message, fields
- Manager for coordinating multiple collectors
- JournalCollector: systemd journal via journalctl CLI
- FileCollector: tail log files with format parsing (plain, json, nginx)
- DockerCollector: docker container logs via docker CLI
- All collectors are pure Go (no CGO dependencies)

Database Storage:
- Add logs table with indexes for efficient querying
- StoreLogs: batch insert log entries
- QueryLogs: filter by agent, source, level, time, full-text search
- DeleteOldLogs: retention cleanup
- Implementations for both SQLite and PostgreSQL

Frontend Log Viewer:
- Log types and level color definitions
- Logs API client with streaming support
- /logs route with search, level filters, source filters
- Live streaming mode for real-time log tailing
- Paginated loading with load more

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 08:52:31 +01:00
parent c0dbf80521
commit 014bc9bbb5
10 changed files with 2453 additions and 0 deletions

View File

@@ -60,6 +60,11 @@ type Database interface {
// Retention
RunRetention(ctx context.Context) error
// Logs
StoreLogs(ctx context.Context, entries []LogEntry) error
QueryLogs(ctx context.Context, filter LogFilter) ([]LogEntry, int, error)
DeleteOldLogs(ctx context.Context, before time.Time) (int, error)
}
// MetricPoint represents a single metric data point.
@@ -182,6 +187,42 @@ type AlertFilter struct {
Offset int
}
// LogLevel represents log severity.
type LogLevel string
const (
LogLevelDebug LogLevel = "debug"
LogLevelInfo LogLevel = "info"
LogLevelWarning LogLevel = "warning"
LogLevelError LogLevel = "error"
LogLevelFatal LogLevel = "fatal"
)
// LogEntry represents a stored log entry.
type LogEntry struct {
ID int64 `json:"id"`
AgentID string `json:"agentId"`
Timestamp time.Time `json:"timestamp"`
Source string `json:"source"` // "journal", "file", "docker"
SourceName string `json:"sourceName"` // Unit name, filename, container
Level LogLevel `json:"level"`
Message string `json:"message"`
Fields map[string]string `json:"fields,omitempty"`
}
// LogFilter specifies criteria for querying logs.
type LogFilter struct {
AgentID string // Filter by agent
Source string // Filter by source type (journal, file, docker)
SourceName string // Filter by source name
Level []LogLevel // Filter by levels
Query string // Full-text search query
From time.Time
To time.Time
Limit int
Offset int
}
// RetentionConfig defines data retention policies.
type RetentionConfig struct {
// Raw metrics retention (default: 24 hours)
@@ -195,6 +236,9 @@ type RetentionConfig struct {
// Hourly aggregation retention (default: 1 year)
HourlyRetention time.Duration
// Log retention (default: 7 days)
LogRetention time.Duration
}
// DefaultRetentionConfig returns default retention settings.
@@ -204,5 +248,6 @@ func DefaultRetentionConfig() RetentionConfig {
OneMinuteRetention: 7 * 24 * time.Hour,
FiveMinuteRetention: 30 * 24 * time.Hour,
HourlyRetention: 365 * 24 * time.Hour,
LogRetention: 7 * 24 * time.Hour,
}
}