move implementation to internal/ directory (#828)
This commit is contained in:
committed by
GitHub
parent
b9b9ad10cf
commit
908807bd59
@@ -0,0 +1,186 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
func (l Level) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(l.String())
|
||||
}
|
||||
|
||||
func (l *Level) UnmarshalJSON(input []byte) (err error) {
|
||||
var s string
|
||||
if err = json.Unmarshal(input, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
*l, err = ParseLevel(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// implement flag.Value
|
||||
// implement github.com/spf13/pflag.Value
|
||||
func (l *Level) Set(s string) error {
|
||||
newl, err := ParseLevel(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*l = newl
|
||||
return nil
|
||||
}
|
||||
|
||||
// implement github.com/spf13/pflag.Value
|
||||
func (l *Level) Type() string {
|
||||
var buf bytes.Buffer
|
||||
for i, l := range AllLevels {
|
||||
fmt.Fprintf(&buf, "%s", l)
|
||||
if i != len(AllLevels)-1 {
|
||||
fmt.Fprintf(&buf, "|")
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("(%s)", buf.String())
|
||||
}
|
||||
|
||||
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 l.String()
|
||||
}
|
||||
}
|
||||
|
||||
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("unknown level %d", 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
|
||||
}
|
||||
|
||||
func (e Entry) Color() *color.Color {
|
||||
c := color.New()
|
||||
switch e.Level {
|
||||
case Debug:
|
||||
c.Add(color.FgHiBlue)
|
||||
case Info:
|
||||
c.Add(color.FgHiGreen)
|
||||
case Warn:
|
||||
c.Add(color.FgHiYellow)
|
||||
case Error:
|
||||
c.Add(color.FgHiRed)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// An outlet receives log entries produced by the Logger and writes them to some destination.
|
||||
type Outlet interface {
|
||||
// Write the entry to the destination.
|
||||
//
|
||||
// Logger waits for all outlets to return from WriteEntry() before returning from the log call.
|
||||
// An implementation of Outlet must assert that it does not block in WriteEntry.
|
||||
// Otherwise, it will slow down the program.
|
||||
//
|
||||
// Note: os.Stderr is also used by logger.Logger for reporting errors returned by outlets
|
||||
// => you probably don't want to log there
|
||||
WriteEntry(entry Entry) error
|
||||
}
|
||||
|
||||
type Outlets struct {
|
||||
mtx sync.RWMutex
|
||||
outs map[Level][]Outlet
|
||||
}
|
||||
|
||||
func NewOutlets() *Outlets {
|
||||
return &Outlets{
|
||||
mtx: sync.RWMutex{},
|
||||
outs: make(map[Level][]Outlet, len(AllLevels)),
|
||||
}
|
||||
}
|
||||
|
||||
func (os *Outlets) DeepCopy() (copy *Outlets) {
|
||||
copy = NewOutlets()
|
||||
for level := range os.outs {
|
||||
copy.outs[level] = append(copy.outs[level], os.outs[level]...)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
func (os *Outlets) Add(outlet Outlet, minLevel Level) {
|
||||
os.mtx.Lock()
|
||||
defer os.mtx.Unlock()
|
||||
for _, l := range AllLevels[minLevel:] {
|
||||
os.outs[l] = append(os.outs[l], outlet)
|
||||
}
|
||||
}
|
||||
|
||||
func (os *Outlets) Get(level Level) []Outlet {
|
||||
os.mtx.RLock()
|
||||
defer os.mtx.RUnlock()
|
||||
return os.outs[level]
|
||||
}
|
||||
|
||||
// Return the first outlet added to this Outlets list using Add()
|
||||
// with minLevel <= Error.
|
||||
// If no such outlet is in this Outlets list, a discarding outlet is returned.
|
||||
func (os *Outlets) GetLoggerErrorOutlet() Outlet {
|
||||
os.mtx.RLock()
|
||||
defer os.mtx.RUnlock()
|
||||
if len(os.outs[Error]) < 1 {
|
||||
return nullOutlet{}
|
||||
}
|
||||
return os.outs[Error][0]
|
||||
}
|
||||
|
||||
type nullOutlet struct{}
|
||||
|
||||
func (nullOutlet) WriteEntry(entry Entry) error { return nil }
|
||||
@@ -0,0 +1,184 @@
|
||||
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...))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package logger_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kr/pretty"
|
||||
|
||||
"github.com/zrepl/zrepl/internal/logger"
|
||||
)
|
||||
|
||||
type TestOutlet struct {
|
||||
Record []logger.Entry
|
||||
}
|
||||
|
||||
func (o *TestOutlet) WriteEntry(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))
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package logger
|
||||
|
||||
type nullLogger struct{}
|
||||
|
||||
var _ Logger = nullLogger{}
|
||||
|
||||
func NewNullLogger() Logger {
|
||||
return nullLogger{}
|
||||
}
|
||||
|
||||
func (n nullLogger) WithOutlet(outlet Outlet, level Level) Logger { return n }
|
||||
func (n nullLogger) ReplaceField(field string, val interface{}) Logger { return n }
|
||||
func (n nullLogger) WithField(field string, val interface{}) Logger { return n }
|
||||
func (n nullLogger) WithFields(fields Fields) Logger { return n }
|
||||
func (n nullLogger) WithError(err error) Logger { return n }
|
||||
func (nullLogger) Log(level Level, msg string) {}
|
||||
func (nullLogger) Debug(msg string) {}
|
||||
func (nullLogger) Info(msg string) {}
|
||||
func (nullLogger) Warn(msg string) {}
|
||||
func (nullLogger) Error(msg string) {}
|
||||
func (nullLogger) Printf(format string, args ...interface{}) {}
|
||||
@@ -0,0 +1,23 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type stderrLoggerOutlet struct{}
|
||||
|
||||
func (stderrLoggerOutlet) WriteEntry(entry Entry) error {
|
||||
fmt.Fprintf(os.Stderr, "%#v\n", entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Logger = testLogger{}
|
||||
|
||||
func NewStderrDebugLogger() Logger {
|
||||
outlets := NewOutlets()
|
||||
outlets.Add(&stderrLoggerOutlet{}, Debug)
|
||||
return &testLogger{
|
||||
Logger: NewLogger(outlets, 0),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testLogger struct {
|
||||
Logger
|
||||
}
|
||||
|
||||
type testingLoggerOutlet struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (o testingLoggerOutlet) WriteEntry(entry Entry) error {
|
||||
o.t.Logf("%#v", entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Logger = testLogger{}
|
||||
|
||||
func NewTestLogger(t *testing.T) Logger {
|
||||
outlets := NewOutlets()
|
||||
outlets.Add(&testingLoggerOutlet{t}, Debug)
|
||||
return &testLogger{
|
||||
Logger: NewLogger(outlets, 0),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user