diff --git a/config/config.go b/config/config.go index 58ab172be2565..461dfba3344e8 100644 --- a/config/config.go +++ b/config/config.go @@ -252,6 +252,10 @@ type AgentConfig struct { // Ignored if "logformat" is not "structured". StructuredLogMessageKey string `toml:"structured_log_message_key"` + // When set to true and using logformat = "structured", the tags of each + // input plugin are added to the log output as a "tags" JSON field. + StructuredLogAddInputTags bool `toml:"structured_log_add_input_tags"` + // 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"` @@ -570,6 +574,19 @@ func (c *Config) LoadAll(configFiles ...string) error { } } + // When loading multiple config files (e.g. via --config-directory), the + // [agent] section may be parsed after some input files, meaning + // buildInput saw StructuredLogAddInputTags=false and skipped the logger + // attribute. Therefore, it needs to be parsed again after loading all configs. + if c.Agent.StructuredLogAddInputTags { + for _, input := range c.Inputs { + if !input.Config.AddTagsToStructuredLogs && len(input.Config.Tags) > 0 { + input.Config.AddTagsToStructuredLogs = true + input.Log().AddAttribute("tags", input.Config.Tags) + } + } + } + // Sort the processors according to their `order` setting while // using a stable sort to keep the file loading / file position order. sort.Stable(c.Processors) @@ -1690,6 +1707,7 @@ func (c *Config) buildInput(name, source string, tbl *ast.Table) (*models.InputC Source: source, AlwaysIncludeLocalTags: c.Agent.AlwaysIncludeLocalTags, AlwaysIncludeGlobalTags: c.Agent.AlwaysIncludeGlobalTags, + AddTagsToStructuredLogs: c.Agent.StructuredLogAddInputTags, } cp.Interval, _ = c.getFieldDuration(tbl, "interval") cp.Precision, _ = c.getFieldDuration(tbl, "precision") diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index df5d79dc5a85c..9274223562090 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -311,6 +311,28 @@ The agent table configures Telegraf and the defaults used across all plugins. Message key for structured logs, to override the default of "msg". Ignored if `logformat` is not "structured". +- **structured_log_add_input_tags**: + To add input configuration tags to the log output. + Example: + + ```json + { + "time":"2026-03-27T09:14:36.078389805Z", + "level":"INFO", + "msg":"Error connecting to target. Restarting in 10s...", + "category":"inputs", + "plugin":"execd", + "tags":{ + "server_host":"10.0.1.20", + "friendly_name":"friendly_server_name", + "log_source":"telegraf", + "metric_type":"logs" + } + } + ``` + + 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/models/running_input.go b/models/running_input.go index 2342cf0f16dfb..a93876d048157 100644 --- a/models/running_input.go +++ b/models/running_input.go @@ -54,6 +54,9 @@ func NewRunningInput(input telegraf.Input, config *InputConfig) *RunningInput { if err := logger.SetLogLevel(config.LogLevel); err != nil { logger.Error(err) } + if config.AddTagsToStructuredLogs && len(config.Tags) > 0 { + logger.AddAttribute("tags", config.Tags) + } SetLoggerOnPlugin(input, logger) SetStatisticsOnPlugin(input, logger, tags) @@ -110,6 +113,10 @@ type InputConfig struct { Filter Filter AlwaysIncludeLocalTags bool AlwaysIncludeGlobalTags bool + + // When true and using logformat = "structured", the input's tags are + // added to the log output as a "tags" JSON field. + AddTagsToStructuredLogs bool } func (*RunningInput) metricFiltered(metric telegraf.Metric) {