package logger import ( "errors" "io" "log" "os" "regexp" "strings" "time" "github.com/influxdata/wlog" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/internal/rotate" ) var prefixRegex = regexp.MustCompile("^[DIWE]!") const ( LogTargetFile = "file" LogTargetStderr = "stderr" ) // LogConfig contains the log configuration settings type LogConfig struct { // will set the log level to DEBUG Debug bool //will set the log level to ERROR Quiet bool //stderr, stdout, file or eventlog (Windows only) LogTarget string // will direct the logging output to a file. Empty string is // interpreted as stderr. If there is an error opening the file the // logger will fall back to stderr Logfile string // will rotate when current file at the specified time interval RotationInterval config.Duration // will rotate when current file size exceeds this parameter. RotationMaxSize config.Size // 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 } type creator interface { CreateLogger(cfg LogConfig) (io.Writer, error) } var loggerRegistry map[string]creator func registerLogger(name string, loggerCreator creator) { if loggerRegistry == nil { loggerRegistry = make(map[string]creator) } loggerRegistry[name] = loggerCreator } type telegrafLog struct { writer io.Writer internalWriter io.Writer timezone *time.Location } func (t *telegrafLog) Write(b []byte) (n int, err error) { var line []byte timeToPrint := time.Now().In(t.timezone) if !prefixRegex.Match(b) { line = append([]byte(timeToPrint.Format(time.RFC3339)+" I! "), b...) } else { line = append([]byte(timeToPrint.Format(time.RFC3339)+" "), b...) } return t.writer.Write(line) } func (t *telegrafLog) Close() error { stdErrWriter := os.Stderr // avoid closing stderr if t.internalWriter == stdErrWriter { return nil } closer, isCloser := t.internalWriter.(io.Closer) if !isCloser { return errors.New("the underlying writer cannot be closed") } return closer.Close() } // newTelegrafWriter returns a logging-wrapped writer. func newTelegrafWriter(w io.Writer, c LogConfig) (io.Writer, error) { timezoneName := c.LogWithTimezone if strings.ToLower(timezoneName) == "local" { timezoneName = "Local" } tz, err := time.LoadLocation(timezoneName) if err != nil { return nil, errors.New("error while setting logging timezone: " + err.Error()) } return &telegrafLog{ writer: wlog.NewWriter(w), internalWriter: w, timezone: tz, }, nil } // SetupLogging configures the logging output. func SetupLogging(cfg LogConfig) error { _, err := newLogWriter(cfg) return err } type telegrafLogCreator struct { } func (t *telegrafLogCreator) CreateLogger(cfg LogConfig) (io.Writer, error) { var writer, defaultWriter io.Writer defaultWriter = os.Stderr switch cfg.LogTarget { case LogTargetFile: if cfg.Logfile != "" { var err error if writer, err = rotate.NewFileWriter(cfg.Logfile, time.Duration(cfg.RotationInterval), int64(cfg.RotationMaxSize), cfg.RotationMaxArchives); err != nil { log.Printf("E! Unable to open %s (%s), using stderr", cfg.Logfile, err) writer = defaultWriter } } else { writer = defaultWriter } case LogTargetStderr, "": writer = defaultWriter default: log.Printf("E! Unsupported logtarget: %s, using stderr", cfg.LogTarget) writer = defaultWriter } return newTelegrafWriter(writer, cfg) } // Keep track what is actually set as a log output, because log package doesn't provide a getter. // It allows closing previous writer if re-set and have possibility to test what is actually set var actualLogger io.Writer func newLogWriter(cfg LogConfig) (io.Writer, error) { log.SetFlags(0) if cfg.Debug { wlog.SetLevel(wlog.DEBUG) } if cfg.Quiet { wlog.SetLevel(wlog.ERROR) } if !cfg.Debug && !cfg.Quiet { wlog.SetLevel(wlog.INFO) } var logWriter io.Writer if logCreator, ok := loggerRegistry[cfg.LogTarget]; ok { logWriter, _ = logCreator.CreateLogger(cfg) } if logWriter == nil { logWriter, _ = (&telegrafLogCreator{}).CreateLogger(cfg) } if closer, isCloser := actualLogger.(io.Closer); isCloser { if err := closer.Close(); err != nil { return nil, err } } log.SetOutput(logWriter) actualLogger = logWriter return logWriter, nil } func init() { tlc := &telegrafLogCreator{} registerLogger("", tlc) registerLogger(LogTargetStderr, tlc) registerLogger(LogTargetFile, tlc) }