189 lines
4.5 KiB
Go
189 lines
4.5 KiB
Go
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)
|
|
}
|