1
0
Fork 0
telegraf/logger/logger.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

314 lines
7.9 KiB
Go

package logger
import (
"errors"
"fmt"
"io"
"log"
"os"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf"
)
// Central handler for the logs used by the logger to actually output the logs.
// This is necessary to be able to dynamically switch the sink even though
// plugins already instantiated a logger _before_ the final sink is set up.
var (
instance *handler // handler for the actual output
once sync.Once // once token to initialize the handler only once
)
// sink interface that has to be implemented by a logging sink
type sink interface {
Print(telegraf.LogLevel, time.Time, string, map[string]interface{}, ...interface{})
}
// logger is the actual implementation of the telegraf logger interface
type logger struct {
level *telegraf.LogLevel
category string
name string
alias string
prefix string
onError []func()
attributes map[string]interface{}
}
// New creates a new logging instance to be used in models
func New(category, name, alias string) *logger {
l := &logger{
category: category,
name: name,
alias: alias,
attributes: map[string]interface{}{"category": category, "plugin": name},
}
if alias != "" {
l.attributes["alias"] = alias
}
// Format the prefix
l.prefix = l.category
if l.prefix != "" && l.name != "" {
l.prefix += "."
}
l.prefix += l.name
if l.prefix != "" && l.alias != "" {
l.prefix += "::"
}
l.prefix += l.alias
if l.prefix != "" {
l.prefix = "[" + l.prefix + "] "
}
return l
}
// Level returns the current log-level of the logger
func (l *logger) Level() telegraf.LogLevel {
if l.level != nil {
return *l.level
}
return instance.level
}
// AddAttribute allows to add a key-value attribute to the logging output
func (l *logger) AddAttribute(key string, value interface{}) {
// Do not allow to overwrite general keys
switch key {
case "category", "plugin", "alias":
default:
l.attributes[key] = value
}
}
// Error logging including callbacks
func (l *logger) Errorf(format string, args ...interface{}) {
l.Error(fmt.Sprintf(format, args...))
}
func (l *logger) Error(args ...interface{}) {
l.Print(telegraf.Error, time.Now(), args...)
for _, f := range l.onError {
f()
}
}
// Warning logging
func (l *logger) Warnf(format string, args ...interface{}) {
l.Warn(fmt.Sprintf(format, args...))
}
func (l *logger) Warn(args ...interface{}) {
l.Print(telegraf.Warn, time.Now(), args...)
}
// Info logging
func (l *logger) Infof(format string, args ...interface{}) {
l.Info(fmt.Sprintf(format, args...))
}
func (l *logger) Info(args ...interface{}) {
l.Print(telegraf.Info, time.Now(), args...)
}
// Debug logging, this is suppressed on console
func (l *logger) Debugf(format string, args ...interface{}) {
l.Debug(fmt.Sprintf(format, args...))
}
func (l *logger) Debug(args ...interface{}) {
l.Print(telegraf.Debug, time.Now(), args...)
}
// Trace logging, this is suppressed on console
func (l *logger) Tracef(format string, args ...interface{}) {
l.Trace(fmt.Sprintf(format, args...))
}
func (l *logger) Trace(args ...interface{}) {
l.Print(telegraf.Trace, time.Now(), args...)
}
func (l *logger) Print(level telegraf.LogLevel, ts time.Time, args ...interface{}) {
// Check if we are in early logging state and store the message in this case
if instance.impl == nil {
instance.add(level, ts, l.prefix, l.attributes, args...)
}
// Skip all messages with insufficient log-levels
if l.level != nil && !l.level.Includes(level) || l.level == nil && !instance.level.Includes(level) {
return
}
if instance.impl != nil {
instance.impl.Print(level, ts.In(instance.timezone), l.prefix, l.attributes, args...)
} else {
msg := append([]interface{}{ts.In(instance.timezone).Format(time.RFC3339), " ", level.Indicator(), " ", l.prefix}, args...)
instance.earlysink.Print(msg...)
}
}
// SetLevel overrides the current log-level of the logger
func (l *logger) SetLevel(level telegraf.LogLevel) {
l.level = &level
}
// SetLevel changes the log-level to the given one
func (l *logger) SetLogLevel(name string) error {
if name == "" {
return nil
}
level := telegraf.LogLevelFromString(name)
if level == telegraf.None {
return fmt.Errorf("invalid log-level %q", name)
}
l.SetLevel(level)
return nil
}
// Register a callback triggered when errors are about to be written to the log
func (l *logger) RegisterErrorCallback(f func()) {
l.onError = append(l.onError, f)
}
type Config struct {
// will set the log level to DEBUG
Debug bool
// will set the log level to ERROR
Quiet bool
// format and target of log messages
LogTarget string
LogFormat string
Logfile string
// will rotate when current file at the specified time interval
RotationInterval time.Duration
// will rotate when current file size exceeds this parameter.
RotationMaxSize int64
// maximum rotated files to keep (older ones will be deleted)
RotationMaxArchives int
// pick a timezone to use when logging. or type 'local' for local time.
LogWithTimezone string
// Logger instance name
InstanceName string
// Structured logging message key
StructuredLogMessageKey string
// internal log-level
logLevel telegraf.LogLevel
}
// SetupLogging configures the logging output.
func SetupLogging(cfg *Config) error {
// Issue deprecation warning for option
switch cfg.LogTarget {
case "":
// Best-case no target set or file already migrated...
case "stderr":
msg := "Agent setting %q is deprecated, please leave %q empty and remove this setting!"
deprecation := "The setting will be removed in v1.40.0."
log.Printf("W! "+msg+" "+deprecation, "logtarget", "logfile")
cfg.Logfile = ""
case "file":
msg := "Agent setting %q is deprecated, please just set %q and remove this setting!"
deprecation := "The setting will be removed in v1.40.0."
log.Printf("W! "+msg+" "+deprecation, "logtarget", "logfile")
case "eventlog":
msg := "Agent setting %q is deprecated, please set %q to %q and remove this setting!"
deprecation := "The setting will be removed in v1.40.0."
log.Printf("W! "+msg+" "+deprecation, "logtarget", "logformat", "eventlog")
if cfg.LogFormat != "" && cfg.LogFormat != "eventlog" {
return errors.New("contradicting setting between 'logtarget' and 'logformat'")
}
cfg.LogFormat = "eventlog"
default:
return fmt.Errorf("invalid deprecated 'logtarget' setting %q", cfg.LogTarget)
}
if cfg.LogFormat == "" {
cfg.LogFormat = "text"
}
if cfg.Debug {
cfg.logLevel = telegraf.Debug
}
if cfg.Quiet {
cfg.logLevel = telegraf.Error
}
if !cfg.Debug && !cfg.Quiet {
cfg.logLevel = telegraf.Info
}
if cfg.InstanceName == "" {
cfg.InstanceName = "telegraf"
}
if cfg.LogFormat == "" {
cfg.LogFormat = "text"
}
// Get configured timezone
timezoneName := cfg.LogWithTimezone
if strings.EqualFold(timezoneName, "local") {
timezoneName = "Local"
}
tz, err := time.LoadLocation(timezoneName)
if err != nil {
return fmt.Errorf("setting logging timezone failed: %w", err)
}
// Get the logging factory and create the root instance
creator, found := registry[cfg.LogFormat]
if !found {
return fmt.Errorf("unsupported log-format: %s", cfg.LogFormat)
}
l, err := creator(cfg)
if err != nil {
return err
}
// Close the previous logger if possible
if err := CloseLogging(); err != nil {
return err
}
// Update the logging instance
skipEarlyLogs := cfg.LogFormat == "text" && cfg.Logfile == ""
instance.switchSink(l, cfg.logLevel, tz, skipEarlyLogs)
return nil
}
func RedirectLogging(w io.Writer) {
instance = redirectHandler(w)
}
func CloseLogging() error {
if instance == nil {
return nil
}
if err := instance.close(); err != nil && !errors.Is(err, os.ErrClosed) {
return err
}
return nil
}
func init() {
once.Do(func() {
// Create a special logging instance that additionally buffers all
// messages logged before the final logger is up.
instance = defaultHandler()
// Redirect the standard logger output to our logger instance
log.SetFlags(0)
log.SetOutput(&stdlogRedirector{})
})
}