package output import ( "io" "log/slog" "os" "edp.buildth.ing/DevFW-CICD/forgejo-runner-resource-collector/internal/metrics" ) // LogFormat specifies the log output format type LogFormat string const ( LogFormatJSON LogFormat = "json" LogFormatText LogFormat = "text" ) // LoggerWriter outputs metrics using structured logging type LoggerWriter struct { logger *slog.Logger level slog.Level } // LoggerConfig holds configuration for the logger type LoggerConfig struct { Output io.Writer Format LogFormat Level slog.Level } // NewLoggerWriter creates a new logger-based writer func NewLoggerWriter(cfg LoggerConfig) *LoggerWriter { if cfg.Output == nil { cfg.Output = os.Stdout } var handler slog.Handler opts := &slog.HandlerOptions{ Level: cfg.Level, } switch cfg.Format { case LogFormatText: handler = slog.NewTextHandler(cfg.Output, opts) default: handler = slog.NewJSONHandler(cfg.Output, opts) } return &LoggerWriter{ logger: slog.New(handler), level: cfg.Level, } } // topCPUEntry is a lightweight struct for JSON serialization of top CPU processes type topCPUEntry struct { PID int `json:"pid"` Name string `json:"name"` CPUPercent float64 `json:"cpu_percent"` } // topMemEntry is a lightweight struct for JSON serialization of top memory processes type topMemEntry struct { PID int `json:"pid"` Name string `json:"name"` RSSBytes uint64 `json:"rss_bytes"` } // Write outputs the metrics using structured logging func (w *LoggerWriter) Write(m *metrics.SystemMetrics) error { // Build top CPU process entries topCPU := make([]topCPUEntry, 0, len(m.TopCPU)) for _, p := range m.TopCPU { topCPU = append(topCPU, topCPUEntry{ PID: p.PID, Name: p.Name, CPUPercent: p.CPUPercent, }) } // Build top memory process entries topMem := make([]topMemEntry, 0, len(m.TopMemory)) for _, p := range m.TopMemory { topMem = append(topMem, topMemEntry{ PID: p.PID, Name: p.Name, RSSBytes: p.MemRSS, }) } w.logger.Info("metrics_collected", slog.Time("collection_time", m.Timestamp), slog.Int("total_processes", m.TotalProcesses), slog.Group("cpu", slog.Float64("total_percent", m.CPU.TotalPercent), slog.Float64("user_percent", m.CPU.UserPercent), slog.Float64("system_percent", m.CPU.SystemPercent), slog.Float64("idle_percent", m.CPU.IdlePercent), slog.Float64("iowait_percent", m.CPU.IOWaitPercent), ), slog.Group("memory", slog.Uint64("total_bytes", m.Memory.TotalBytes), slog.Uint64("used_bytes", m.Memory.UsedBytes), slog.Uint64("free_bytes", m.Memory.FreeBytes), slog.Uint64("available_bytes", m.Memory.AvailableBytes), slog.Float64("used_percent", m.Memory.UsedPercent), slog.Uint64("total_rss_bytes", m.Memory.TotalRSSBytes), slog.Float64("rss_percent", m.Memory.RSSPercent), ), slog.Any("top_cpu", topCPU), slog.Any("top_memory", topMem), ) return nil } // Close is a no-op for the logger writer func (w *LoggerWriter) Close() error { return nil } // ParseLogLevel parses a log level string func ParseLogLevel(level string) slog.Level { switch level { case "debug": return slog.LevelDebug case "warn", "warning": return slog.LevelWarn case "error": return slog.LevelError default: return slog.LevelInfo } } // ParseLogFormat parses a log format string func ParseLogFormat(format string) LogFormat { switch format { case "text": return LogFormatText default: return LogFormatJSON } }