dump logrus and roll our own logger instead
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
const (
|
||||
Debug Level = iota
|
||||
Info
|
||||
Warn
|
||||
Error
|
||||
)
|
||||
|
||||
func (l Level) Short() string {
|
||||
switch l {
|
||||
case Debug:
|
||||
return "DEBG"
|
||||
case Info:
|
||||
return "INFO"
|
||||
case Warn:
|
||||
return "WARN"
|
||||
case Error:
|
||||
return "ERRO"
|
||||
default:
|
||||
return fmt.Sprintf("%s", l)
|
||||
}
|
||||
}
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case Debug:
|
||||
return "debug"
|
||||
case Info:
|
||||
return "info"
|
||||
case Warn:
|
||||
return "warn"
|
||||
case Error:
|
||||
return "error"
|
||||
default:
|
||||
return fmt.Sprintf("%s", l)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseLevel(s string) (l Level, err error) {
|
||||
for _, l := range AllLevels {
|
||||
if s == l.String() {
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
return -1, errors.Errorf("unknown level '%s'", s)
|
||||
}
|
||||
|
||||
// Levels ordered least severe to most severe
|
||||
var AllLevels []Level = []Level{Debug, Info, Warn, Error}
|
||||
|
||||
type Fields map[string]interface{}
|
||||
|
||||
type Entry struct {
|
||||
Level Level
|
||||
Message string
|
||||
Time time.Time
|
||||
Fields Fields
|
||||
}
|
||||
|
||||
type Outlet interface {
|
||||
// Note: os.Stderr is also used by logger.Logger for reporting errors returned by outlets
|
||||
// => you probably don't want to log there
|
||||
WriteEntry(ctx context.Context, entry Entry) error
|
||||
}
|
||||
|
||||
type Outlets map[Level][]Outlet
|
||||
|
||||
func NewOutlets() Outlets {
|
||||
return make(Outlets, len(AllLevels))
|
||||
}
|
||||
|
||||
func (os Outlets) Add(outlet Outlet, minLevel Level) {
|
||||
for _, l := range AllLevels[minLevel:] {
|
||||
os[l] = append(os[l], outlet)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// The field set by WithError function
|
||||
FieldError = "err"
|
||||
)
|
||||
|
||||
const DefaultUserFieldCapacity = 5
|
||||
const InternalErrorPrefix = "github.com/zrepl/zrepl/logger: "
|
||||
|
||||
type Logger struct {
|
||||
fields Fields
|
||||
outlets Outlets
|
||||
outletTimeout time.Duration
|
||||
|
||||
mtx *sync.Mutex
|
||||
}
|
||||
|
||||
func NewLogger(outlets Outlets, outletTimeout time.Duration) *Logger {
|
||||
return &Logger{
|
||||
make(Fields, DefaultUserFieldCapacity),
|
||||
outlets,
|
||||
outletTimeout,
|
||||
&sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) log(level Level, msg string) {
|
||||
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
|
||||
entry := Entry{level, msg, time.Now(), l.fields}
|
||||
|
||||
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(l.outletTimeout))
|
||||
ech := make(chan error)
|
||||
|
||||
louts := l.outlets[level]
|
||||
for i := range louts {
|
||||
go func(ctx context.Context, outlet Outlet, entry Entry) {
|
||||
ech <- outlet.WriteEntry(ctx, entry)
|
||||
}(ctx, louts[i], entry)
|
||||
}
|
||||
|
||||
for fin := 0; fin < len(louts); fin++ {
|
||||
select {
|
||||
case err := <-ech:
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s outlet error: %s\n", InternalErrorPrefix, err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
fmt.Fprintf(os.Stderr, "%s outlets exceeded deadline, keep waiting anyways", InternalErrorPrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (l *Logger) WithField(field string, val interface{}) *Logger {
|
||||
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
|
||||
if _, ok := l.fields[field]; ok {
|
||||
fmt.Fprintf(os.Stderr, "%s caller overwrites field '%s'. Stack:\n%s\n", InternalErrorPrefix, string(debug.Stack()))
|
||||
}
|
||||
|
||||
child := &Logger{
|
||||
fields: make(Fields, len(l.fields)+1),
|
||||
outlets: l.outlets, // cannot be changed after logger initialized
|
||||
outletTimeout: l.outletTimeout,
|
||||
mtx: l.mtx,
|
||||
}
|
||||
for k, v := range l.fields {
|
||||
child.fields[k] = v
|
||||
}
|
||||
child.fields[field] = val
|
||||
|
||||
return child
|
||||
|
||||
}
|
||||
|
||||
func (l *Logger) WithFields(fields Fields) (ret *Logger) {
|
||||
// TODO optimize
|
||||
ret = l
|
||||
for field, value := range fields {
|
||||
ret = l.WithField(field, value)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (l *Logger) WithError(err error) *Logger {
|
||||
return l.WithField(FieldError, err.Error())
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(msg string) {
|
||||
l.log(Debug, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(msg string) {
|
||||
l.log(Info, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(msg string) {
|
||||
l.log(Warn, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(msg string) {
|
||||
l.log(Error, msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Printf(format string, args ...interface{}) {
|
||||
l.log(Error, fmt.Sprintf(format, args...))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package logger_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/zrepl/zrepl/logger"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TestOutlet struct {
|
||||
Record []logger.Entry
|
||||
}
|
||||
|
||||
func (o *TestOutlet) WriteEntry(ctx context.Context, entry logger.Entry) error {
|
||||
o.Record = append(o.Record, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTestOutlet() *TestOutlet {
|
||||
return &TestOutlet{make([]logger.Entry, 0)}
|
||||
}
|
||||
|
||||
func TestLogger_Basic(t *testing.T) {
|
||||
|
||||
outlet_arr := []logger.Outlet{
|
||||
NewTestOutlet(),
|
||||
NewTestOutlet(),
|
||||
}
|
||||
|
||||
outlets := logger.NewOutlets()
|
||||
for _, o := range outlet_arr {
|
||||
outlets.Add(o, logger.Debug)
|
||||
}
|
||||
|
||||
l := logger.NewLogger(outlets, 1*time.Second)
|
||||
|
||||
l.Info("foobar")
|
||||
|
||||
l.WithField("fieldname", "fieldval").Info("log with field")
|
||||
|
||||
l.WithError(fmt.Errorf("fooerror")).Error("error")
|
||||
|
||||
t.Log(pretty.Sprint(outlet_arr))
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user