314 lines
7.9 KiB
Go
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{})
|
|
})
|
|
}
|