Files
core/log/log.go
2024-05-29 15:51:51 +02:00

367 lines
7.7 KiB
Go

// Package log provides an opiniated logging facility as it provides only 4 log levels.
package log
import (
"encoding/json"
"fmt"
"reflect"
"runtime"
"runtime/debug"
"strings"
"time"
)
// LogLevel represents a log level
type Level uint
const (
Lsilent Level = 0
Lerror Level = 1
Lwarn Level = 2
Linfo Level = 3
Ldebug Level = 4
)
// String returns a string representing the log level.
func (level Level) String() string {
names := []string{
"SILENT",
"ERROR",
"WARN",
"INFO",
"DEBUG",
}
if level > Ldebug {
return `¯\_(ツ)_/¯`
}
return names[level]
}
func (level *Level) MarshalJSON() ([]byte, error) {
return json.Marshal(level.String())
}
type Fields map[string]interface{}
// Logger is an interface that provides means for writing log messages.
//
// There are 4 log levels available (debug, info, warn, error) with increasing
// severity. A message will be written to an output if the log level of the message
// has the same or a higher severity than the output. Otherwise it will be
// discarded.
//
// Different outputs should be added with the AddOutput function.
//
// The context is a string that represents who wrote the message.
type Logger interface {
// WithOutput adds an output to the Logger. The messages are written to the
// provided writer if the log level of the message is more or equally critical
// than level. Pass true for useColoe if colored output is desired. If the
// writer doesn't support colored output, it will be automatically disabled.
// The returned value implements the LoggerOutput interface which allows to
// change the log level at any later point in time.
WithOutput(w Writer) Logger
// With returns a new Logger with the given context. The context may
// printed along the message. This is up to the implementation.
WithComponent(component string) Logger
WithField(key string, value interface{}) Logger
WithFields(fields Fields) Logger
WithError(err error) Logger
Log(format string, args ...interface{})
// Debug writes a message with the debug log level to all registered outputs.
// The message will be written according to fmt.Printf(). The detail field will
// be reset to nil.
Debug() Logger
// Info writes a message with the info log level to all registered outputs.
// The message will be written according to fmt.Printf(). The detail field will
// be reset to nil.
Info() Logger
// Warn writes a message with the warn log level to all registered outputs.
// The message will be written according to fmt.Printf(). The detail field will
// be reset to nil.
Warn() Logger
// Error writes a message with the error log level to all registered outputs.
// The message will be written according to fmt.Printf(). The detail field will
// be reset to nil.
Error() Logger
// WithLevel writes a message with the given level to all registered outputs.
// The message will be written according to fmt.Printf(). The detail field will
// be reset to nil.
WithLevel(level Level) Logger
// Write implements the io.Writer interface such that it can be used in e.g. the
// the log/Logger facility. Messages will be printed with debug level.
Write(p []byte) (int, error)
}
// logger is an implementation of the Logger interface.
type logger struct {
output Writer
component string
modulePath string
}
// New returns an implementation of the Logger interface.
func New(component string) Logger {
l := &logger{
component: component,
}
if info, ok := debug.ReadBuildInfo(); ok {
l.modulePath = info.Path
}
return l
}
func (l *logger) clone() *logger {
clone := &logger{
output: l.output,
component: l.component,
modulePath: l.modulePath,
}
return clone
}
func (l *logger) WithOutput(w Writer) Logger {
clone := l.clone()
clone.output = w
return clone
}
func (l *logger) WithField(key string, value interface{}) Logger {
return newEvent(l).WithField(key, value)
}
func (l *logger) WithFields(f Fields) Logger {
return newEvent(l).WithFields(f)
}
func (l *logger) WithError(err error) Logger {
return newEvent(l).WithError(err)
}
func (l *logger) WithComponent(component string) Logger {
clone := l.clone()
clone.component = component
return clone
}
func (l *logger) Log(format string, args ...interface{}) {
e := newEvent(l)
e.Log(format, args...)
}
func (l *logger) Debug() Logger {
return newEvent(l).Debug()
}
func (l *logger) Info() Logger {
return newEvent(l).Info()
}
func (l *logger) Warn() Logger {
return newEvent(l).Warn()
}
func (l *logger) Error() Logger {
return newEvent(l).Error()
}
func (l *logger) WithLevel(level Level) Logger {
return newEvent(l).WithLevel(level)
}
func (l *logger) Write(p []byte) (int, error) {
return newEvent(l).Write(p)
}
type Event struct {
logger *logger
Time time.Time
Level Level
Component string
Caller string
Message string
err string
Data Fields
}
func newEvent(l *logger) Logger {
e := &Event{
logger: l,
Component: l.component,
Data: map[string]interface{}{},
}
return e
}
func (e *Event) WithOutput(w Writer) Logger {
return e.logger.WithOutput(w)
}
func (e *Event) WithComponent(component string) Logger {
clone := e.clone()
clone.Component = component
return clone
}
func (e *Event) Log(format string, args ...interface{}) {
_, file, line, _ := runtime.Caller(1)
file = strings.TrimPrefix(file, e.logger.modulePath)
n := e.clone()
n.logger = nil
n.Time = time.Now()
n.Caller = fmt.Sprintf("%s:%d", file, line)
if n.Level == Lsilent {
n.Level = Ldebug
}
if len(format) != 0 {
if len(args) == 0 {
n.Message = format
} else {
n.Message = fmt.Sprintf(format, args...)
}
}
if e.logger.output != nil {
e.logger.output.Write(n)
}
}
func (e *Event) clone() *Event {
data := make(Fields, len(e.Data))
for k, v := range e.Data {
data[k] = v
}
return &Event{
Time: e.Time,
Caller: e.Caller,
logger: e.logger,
Level: e.Level,
Component: e.Component,
Message: e.Message,
err: e.err,
Data: data,
}
}
func (e *Event) WithField(key string, value interface{}) Logger {
return e.WithFields(Fields{
key: value,
})
}
const maxFields = 1024
func (e *Event) WithFields(f Fields) Logger {
if maxFields-len(e.Data)-len(f) < 0 {
return e
}
data := make(Fields, len(e.Data)+len(f))
for k, v := range e.Data {
data[k] = v
}
fieldErr := e.err
for k, v := range f {
isErrField := false
if t := reflect.TypeOf(v); t != nil {
switch {
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
isErrField = true
}
}
if isErrField {
tmp := fmt.Sprintf("can not add field %q", k)
if fieldErr != "" {
fieldErr = e.err + ", " + tmp
} else {
fieldErr = tmp
}
} else {
data[k] = v
}
}
return &Event{
logger: e.logger,
Component: e.Component,
Level: e.Level,
err: fieldErr,
Data: data,
}
}
func (e *Event) WithError(err error) Logger {
return e.WithFields(Fields{
"error": err,
})
}
func (e *Event) Debug() Logger {
return e.WithLevel(Ldebug)
}
func (e *Event) Info() Logger {
return e.WithLevel(Linfo)
}
func (e *Event) Warn() Logger {
return e.WithLevel(Lwarn)
}
func (e *Event) Error() Logger {
return e.WithLevel(Lerror)
}
func (e *Event) WithLevel(level Level) Logger {
clone := e.clone()
clone.Level = level
return clone
}
func (l *Event) Write(p []byte) (int, error) {
l.Log("%s", strings.TrimSpace(string(p)))
return len(p), nil
}
type Eventx struct {
Time time.Time `json:"ts"`
Level Level `json:"level"`
Component string `json:"component"`
Reference string `json:"ref"`
Message string `json:"message"`
Caller string `json:"caller"`
Detail interface{} `json:"detail"`
}