mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-22 15:11:16 +01:00
729c83ee72
* stack-based execution model, documented in documentation * circbuf for capturing hook output * built-in hooks for postgres and mysql * refactor docs, too much info on the jobs page, too difficult to discover snapshotting & hooks Co-authored-by: Ross Williams <ross@ross-williams.net> Co-authored-by: Christian Schwarz <me@cschwarz.com> fixes #74
185 lines
3.8 KiB
Go
185 lines
3.8 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime/debug"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// The field set by WithError function
|
|
FieldError = "err"
|
|
)
|
|
|
|
const DefaultUserFieldCapacity = 5
|
|
|
|
type Logger interface {
|
|
WithOutlet(outlet Outlet, level Level) Logger
|
|
ReplaceField(field string, val interface{}) Logger
|
|
WithField(field string, val interface{}) Logger
|
|
WithFields(fields Fields) Logger
|
|
WithError(err error) Logger
|
|
Log(level Level, msg string)
|
|
Debug(msg string)
|
|
Info(msg string)
|
|
Warn(msg string)
|
|
Error(msg string)
|
|
Printf(format string, args ...interface{})
|
|
}
|
|
|
|
type loggerImpl struct {
|
|
fields Fields
|
|
outlets *Outlets
|
|
outletTimeout time.Duration
|
|
|
|
mtx *sync.Mutex
|
|
}
|
|
|
|
var _ Logger = &loggerImpl{}
|
|
|
|
func NewLogger(outlets *Outlets, outletTimeout time.Duration) Logger {
|
|
return &loggerImpl{
|
|
make(Fields, DefaultUserFieldCapacity),
|
|
outlets,
|
|
outletTimeout,
|
|
&sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
type outletResult struct {
|
|
Outlet Outlet
|
|
Error error
|
|
}
|
|
|
|
func (l *loggerImpl) logInternalError(outlet Outlet, err string) {
|
|
fields := Fields{}
|
|
if outlet != nil {
|
|
if _, ok := outlet.(fmt.Stringer); ok {
|
|
fields["outlet"] = fmt.Sprintf("%s", outlet)
|
|
}
|
|
fields["outlet_type"] = fmt.Sprintf("%T", outlet)
|
|
}
|
|
fields[FieldError] = err
|
|
entry := Entry{
|
|
Error,
|
|
"outlet error",
|
|
time.Now(),
|
|
fields,
|
|
}
|
|
// ignore errors at this point (still better than panicking if the error is temporary)
|
|
_ = l.outlets.GetLoggerErrorOutlet().WriteEntry(entry)
|
|
}
|
|
|
|
func (l *loggerImpl) log(level Level, msg string) {
|
|
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
|
|
entry := Entry{level, msg, time.Now(), l.fields}
|
|
|
|
louts := l.outlets.Get(level)
|
|
ech := make(chan outletResult, len(louts))
|
|
for i := range louts {
|
|
go func(outlet Outlet, entry Entry) {
|
|
ech <- outletResult{outlet, outlet.WriteEntry(entry)}
|
|
}(louts[i], entry)
|
|
}
|
|
for fin := 0; fin < len(louts); fin++ {
|
|
res := <-ech
|
|
if res.Error != nil {
|
|
l.logInternalError(res.Outlet, res.Error.Error())
|
|
}
|
|
}
|
|
close(ech)
|
|
|
|
}
|
|
|
|
func (l *loggerImpl) WithOutlet(outlet Outlet, level Level) Logger {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
newOutlets := l.outlets.DeepCopy()
|
|
newOutlets.Add(outlet, level)
|
|
child := &loggerImpl{
|
|
fields: l.fields,
|
|
outlets: newOutlets,
|
|
outletTimeout: l.outletTimeout,
|
|
mtx: l.mtx,
|
|
}
|
|
return child
|
|
}
|
|
|
|
// callers must hold l.mtx
|
|
func (l *loggerImpl) forkLogger(field string, val interface{}) *loggerImpl {
|
|
|
|
child := &loggerImpl{
|
|
fields: make(Fields, len(l.fields)+1),
|
|
outlets: l.outlets,
|
|
outletTimeout: l.outletTimeout,
|
|
mtx: l.mtx,
|
|
}
|
|
for k, v := range l.fields {
|
|
child.fields[k] = v
|
|
}
|
|
child.fields[field] = val
|
|
|
|
return child
|
|
}
|
|
|
|
func (l *loggerImpl) ReplaceField(field string, val interface{}) Logger {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
return l.forkLogger(field, val)
|
|
}
|
|
|
|
func (l *loggerImpl) WithField(field string, val interface{}) Logger {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
if val, ok := l.fields[field]; ok && val != nil {
|
|
l.logInternalError(nil,
|
|
fmt.Sprintf("caller overwrites field '%s'. Stack: %s", field, string(debug.Stack())))
|
|
}
|
|
return l.forkLogger(field, val)
|
|
}
|
|
|
|
func (l *loggerImpl) WithFields(fields Fields) Logger {
|
|
// TODO optimize
|
|
var ret Logger = l
|
|
for field, value := range fields {
|
|
ret = ret.WithField(field, value)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (l *loggerImpl) WithError(err error) Logger {
|
|
val := interface{}(nil)
|
|
if err != nil {
|
|
val = err.Error()
|
|
}
|
|
return l.WithField(FieldError, val)
|
|
}
|
|
|
|
func (l *loggerImpl) Log(level Level, msg string) {
|
|
l.log(level, msg)
|
|
}
|
|
|
|
func (l *loggerImpl) Debug(msg string) {
|
|
l.log(Debug, msg)
|
|
}
|
|
|
|
func (l *loggerImpl) Info(msg string) {
|
|
l.log(Info, msg)
|
|
}
|
|
|
|
func (l *loggerImpl) Warn(msg string) {
|
|
l.log(Warn, msg)
|
|
}
|
|
|
|
func (l *loggerImpl) Error(msg string) {
|
|
l.log(Error, msg)
|
|
}
|
|
|
|
func (l *loggerImpl) Printf(format string, args ...interface{}) {
|
|
l.log(Error, fmt.Sprintf(format, args...))
|
|
}
|