move implementation to internal/ directory (#828)

This commit is contained in:
Christian Schwarz
2024-10-18 19:21:17 +02:00
committed by GitHub
parent b9b9ad10cf
commit 908807bd59
360 changed files with 507 additions and 507 deletions
+186
View File
@@ -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 }
+184
View File
@@ -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...))
}
+48
View File
@@ -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))
}
+21
View File
@@ -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{}) {}
+23
View File
@@ -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),
}
}
+28
View File
@@ -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),
}
}