fleet/server/platform/logging/kitlog_adapter_test.go
Scott Gress 421dc67e0c
Add ability to enable/disable logs by topic (#40126)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #40124 

# Details

Implements the proposal in
https://docs.google.com/document/d/16qe6oVLKK25nA9GEIPR9Gw_IJ342_wlJRdnWEMmWdas/edit?tab=t.0#heading=h.nlw4agv1xs3g

Allows doing e.g.
```go
logger.WarnContext(logCtx, "The `team_id` param is deprecated, use `fleet_id` instead", "log_topic", "deprecated-field-names")
```
or
```go
if logging.TopicEnabled("deprecated-api-params") {
  logging.WithLevel(ctx, slog.LevelWarn)
  logging.WithExtras(
    ctx, 
    "deprecated_param", 
    queryTagValue,
    "deprecation_warning", 
    fmt.Sprintf("'%s' is deprecated, use '%s'", queryTagValue, renameTo),
  )
}
```

Topics can be disabled at the app level, and enabled/disabled at the
command-line level.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
No logs have this in prod yet, but I added some manually in a branch and
verified that I could enable/disable them via CLI options and env vars,
including enabling topics that were disabled on the server. Tested for
both server and `fleetctl gitops`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added per-topic logging control to enable or disable logging for
specific topics via configuration and CLI flags.
* Added context-aware logging methods (ErrorContext, WarnContext,
InfoContext, DebugContext) to support contextual logging.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-20 17:22:50 -06:00

155 lines
3.6 KiB
Go

package logging
import (
"context"
"log/slog"
"testing"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/fleetdm/fleet/v4/server/platform/logging/testutils"
)
// newTestAdapter creates a kitlog adapter with a TestHandler for capturing records.
func newTestAdapter(t *testing.T) (*testutils.TestHandler, kitlog.Logger) {
t.Helper()
handler := testutils.NewTestHandler()
slogLogger := slog.New(handler)
return handler, NewLogger(slogLogger)
}
func TestKitlogAdapter(t *testing.T) {
t.Parallel()
t.Run("basic logging", func(t *testing.T) {
t.Parallel()
handler, adapter := newTestAdapter(t)
err := adapter.Log("msg", "hello world", "key", "value")
require.NoError(t, err)
record := handler.LastRecord()
require.NotNil(t, record)
assert.Equal(t, "hello world", record.Message)
attrs := testutils.RecordAttrs(record)
assert.Equal(t, "value", attrs["key"])
})
t.Run("with context via With", func(t *testing.T) {
t.Parallel()
handler, adapter := newTestAdapter(t)
kitlogAdapter, ok := adapter.(*Logger)
require.True(t, ok, "adapter should be *Logger")
contextLogger := kitlogAdapter.With("component", "test-component")
err := contextLogger.Log("msg", "message with context")
require.NoError(t, err)
record := handler.LastRecord()
require.NotNil(t, record)
assert.Equal(t, "message with context", record.Message)
attrs := testutils.RecordAttrs(record)
assert.Equal(t, "test-component", attrs["component"])
})
}
func TestKitlogAdapterLevels(t *testing.T) {
t.Parallel()
tests := []struct {
name string
levelFunc func(kitlog.Logger) kitlog.Logger
expectedLevel slog.Level
}{
{
name: "info",
levelFunc: level.Info,
expectedLevel: slog.LevelInfo,
},
{
name: "debug",
levelFunc: level.Debug,
expectedLevel: slog.LevelDebug,
},
{
name: "warn",
levelFunc: level.Warn,
expectedLevel: slog.LevelWarn,
},
{
name: "error",
levelFunc: level.Error,
expectedLevel: slog.LevelError,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
handler, adapter := newTestAdapter(t)
leveledLogger := tc.levelFunc(adapter)
err := leveledLogger.Log("msg", tc.name+" message")
require.NoError(t, err)
record := handler.LastRecord()
require.NotNil(t, record)
assert.Equal(t, tc.name+" message", record.Message)
assert.Equal(t, tc.expectedLevel, record.Level)
})
}
}
func TestKitlogSlogWrappers(t *testing.T) {
handler := testutils.NewTestHandler()
adapter := NewLogger(slog.New(handler))
tests := []struct {
name string
logFunc func(ctx context.Context, msg string, keyvals ...any)
expectedLevel slog.Level
}{
{
name: "error",
logFunc: adapter.ErrorContext,
expectedLevel: slog.LevelError,
},
{
name: "warn",
logFunc: adapter.WarnContext,
expectedLevel: slog.LevelWarn,
},
{
name: "info",
logFunc: adapter.InfoContext,
expectedLevel: slog.LevelInfo,
},
{
name: "debug",
logFunc: adapter.DebugContext,
expectedLevel: slog.LevelDebug,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tc.logFunc(context.Background(), tc.name+" message", "key", "value")
record := handler.LastRecord()
require.NotNil(t, record)
assert.Equal(t, tc.name+" message", record.Message)
assert.Equal(t, tc.expectedLevel, record.Level)
attrs := testutils.RecordAttrs(record)
assert.Equal(t, "value", attrs["key"])
})
}
}