mirror of
https://github.com/fleetdm/fleet
synced 2026-05-10 18:51:03 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #38889 # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Structured logging with selectable JSON/text output and optional trace correlation (trace_id, span_id). * Backward-compatible output (ts timestamp, lowercase levels) and adapter to interoperate with existing logging calls. * **Refactor** * Simplified logger initialization and centralized slog-based logging infrastructure. * **Tests** * Extensive tests and a test handler for logging behavior, formats, levels, and trace injection. * **Chores** * Added package-level dependency check for the logging package. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
// Package testutils provides testing utilities for the logging package.
|
|
package testutils
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"slices"
|
|
"sync"
|
|
)
|
|
|
|
// TestHandler is a slog.Handler that captures log records for testing.
|
|
// It allows tests to verify logging behavior without parsing serialized output.
|
|
type TestHandler struct {
|
|
// mu and records are pointers so they're shared across WithAttrs/WithGroup calls.
|
|
// This mirrors how real handlers share their output destination (io.Writer)
|
|
// while maintaining independent configuration (attrs, groups).
|
|
mu *sync.Mutex
|
|
records *[]slog.Record
|
|
attrs []slog.Attr
|
|
group string
|
|
}
|
|
|
|
// NewTestHandler creates a new TestHandler.
|
|
func NewTestHandler() *TestHandler {
|
|
return &TestHandler{
|
|
mu: &sync.Mutex{},
|
|
records: &[]slog.Record{},
|
|
}
|
|
}
|
|
|
|
// Enabled returns true for all levels.
|
|
func (h *TestHandler) Enabled(context.Context, slog.Level) bool {
|
|
return true
|
|
}
|
|
|
|
// Handle captures the record for later inspection.
|
|
func (h *TestHandler) Handle(_ context.Context, r slog.Record) error {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
// Clone the record to avoid issues with reused records
|
|
clone := slog.NewRecord(r.Time, r.Level, r.Message, r.PC)
|
|
|
|
// Add pre-set attrs first
|
|
clone.AddAttrs(h.attrs...)
|
|
|
|
// Then add record attrs
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
clone.AddAttrs(a)
|
|
return true
|
|
})
|
|
|
|
*h.records = append(*h.records, clone)
|
|
return nil
|
|
}
|
|
|
|
// WithAttrs returns a new handler with the given attributes added.
|
|
func (h *TestHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
return &TestHandler{
|
|
mu: h.mu,
|
|
records: h.records,
|
|
attrs: slices.Concat(h.attrs, attrs),
|
|
group: h.group,
|
|
}
|
|
}
|
|
|
|
// WithGroup returns a new handler with the given group name.
|
|
func (h *TestHandler) WithGroup(name string) slog.Handler {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
return &TestHandler{
|
|
mu: h.mu,
|
|
records: h.records,
|
|
attrs: h.attrs,
|
|
group: name,
|
|
}
|
|
}
|
|
|
|
// Records returns all captured log records.
|
|
func (h *TestHandler) Records() []slog.Record {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
return slices.Clone(*h.records)
|
|
}
|
|
|
|
// Clear removes all captured records.
|
|
func (h *TestHandler) Clear() {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
*h.records = nil
|
|
}
|
|
|
|
// LastRecord returns the most recently captured record, or nil if none.
|
|
func (h *TestHandler) LastRecord() *slog.Record {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if len(*h.records) == 0 {
|
|
return nil
|
|
}
|
|
r := (*h.records)[len(*h.records)-1]
|
|
return &r
|
|
}
|
|
|
|
// RecordAttrs extracts all attributes from a record as a map.
|
|
func RecordAttrs(r *slog.Record) map[string]any {
|
|
attrs := make(map[string]any)
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
attrs[a.Key] = a.Value.Any()
|
|
return true
|
|
})
|
|
return attrs
|
|
}
|