From c5c872a3731397f6921ff5b87f704fc76a0274cf Mon Sep 17 00:00:00 2001 From: Manuel Ganter Date: Fri, 6 Feb 2026 11:00:34 +0100 Subject: [PATCH] fix(output): correct JSON serialization of top process metrics slog.Any() does not properly serialize slices of slog.Group() attributes, resulting in broken output like {"Key":"","Value":{}}. Fixed by passing structs with JSON tags directly to slog.Any() instead. Co-Authored-By: Claude Opus 4.5 --- internal/output/logger.go | 46 +++++++++++++++++++++++++------------- internal/summary/writer.go | 24 ++------------------ 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/internal/output/logger.go b/internal/output/logger.go index 0ce5ddd..4c57794 100644 --- a/internal/output/logger.go +++ b/internal/output/logger.go @@ -53,26 +53,40 @@ func NewLoggerWriter(cfg LoggerConfig) *LoggerWriter { } } +// 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 attrs - topCPUAttrs := make([]any, 0, len(m.TopCPU)) + // Build top CPU process entries + topCPU := make([]topCPUEntry, 0, len(m.TopCPU)) for _, p := range m.TopCPU { - topCPUAttrs = append(topCPUAttrs, slog.Group("", - slog.Int("pid", p.PID), - slog.String("name", p.Name), - slog.Float64("cpu_percent", p.CPUPercent), - )) + topCPU = append(topCPU, topCPUEntry{ + PID: p.PID, + Name: p.Name, + CPUPercent: p.CPUPercent, + }) } - // Build top memory process attrs - topMemAttrs := make([]any, 0, len(m.TopMemory)) + // Build top memory process entries + topMem := make([]topMemEntry, 0, len(m.TopMemory)) for _, p := range m.TopMemory { - topMemAttrs = append(topMemAttrs, slog.Group("", - slog.Int("pid", p.PID), - slog.String("name", p.Name), - slog.Uint64("rss_bytes", p.MemRSS), - )) + topMem = append(topMem, topMemEntry{ + PID: p.PID, + Name: p.Name, + RSSBytes: p.MemRSS, + }) } w.logger.Info("metrics_collected", @@ -94,8 +108,8 @@ func (w *LoggerWriter) Write(m *metrics.SystemMetrics) error { slog.Uint64("total_rss_bytes", m.Memory.TotalRSSBytes), slog.Float64("rss_percent", m.Memory.RSSPercent), ), - slog.Any("top_cpu", topCPUAttrs), - slog.Any("top_memory", topMemAttrs), + slog.Any("top_cpu", topCPU), + slog.Any("top_memory", topMem), ) return nil diff --git a/internal/summary/writer.go b/internal/summary/writer.go index f6781e1..30b392b 100644 --- a/internal/summary/writer.go +++ b/internal/summary/writer.go @@ -35,26 +35,6 @@ func (w *SummaryWriter) Write(s *RunSummary) { return } - topCPUAttrs := make([]any, 0, len(s.TopCPUProcesses)) - for _, p := range s.TopCPUProcesses { - topCPUAttrs = append(topCPUAttrs, slog.Group("", - slog.Int("pid", p.PID), - slog.String("name", p.Name), - slog.Float64("peak_cpu_percent", p.PeakCPU), - slog.Uint64("peak_mem_rss_bytes", p.PeakMem), - )) - } - - topMemAttrs := make([]any, 0, len(s.TopMemProcesses)) - for _, p := range s.TopMemProcesses { - topMemAttrs = append(topMemAttrs, slog.Group("", - slog.Int("pid", p.PID), - slog.String("name", p.Name), - slog.Float64("peak_cpu_percent", p.PeakCPU), - slog.Uint64("peak_mem_rss_bytes", p.PeakMem), - )) - } - w.logger.Info("run_summary", slog.Time("start_time", s.StartTime), slog.Time("end_time", s.EndTime), @@ -75,7 +55,7 @@ func (w *SummaryWriter) Write(s *RunSummary) { slog.Float64("avg", s.MemUsedPercent.Avg), slog.Float64("p95", s.MemUsedPercent.P95), ), - slog.Any("top_cpu_processes", topCPUAttrs), - slog.Any("top_mem_processes", topMemAttrs), + slog.Any("top_cpu_processes", s.TopCPUProcesses), + slog.Any("top_mem_processes", s.TopMemProcesses), ) }