From 7355669f392d0e4e284b68ee653d46e58376fdd4 Mon Sep 17 00:00:00 2001 From: Alex Gokhale Date: Thu, 28 Nov 2024 08:50:26 +0000 Subject: [PATCH] feat(slog): Allow overriding the log message key This adds a config option which controls the message key used for structured logs, which currently defaults to the `slog.MessageKey` value of "msg". --- cmd/telegraf/agent.conf | 4 ++++ cmd/telegraf/telegraf.go | 19 ++++++++-------- config/config.go | 4 ++++ docs/CONFIGURATION.md | 4 ++++ logger/logger.go | 2 ++ logger/structured_logger.go | 36 +++++++++++++++++++++--------- logger/structured_logger_test.go | 38 ++++++++++++++++++++++++++++++++ 7 files changed, 87 insertions(+), 20 deletions(-) diff --git a/cmd/telegraf/agent.conf b/cmd/telegraf/agent.conf index 12fd81ac4008c..dc2961c94bac0 100644 --- a/cmd/telegraf/agent.conf +++ b/cmd/telegraf/agent.conf @@ -57,6 +57,10 @@ ## "structured" or, on Windows, "eventlog". # logformat = "text" + ## Message key for structured logs, to override the default of "msg". + ## Ignored if `logformat` is not "structured". + # structured_log_message_key = "message" + ## Name of the file to be logged to or stderr if unset or empty. This ## setting is ignored for the "eventlog" format. # logfile = "" diff --git a/cmd/telegraf/telegraf.go b/cmd/telegraf/telegraf.go index de886c48eed7d..4fad778933f45 100644 --- a/cmd/telegraf/telegraf.go +++ b/cmd/telegraf/telegraf.go @@ -364,15 +364,16 @@ func (t *Telegraf) runAgent(ctx context.Context, reloadConfig bool) error { // Setup logging as configured. logConfig := &logger.Config{ - Debug: c.Agent.Debug || t.debug, - Quiet: c.Agent.Quiet || t.quiet, - LogTarget: c.Agent.LogTarget, - LogFormat: c.Agent.LogFormat, - Logfile: c.Agent.Logfile, - RotationInterval: time.Duration(c.Agent.LogfileRotationInterval), - RotationMaxSize: int64(c.Agent.LogfileRotationMaxSize), - RotationMaxArchives: c.Agent.LogfileRotationMaxArchives, - LogWithTimezone: c.Agent.LogWithTimezone, + Debug: c.Agent.Debug || t.debug, + Quiet: c.Agent.Quiet || t.quiet, + LogTarget: c.Agent.LogTarget, + LogFormat: c.Agent.LogFormat, + Logfile: c.Agent.Logfile, + StructuredLogMessageKey: c.Agent.StructuredLogMessageKey, + RotationInterval: time.Duration(c.Agent.LogfileRotationInterval), + RotationMaxSize: int64(c.Agent.LogfileRotationMaxSize), + RotationMaxArchives: c.Agent.LogfileRotationMaxArchives, + LogWithTimezone: c.Agent.LogWithTimezone, } if err := logger.SetupLogging(logConfig); err != nil { diff --git a/config/config.go b/config/config.go index d80033a6050f1..3cc8f59d62eaf 100644 --- a/config/config.go +++ b/config/config.go @@ -236,6 +236,10 @@ type AgentConfig struct { // Name of the file to be logged to or stderr if empty. Ignored for "eventlog" format. Logfile string `toml:"logfile"` + // Message key for structured logs, to override the default of "msg". + // Ignored if "logformat" is not "structured". + StructuredLogMessageKey string `toml:"structured_log_message_key"` + // The file will be rotated after the time interval specified. When set // to 0 no time based rotation is performed. LogfileRotationInterval Duration `toml:"logfile_rotation_interval"` diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index bab126fa06f9c..f104c06049e32 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -307,6 +307,10 @@ The agent table configures Telegraf and the defaults used across all plugins. "structured" or, on Windows, "eventlog". The output file (if any) is determined by the `logfile` setting. +- **structured_log_message_key**: + Message key for structured logs, to override the default of "msg". + Ignored if `logformat` is not "structured". + - **logfile**: Name of the file to be logged to or stderr if unset or empty. This setting is ignored for the "eventlog" format. diff --git a/logger/logger.go b/logger/logger.go index c344838c0b667..ab5a2ff2fb4c9 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -195,6 +195,8 @@ type Config struct { LogWithTimezone string // Logger instance name InstanceName string + // Structured logging message key + StructuredLogMessageKey string // internal log-level logLevel telegraf.LogLevel diff --git a/logger/structured_logger.go b/logger/structured_logger.go index dfe7dc2756f5e..5a4ed86a40e93 100644 --- a/logger/structured_logger.go +++ b/logger/structured_logger.go @@ -41,17 +41,19 @@ func (l *structuredLogger) Print(level telegraf.LogLevel, ts time.Time, _ string } } -var defaultStructuredHandlerOptions = &slog.HandlerOptions{ - Level: slog.Level(-99), - ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr { - // Translate the Telegraf log-levels to strings - if attr.Key == slog.LevelKey { - if level, ok := attr.Value.Any().(slog.Level); ok { - attr.Value = slog.StringValue(telegraf.LogLevel(level).String()) - } +var defaultReplaceAttr = func(_ []string, attr slog.Attr) slog.Attr { + // Translate the Telegraf log-levels to strings + if attr.Key == slog.LevelKey { + if level, ok := attr.Value.Any().(slog.Level); ok { + attr.Value = slog.StringValue(telegraf.LogLevel(level).String()) } - return attr - }, + } + return attr +} + +var defaultStructuredHandlerOptions = &slog.HandlerOptions{ + Level: slog.Level(-99), + ReplaceAttr: defaultReplaceAttr, } func init() { @@ -70,8 +72,20 @@ func init() { writer = w } + structuredHandlerOptions := defaultStructuredHandlerOptions + + if cfg.StructuredLogMessageKey != "" { + structuredHandlerOptions.ReplaceAttr = func(groups []string, attr slog.Attr) slog.Attr { + if attr.Key == slog.MessageKey { + attr.Key = cfg.StructuredLogMessageKey + } + + return defaultReplaceAttr(groups, attr) + } + } + return &structuredLogger{ - handler: slog.NewJSONHandler(writer, defaultStructuredHandlerOptions), + handler: slog.NewJSONHandler(writer, structuredHandlerOptions), output: writer, errlog: log.New(os.Stderr, "", 0), }, nil diff --git a/logger/structured_logger_test.go b/logger/structured_logger_test.go index 89208563a225b..1721bc48f5bcf 100644 --- a/logger/structured_logger_test.go +++ b/logger/structured_logger_test.go @@ -307,6 +307,44 @@ func TestStructuredWriteToFileInRotation(t *testing.T) { require.Len(t, files, 2) } +func TestStructuredLogMessageKey(t *testing.T) { + instance = defaultHandler() + + tmpfile, err := os.CreateTemp("", "") + require.NoError(t, err) + defer os.Remove(tmpfile.Name()) + + cfg := &Config{ + Logfile: tmpfile.Name(), + LogFormat: "structured", + RotationMaxArchives: -1, + Debug: true, + StructuredLogMessageKey: "message", + } + require.NoError(t, SetupLogging(cfg)) + + l := New("testing", "test", "") + l.Info("TEST") + + buf, err := os.ReadFile(tmpfile.Name()) + require.NoError(t, err) + + expected := map[string]interface{}{ + "level": "INFO", + "message": "TEST", + "category": "testing", + "plugin": "test", + } + + var actual map[string]interface{} + require.NoError(t, json.Unmarshal(buf, &actual)) + + require.Contains(t, actual, "time") + require.NotEmpty(t, actual["time"]) + delete(actual, "time") + require.Equal(t, expected, actual) +} + func BenchmarkTelegrafStructuredLogWrite(b *testing.B) { // Discard all logging output l := &structuredLogger{