277 lines
6.5 KiB
Go
277 lines
6.5 KiB
Go
package watermill
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"maps"
|
|
"os"
|
|
"reflect"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// LogFields is the logger's key-value list of fields.
|
|
type LogFields map[string]interface{}
|
|
|
|
// Add adds new fields to the list of LogFields.
|
|
func (l LogFields) Add(newFields LogFields) LogFields {
|
|
resultFields := make(LogFields, len(l)+len(newFields))
|
|
|
|
maps.Copy(resultFields, l)
|
|
maps.Copy(resultFields, newFields)
|
|
|
|
return resultFields
|
|
}
|
|
|
|
// Copy copies the LogFields.
|
|
func (l LogFields) Copy() LogFields {
|
|
cpy := make(LogFields, len(l))
|
|
maps.Copy(cpy, l)
|
|
|
|
return cpy
|
|
}
|
|
|
|
// LoggerAdapter is an interface, that you need to implement to support Watermill logging.
|
|
// You can use StdLoggerAdapter as a reference implementation.
|
|
type LoggerAdapter interface {
|
|
Error(msg string, err error, fields LogFields)
|
|
Info(msg string, fields LogFields)
|
|
Debug(msg string, fields LogFields)
|
|
Trace(msg string, fields LogFields)
|
|
With(fields LogFields) LoggerAdapter
|
|
}
|
|
|
|
// NopLogger is a logger which discards all logs.
|
|
type NopLogger struct{}
|
|
|
|
func (NopLogger) Error(msg string, err error, fields LogFields) {}
|
|
func (NopLogger) Info(msg string, fields LogFields) {}
|
|
func (NopLogger) Debug(msg string, fields LogFields) {}
|
|
func (NopLogger) Trace(msg string, fields LogFields) {}
|
|
func (l NopLogger) With(fields LogFields) LoggerAdapter { return l }
|
|
|
|
// StdLoggerAdapter is a logger implementation, which sends all logs to provided standard output.
|
|
type StdLoggerAdapter struct {
|
|
ErrorLogger *log.Logger
|
|
InfoLogger *log.Logger
|
|
DebugLogger *log.Logger
|
|
TraceLogger *log.Logger
|
|
|
|
fields LogFields
|
|
}
|
|
|
|
// NewStdLogger creates StdLoggerAdapter which sends all logs to stderr.
|
|
func NewStdLogger(debug, trace bool) LoggerAdapter {
|
|
return NewStdLoggerWithOut(os.Stderr, debug, trace)
|
|
}
|
|
|
|
// NewStdLoggerWithOut creates StdLoggerAdapter which sends all logs to provided io.Writer.
|
|
func NewStdLoggerWithOut(out io.Writer, debug bool, trace bool) LoggerAdapter {
|
|
l := log.New(out, "[watermill] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
|
|
a := &StdLoggerAdapter{InfoLogger: l, ErrorLogger: l}
|
|
|
|
if debug {
|
|
a.DebugLogger = l
|
|
}
|
|
if trace {
|
|
a.TraceLogger = l
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) Error(msg string, err error, fields LogFields) {
|
|
l.log(l.ErrorLogger, "ERROR", msg, fields.Add(LogFields{"err": err}))
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) Info(msg string, fields LogFields) {
|
|
l.log(l.InfoLogger, "INFO ", msg, fields)
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) Debug(msg string, fields LogFields) {
|
|
l.log(l.DebugLogger, "DEBUG", msg, fields)
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) Trace(msg string, fields LogFields) {
|
|
l.log(l.TraceLogger, "TRACE", msg, fields)
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) With(fields LogFields) LoggerAdapter {
|
|
return &StdLoggerAdapter{
|
|
ErrorLogger: l.ErrorLogger,
|
|
InfoLogger: l.InfoLogger,
|
|
DebugLogger: l.DebugLogger,
|
|
TraceLogger: l.TraceLogger,
|
|
fields: l.fields.Add(fields),
|
|
}
|
|
}
|
|
|
|
func (l *StdLoggerAdapter) log(logger *log.Logger, level string, msg string, fields LogFields) {
|
|
if logger == nil {
|
|
return
|
|
}
|
|
|
|
fieldsStr := ""
|
|
|
|
allFields := l.fields.Add(fields)
|
|
|
|
keys := make([]string, len(allFields))
|
|
i := 0
|
|
for field := range allFields {
|
|
keys[i] = field
|
|
i++
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, key := range keys {
|
|
var valueStr string
|
|
value := allFields[key]
|
|
|
|
if stringer, ok := value.(fmt.Stringer); ok {
|
|
valueStr = stringer.String()
|
|
} else {
|
|
valueStr = fmt.Sprintf("%v", value)
|
|
}
|
|
|
|
if strings.Contains(valueStr, " ") {
|
|
valueStr = `"` + valueStr + `"`
|
|
}
|
|
|
|
fieldsStr += key + "=" + valueStr + " "
|
|
}
|
|
|
|
_ = logger.Output(3, fmt.Sprintf("\t"+`level=%s msg="%s" %s`, level, msg, fieldsStr))
|
|
}
|
|
|
|
type LogLevel uint
|
|
|
|
const (
|
|
TraceLogLevel LogLevel = iota + 1
|
|
DebugLogLevel
|
|
InfoLogLevel
|
|
ErrorLogLevel
|
|
)
|
|
|
|
type CapturedMessage struct {
|
|
Level LogLevel
|
|
Time time.Time
|
|
Fields LogFields
|
|
Msg string
|
|
Err error
|
|
}
|
|
|
|
func (c CapturedMessage) ContentEquals(other CapturedMessage) bool {
|
|
return c.Level == other.Level &&
|
|
reflect.DeepEqual(c.Fields, other.Fields) &&
|
|
c.Msg == other.Msg &&
|
|
errors.Is(c.Err, other.Err)
|
|
}
|
|
|
|
// CaptureLoggerAdapter is a logger which captures all logs.
|
|
// This logger is mostly useful for testing logging.
|
|
type CaptureLoggerAdapter struct {
|
|
captured map[LogLevel][]CapturedMessage
|
|
fields LogFields
|
|
lock *sync.Mutex
|
|
}
|
|
|
|
func NewCaptureLogger() *CaptureLoggerAdapter {
|
|
return &CaptureLoggerAdapter{
|
|
captured: map[LogLevel][]CapturedMessage{},
|
|
lock: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) With(fields LogFields) LoggerAdapter {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
return &CaptureLoggerAdapter{
|
|
captured: c.captured, // we are passing the same map, so we'll capture logs from this instance as well
|
|
fields: c.fields.Copy().Add(fields),
|
|
lock: c.lock,
|
|
}
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) capture(level LogLevel, msg string, err error, fields LogFields) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
logMsg := CapturedMessage{
|
|
Level: level,
|
|
Time: time.Now(),
|
|
Fields: c.fields.Add(fields),
|
|
Msg: msg,
|
|
Err: err,
|
|
}
|
|
|
|
c.captured[level] = append(c.captured[level], logMsg)
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Captured() map[LogLevel][]CapturedMessage {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
cpy := make(map[LogLevel][]CapturedMessage, len(c.captured))
|
|
for level, msgs := range c.captured {
|
|
cpy[level] = slices.Clone(msgs)
|
|
}
|
|
return cpy
|
|
}
|
|
|
|
type Logfer interface {
|
|
Logf(format string, a ...interface{})
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) PrintCaptured(t Logfer) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
for level, messages := range c.captured {
|
|
for _, msg := range messages {
|
|
t.Logf("%s %d %s %v", msg.Time.Format("15:04:05.999999999"), level, msg.Msg, msg.Fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Has(msg CapturedMessage) bool {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
return slices.ContainsFunc(c.captured[msg.Level], msg.ContentEquals)
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) HasError(err error) bool {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
for _, capturedMsg := range c.captured[ErrorLogLevel] {
|
|
if errors.Is(err, capturedMsg.Err) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Error(msg string, err error, fields LogFields) {
|
|
c.capture(ErrorLogLevel, msg, err, fields)
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Info(msg string, fields LogFields) {
|
|
c.capture(InfoLogLevel, msg, nil, fields)
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Debug(msg string, fields LogFields) {
|
|
c.capture(DebugLogLevel, msg, nil, fields)
|
|
}
|
|
|
|
func (c *CaptureLoggerAdapter) Trace(msg string, fields LogFields) {
|
|
c.capture(TraceLogLevel, msg, nil, fields)
|
|
}
|