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 23:22:50 +00:00
|
|
|
package logging
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"log/slog"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const logTopicAttrKey = "log_topic"
|
|
|
|
|
|
|
|
|
|
// TopicFilterHandler is a slog.Handler that filters log records based on
|
|
|
|
|
// the log_topic found in the log record's attributes, or on the handler
|
|
|
|
|
// itself if set using `WithAttrs`.
|
|
|
|
|
type TopicFilterHandler struct {
|
|
|
|
|
base slog.Handler
|
|
|
|
|
topic string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewTopicFilterHandler wraps base with topic-aware filtering.
|
|
|
|
|
func NewTopicFilterHandler(base slog.Handler) *TopicFilterHandler {
|
|
|
|
|
return &TopicFilterHandler{base: base}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enabled reports whether the handler handles records at the given level.
|
|
|
|
|
func (h *TopicFilterHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
|
|
|
return h.base.Enabled(ctx, level)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle processes the log record. It performs a defensive re-check of the
|
|
|
|
|
// topic before delegating to the base handler.
|
|
|
|
|
func (h *TopicFilterHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
|
|
|
topic := h.topic
|
|
|
|
|
// Override any log_topic attribute on the handler with one from the record's attributes, if present.
|
|
|
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
|
|
|
if a.Key == logTopicAttrKey {
|
|
|
|
|
topic = a.Value.String()
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
})
|
|
|
|
|
// Don't log if there's a disabled topic either on the handler or the record.
|
|
|
|
|
if topic != "" && !TopicEnabled(topic) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-02-24 05:09:08 +00:00
|
|
|
|
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 23:22:50 +00:00
|
|
|
return h.base.Handle(ctx, r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithAttrs returns a new TopicFilterHandler wrapping the base with added attributes.
|
|
|
|
|
func (h *TopicFilterHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
|
|
|
// Check if the new attributes include a log_topic that should override the handler's current topic.
|
|
|
|
|
for _, a := range attrs {
|
|
|
|
|
if a.Key == logTopicAttrKey {
|
|
|
|
|
return &TopicFilterHandler{base: h.base.WithAttrs(attrs), topic: a.Value.String()}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &TopicFilterHandler{base: h.base.WithAttrs(attrs), topic: h.topic}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithGroup returns a new TopicFilterHandler wrapping the base with the given group.
|
|
|
|
|
func (h *TopicFilterHandler) WithGroup(name string) slog.Handler {
|
|
|
|
|
return &TopicFilterHandler{base: h.base.WithGroup(name), topic: h.topic}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure TopicFilterHandler implements slog.Handler at compile time.
|
|
|
|
|
var _ slog.Handler = (*TopicFilterHandler)(nil)
|