diff options
Diffstat (limited to 'internal/log/log.go')
-rw-r--r-- | internal/log/log.go | 310 |
1 files changed, 237 insertions, 73 deletions
diff --git a/internal/log/log.go b/internal/log/log.go index 67dd03606..a9c7340ed 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -19,95 +19,259 @@ package log import ( - "bytes" + "fmt" "io" "log/syslog" "os" + "strings" + "syscall" + "time" - "github.com/sirupsen/logrus" - lSyslog "github.com/sirupsen/logrus/hooks/syslog" - "github.com/superseriousbusiness/gotosocial/internal/config" + "codeberg.org/gruf/go-atomics" + "codeberg.org/gruf/go-kv" + "codeberg.org/gruf/go-logger/v2/level" ) -// Initialize initializes the global Logrus logger, reading the desired -// log level from the viper store, or using a default if the level -// has not been set in viper. -// -// It also sets the output to log.SplitErrOutputs(...) -// so you get error logs on stderr and normal logs on stdout. -// -// If syslog settings are also in viper, then Syslog will be initialized as well. -func Initialize() error { - out := SplitErrOutputs(os.Stdout, os.Stderr) - logrus.SetOutput(out) - - // check if a desired log level has been set - if lvl := config.GetLogLevel(); lvl != "" { - level, err := logrus.ParseLevel(lvl) - if err != nil { - return err - } - logrus.SetLevel(level) - - if level == logrus.TraceLevel { - logrus.SetReportCaller(true) - } +var ( + // loglvl is the currently set logging level. + loglvl atomics.Uint32 + + // lvlstrs is the lookup table of log levels to strings. + lvlstrs = level.Default() + + // Preprepared stdout/stderr log writers. + stdout = &safewriter{w: os.Stdout} + stderr = &safewriter{w: os.Stderr} + + // Syslog output, only set if enabled. + sysout *syslog.Writer +) + +// Level returns the currently set log level. +func Level() level.LEVEL { + return level.LEVEL(loglvl.Load()) +} + +// SetLevel sets the max logging level. +func SetLevel(lvl level.LEVEL) { + loglvl.Store(uint32(lvl)) +} + +func WithField(key string, value interface{}) Entry { + return Entry{fields: []kv.Field{{K: key, V: value}}} +} + +func WithFields(fields ...kv.Field) Entry { + return Entry{fields: fields} +} + +func Trace(a ...interface{}) { + logf(level.TRACE, nil, args(len(a)), a...) +} + +func Tracef(s string, a ...interface{}) { + logf(level.TRACE, nil, s, a...) +} + +func Debug(a ...interface{}) { + logf(level.DEBUG, nil, args(len(a)), a...) +} + +func Debugf(s string, a ...interface{}) { + logf(level.DEBUG, nil, s, a...) +} + +func Info(a ...interface{}) { + logf(level.INFO, nil, args(len(a)), a...) +} + +func Infof(s string, a ...interface{}) { + logf(level.INFO, nil, s, a...) +} + +func Warn(a ...interface{}) { + logf(level.WARN, nil, args(len(a)), a...) +} + +func Warnf(s string, a ...interface{}) { + logf(level.WARN, nil, s, a...) +} + +func Error(a ...interface{}) { + logf(level.ERROR, nil, args(len(a)), a...) +} + +func Errorf(s string, a ...interface{}) { + logf(level.ERROR, nil, s, a...) +} + +func Fatal(a ...interface{}) { + defer syscall.Exit(1) + logf(level.FATAL, nil, args(len(a)), a...) +} + +func Fatalf(s string, a ...interface{}) { + defer syscall.Exit(1) + logf(level.FATAL, nil, s, a...) +} + +func Panic(a ...interface{}) { + defer panic(fmt.Sprint(a...)) + logf(level.PANIC, nil, args(len(a)), a...) +} + +func Panicf(s string, a ...interface{}) { + defer panic(fmt.Sprintf(s, a...)) + logf(level.PANIC, nil, s, a...) +} + +// Log will log formatted args as 'msg' field to the log at given level. +func Log(lvl level.LEVEL, a ...interface{}) { + logf(lvl, nil, args(len(a)), a...) +} + +// Logf will log format string as 'msg' field to the log at given level. +func Logf(lvl level.LEVEL, s string, a ...interface{}) { + logf(lvl, nil, s, a...) +} + +// Print will log formatted args to the stdout log output. +func Print(a ...interface{}) { + printf(nil, args(len(a)), a...) +} + +// Print will log format string to the stdout log output. +func Printf(s string, a ...interface{}) { + printf(nil, s, a...) +} + +func printf(fields []kv.Field, s string, a ...interface{}) { + // Acquire buffer + buf := getBuf() + + // Append formatted timestamp + now := time.Now().Format("02/01/2006 15:04:05.000") + buf.B = append(buf.B, `timestamp="`...) + buf.B = append(buf.B, now...) + buf.B = append(buf.B, `" `...) + + // Append formatted caller func + buf.B = append(buf.B, `func=`...) + buf.B = append(buf.B, Caller(4)...) + buf.B = append(buf.B, ' ') + + if len(fields) > 0 { + // Append formatted fields + kv.Fields(fields).AppendFormat(buf, false) + buf.B = append(buf.B, ' ') + } + + // Append formatted args + fmt.Fprintf(buf, s, a...) + + // Append a final newline + buf.B = append(buf.B, '\n') + + // Write to log and release + _, _ = stdout.Write(buf.B) + putBuf(buf) +} + +func logf(lvl level.LEVEL, fields []kv.Field, s string, a ...interface{}) { + var out io.Writer + + // Check if enabled. + if lvl > Level() { + return } - // set our custom formatter options - logrus.SetFormatter(&logrus.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - - // By default, quoting is enabled to help differentiate key-value - // fields in log lines. But when debug (or higher, e.g. trace) logging - // is enabled, we disable this. This allows easier copy-pasting of - // entry fields without worrying about escaped quotes. - DisableQuote: logrus.GetLevel() >= logrus.DebugLevel, - }) - - // check if syslog has been enabled, and configure it if so - if config.GetSyslogEnabled() { - protocol := config.GetSyslogProtocol() - address := config.GetSyslogAddress() - - hook, err := lSyslog.NewSyslogHook(protocol, address, syslog.LOG_INFO, "") - if err != nil { - return err - } - - logrus.AddHook(&trimHook{hook}) + // Split errors to stderr, + // all else goes to stdout. + if lvl <= level.ERROR { + out = stderr + } else { + out = stdout + } + + // Acquire buffer + buf := getBuf() + + // Append formatted timestamp + now := time.Now().Format("02/01/2006 15:04:05.000") + buf.B = append(buf.B, `timestamp="`...) + buf.B = append(buf.B, now...) + buf.B = append(buf.B, `" `...) + + // Append formatted caller func + buf.B = append(buf.B, `func=`...) + buf.B = append(buf.B, Caller(4)...) + buf.B = append(buf.B, ' ') + + // Append formatted level string + buf.B = append(buf.B, `level=`...) + buf.B = append(buf.B, lvlstrs[lvl]...) + buf.B = append(buf.B, ' ') + + // Append formatted fields with msg + kv.Fields(append(fields, kv.Field{ + "msg", fmt.Sprintf(s, a...), + })).AppendFormat(buf, false) + + // Append a final newline + buf.B = append(buf.B, '\n') + + if sysout != nil { + // Write log entry to syslog + logsys(lvl, buf.String()) } - return nil + // Write to log and release + _, _ = out.Write(buf.B) + putBuf(buf) } -// SplitErrOutputs returns an OutputSplitFunc that splits output to either one of -// two given outputs depending on whether the level is "error","fatal","panic". -func SplitErrOutputs(out, err io.Writer) OutputSplitFunc { - return func(lvl []byte) io.Writer { - switch string(lvl) /* convert to str for compare is no-alloc */ { - case "error", "fatal", "panic": - return err - default: - return out - } +// logsys will log given msg at given severity to the syslog. +func logsys(lvl level.LEVEL, msg string) { + // Truncate message if > 1700 chars + if len(msg) > 1700 { + msg = msg[:1697] + "..." + } + + // Log at appropriate syslog severity + switch lvl { + case level.TRACE: + case level.DEBUG: + case level.INFO: + _ = sysout.Info(msg) + case level.WARN: + _ = sysout.Warning(msg) + case level.ERROR: + _ = sysout.Err(msg) + case level.FATAL: + _ = sysout.Crit(msg) } } -// OutputSplitFunc implements the io.Writer interface for use with Logrus, and simply -// splits logs between stdout and stderr depending on their severity. -type OutputSplitFunc func(lvl []byte) io.Writer +// args returns an args format string of format '%v' * count. +func args(count int) string { + const args = `%v%v%v%v%v%v%v%v%v%v` + + `%v%v%v%v%v%v%v%v%v%v` + + `%v%v%v%v%v%v%v%v%v%v` + + `%v%v%v%v%v%v%v%v%v%v` + + // Use predetermined args str + if count < len(args) { + return args[:count*2] + } -var levelBytes = []byte("level=") + // Allocate buffer of needed len + var buf strings.Builder + buf.Grow(count * 2) -func (fn OutputSplitFunc) Write(b []byte) (int, error) { - var lvl []byte - if i := bytes.Index(b, levelBytes); i >= 0 { - blvl := b[i+len(levelBytes):] - if i := bytes.IndexByte(blvl, ' '); i >= 0 { - lvl = blvl[:i] - } + // Manually build an args str + for i := 0; i < count; i++ { + buf.WriteString(`%v`) } - return fn(lvl).Write(b) + + return buf.String() } |