From 1adf114320a9fb290092dd529aa167cb9bc8acc6 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Wed, 25 Jan 2023 17:01:37 +0100 Subject: [PATCH] 1. Moved some common level name methods/types from `native/level` to `level`. 2. Introduced a `LoggerFacade` interface with some more utility methods. --- level/level.go | 20 ++++++---- level/names.go | 41 +++++++++++++++++++ level/names_test.go | 67 +++++++++++++++++++++++++++++++ logger.go | 23 +++++++++-- logger_impl.go | 14 +++++-- native/facade/value/level.go | 4 +- native/formatter/json.go | 4 +- native/formatter/json_test.go | 4 +- native/formatter/level.go | 2 +- native/formatter/template.go | 2 +- native/formatter/text.go | 2 +- native/level/names.go | 75 +++++++++++++---------------------- native/level/names_test.go | 43 +------------------- 13 files changed, 189 insertions(+), 112 deletions(-) create mode 100644 level/names.go create mode 100644 level/names_test.go diff --git a/level/level.go b/level/level.go index ec0cee3..96e7be7 100644 --- a/level/level.go +++ b/level/level.go @@ -2,7 +2,7 @@ // important is the event. Trace is the less severe and Fatal the // most severe. // -// Customization +// # Customization // // Different implementations of Provider could introduce more Levels. All // ordinals are unique over all instances of Level. Info = 3000 will be @@ -11,6 +11,8 @@ // instances of Level. Standard levels always remains available. package level +import "errors" + const ( // Trace defines the lowest possible level. This is usually only used // in cases where really detailed logs are required. For example to document @@ -19,7 +21,7 @@ const ( Trace Level = 1000 // Debug is used to document information which is used to debug - // problems. These information are in regular operation mode are not + // problems. This information are in regular operation mode are not // required; but could help once enabled to track down common issues. Debug Level = 2000 @@ -30,13 +32,13 @@ const ( Info Level = 3000 // Warn implies that something happened which failed but could be - // recovered gracefully. In best case the user does not noticed anything. + // recovered gracefully. In best case the user does not notice anything. // But personal should investigate as soon as possible to prevent something // like this happening again. Warn Level = 4000 // Error implies that something happened which failed and cannot be - // recovered gracefully but it only affects one or a small amount of users + // recovered gracefully, but it only affects one or a small amount of users // and the rest of the system can continue to work. Personal should // investigate right now to prevent such stuff happening again and recover // broken users. @@ -44,7 +46,7 @@ const ( // Fatal implies that something happened which failed and cannot be // recovered gracefully, which might affect every user of the system. This - // implies that the whole system is not longer operable and should/will be + // implies that the whole system is no longer operable and should/will be // shutdown (if possible gracefully) right now. Personal is required to // investigate right now to prevent such stuff happening again, bring the // the system back to operations and recover broken users. @@ -60,9 +62,9 @@ func (instance Level) CompareTo(o Level) int { return int(instance) - int(o) } -// Levels represents a slice of 0..n Level. +// Levels represents a slice of 0...n Level. // -// Additionally it implements the sort.Interface to enable to call sort.Sort +// Additionally, it implements the sort.Interface to enable to call sort.Sort // to order the contents of this slice by its severity. type Levels []Level @@ -99,3 +101,7 @@ func (instance levelsAsProvider) GetName() string { func (instance levelsAsProvider) GetLevels() Levels { return instance.values } + +// ErrIllegalLevel represents that an illegal level.Level value/name was +// provided. +var ErrIllegalLevel = errors.New("illegal level") diff --git a/level/names.go b/level/names.go new file mode 100644 index 0000000..40ba081 --- /dev/null +++ b/level/names.go @@ -0,0 +1,41 @@ +package level + +// Names is used to make readable names out of level.Level or the other way +// around. +type Names interface { + // ToName converts a given level.Level to a human-readable name. If this + // level is unknown by this instance an error is returned. Most likely + // ErrIllegalLevel. + ToName(Level) (string, error) + + // ToLevel converts a given human-readable name to a level.Level. If this + // name is unknown by this instance an error is returned. Most likely + // ErrIllegalLevel. + ToLevel(string) (Level, error) +} + +// NamesAware represents an object that is aware of Names. +type NamesAware interface { + // GetLevelNames returns an instance of level.Names that support by + // formatting levels in a human-readable format. + GetLevelNames() Names +} + +// NewNamesFacade creates a facade of Names using the given provider. +func NewNamesFacade(provider func() Names) Names { + return namesFacade(provider) +} + +type namesFacade func() Names + +func (instance namesFacade) ToName(lvl Level) (string, error) { + return instance.Unwrap().ToName(lvl) +} + +func (instance namesFacade) ToLevel(name string) (Level, error) { + return instance.Unwrap().ToLevel(name) +} + +func (instance namesFacade) Unwrap() Names { + return instance() +} diff --git a/level/names_test.go b/level/names_test.go new file mode 100644 index 0000000..bb4f68a --- /dev/null +++ b/level/names_test.go @@ -0,0 +1,67 @@ +package level + +import ( + "errors" + "testing" + + "github.com/echocat/slf4g/internal/test/assert" +) + +func Test_NewNamesFacade(t *testing.T) { + givenNames := &mockNames{} + + actual := NewNamesFacade(func() Names { + return givenNames + }) + + assert.ToBeSame(t, givenNames, actual.(namesFacade)()) +} + +func Test_namesFacade_ToName(t *testing.T) { + givenLevel := Warn + givenError := errors.New("foo") + givenNames := &mockNames{onToName: func(actualLevel Level) (string, error) { + assert.ToBeEqual(t, givenLevel, actualLevel) + return "bar", givenError + }} + instance := namesFacade(func() Names { return givenNames }) + + actual, actualErr := instance.ToName(givenLevel) + + assert.ToBeEqual(t, "bar", actual) + assert.ToBeSame(t, givenError, actualErr) +} + +func Test_namesFacade_ToLevel(t *testing.T) { + givenLevel := Warn + givenError := errors.New("foo") + givenNames := &mockNames{onToLevel: func(actualName string) (Level, error) { + assert.ToBeEqual(t, "bar", actualName) + return givenLevel, givenError + }} + instance := namesFacade(func() Names { return givenNames }) + + actual, actualErr := instance.ToLevel("bar") + + assert.ToBeEqual(t, givenLevel, actual) + assert.ToBeSame(t, givenError, actualErr) +} + +type mockNames struct { + onToName func(lvl Level) (string, error) + onToLevel func(name string) (Level, error) +} + +func (instance *mockNames) ToName(l Level) (string, error) { + if v := instance.onToName; v != nil { + return v(l) + } + panic("not implemented") +} + +func (instance *mockNames) ToLevel(s string) (Level, error) { + if v := instance.onToLevel; v != nil { + return v(s) + } + panic("not implemented") +} diff --git a/logger.go b/logger.go index 18e2b4e..f05eedb 100644 --- a/logger.go +++ b/logger.go @@ -1,12 +1,15 @@ package log -import "github.com/echocat/slf4g/fields" +import ( + "github.com/echocat/slf4g/fields" + "github.com/echocat/slf4g/level" +) // Logger defines an instance which executes log event actions. // -// Implementation hints +// # Implementation hints // -// If you considering to implement slf4g you're usually not required to +// If you're considering to implement slf4g you're usually not required to // implement a full instance of Logger. Usually you just need to implement // CoreLogger and call NewLogger(with the CoreLogger) to create a full // implemented instance of a Logger. @@ -123,11 +126,23 @@ func NewLogger(cl CoreLogger) Logger { return NewLoggerFacade(func() CoreLogger { return cl }) } +// LoggerFacade is a fully implemented logger with utility methods for easier +// implementation of delegates. +type LoggerFacade interface { + Logger + + // DoLog is acting as a simple log for the given level. + DoLog(level level.Level, skipFrames uint16, args ...interface{}) + + // DoLogf is acting as a formatted log for the given level. + DoLogf(level level.Level, skipFrames uint16, format string, args ...interface{}) +} + // NewLoggerFacade is like NewLogger but takes a provider function that can // potentially return every time another instance of a CoreLogger. This is // useful especially in cases where you want to deal with concurrency while // creation of objects that need to hold a reference to a Logger. -func NewLoggerFacade(provider func() CoreLogger) Logger { +func NewLoggerFacade(provider func() CoreLogger) LoggerFacade { return &loggerImpl{ coreProvider: provider, fields: fields.Empty(), diff --git a/logger_impl.go b/logger_impl.go index 1b16374..fbfab5e 100644 --- a/logger_impl.go +++ b/logger_impl.go @@ -59,6 +59,14 @@ func (instance *loggerImpl) GetProvider() Provider { } func (instance *loggerImpl) log(level level.Level, args ...interface{}) { + instance.DoLog(level, 2, args...) +} + +func (instance *loggerImpl) logf(level level.Level, format string, args ...interface{}) { + instance.DoLogf(level, 2, format, args...) +} + +func (instance *loggerImpl) DoLog(level level.Level, skipFrames uint16, args ...interface{}) { if !instance.IsLevelEnabled(level) { return } @@ -71,10 +79,10 @@ func (instance *loggerImpl) log(level level.Level, args ...interface{}) { e = e.With(provider.GetFieldKeysSpec().GetMessage(), args) } - instance.Unwrap().Log(e, 2) + instance.Unwrap().Log(e, skipFrames+1) } -func (instance *loggerImpl) logf(level level.Level, format string, args ...interface{}) { +func (instance *loggerImpl) DoLogf(level level.Level, skipFrames uint16, format string, args ...interface{}) { if !instance.IsLevelEnabled(level) { return } @@ -82,7 +90,7 @@ func (instance *loggerImpl) logf(level level.Level, format string, args ...inter e := instance.NewEventWithFields(level, instance.fields). Withf(provider.GetFieldKeysSpec().GetMessage(), format, args...) - instance.Unwrap().Log(e, 2) + instance.Unwrap().Log(e, skipFrames+1) } func (instance *loggerImpl) Trace(args ...interface{}) { diff --git a/native/facade/value/level.go b/native/facade/value/level.go index a466472..996030a 100644 --- a/native/facade/value/level.go +++ b/native/facade/value/level.go @@ -86,11 +86,11 @@ func (instance Level) UnmarshalText(text []byte) error { return instance.Set(string(text)) } -func (instance Level) getNames() nlevel.Names { +func (instance Level) getNames() level.Names { if v := instance.Names; v != nil { return v } - if va, ok := instance.Target.(nlevel.NamesAware); ok { + if va, ok := instance.Target.(level.NamesAware); ok { if v := va.GetLevelNames(); v != nil { return v } diff --git a/native/formatter/json.go b/native/formatter/json.go index 6f754b6..2deee25 100644 --- a/native/formatter/json.go +++ b/native/formatter/json.go @@ -2,6 +2,7 @@ package formatter import ( "fmt" + "github.com/echocat/slf4g/level" "github.com/echocat/slf4g/native/execution" "github.com/echocat/slf4g/native/formatter/encoding" @@ -9,7 +10,6 @@ import ( log "github.com/echocat/slf4g" "github.com/echocat/slf4g/fields" "github.com/echocat/slf4g/native/hints" - nlevel "github.com/echocat/slf4g/native/level" ) const ( @@ -97,7 +97,7 @@ func (instance *Json) getLevelFormatter(using log.Provider) Level { if v := instance.LevelFormatter; v != nil { return v } - if v, ok := using.(nlevel.NamesAware); ok { + if v, ok := using.(level.NamesAware); ok { return NewNamesBasedLevel(v.GetLevelNames()) } return DefaultLevel diff --git a/native/formatter/json_test.go b/native/formatter/json_test.go index 64c4c83..806a326 100644 --- a/native/formatter/json_test.go +++ b/native/formatter/json_test.go @@ -366,10 +366,10 @@ func Test_Json_encodeValuesChecked(t *testing.T) { type someProvider struct { log.Provider - names nlevel.Names + names level.Names } -func (instance *someProvider) GetLevelNames() nlevel.Names { +func (instance *someProvider) GetLevelNames() level.Names { return instance } diff --git a/native/formatter/level.go b/native/formatter/level.go index efa0839..37fa135 100644 --- a/native/formatter/level.go +++ b/native/formatter/level.go @@ -8,7 +8,7 @@ import ( // DefaultLevel is the default instance of Level which should cover the most of // the cases. -var DefaultLevel Level = NewNamesBasedLevel(nlevel.NewNamesFacade(func() nlevel.Names { +var DefaultLevel Level = NewNamesBasedLevel(level.NewNamesFacade(func() level.Names { return nlevel.DefaultNames })) diff --git a/native/formatter/template.go b/native/formatter/template.go index ae9614e..afb9bdf 100644 --- a/native/formatter/template.go +++ b/native/formatter/template.go @@ -159,7 +159,7 @@ func (instance TemplateRenderingContext) Hints() hints.Hints { // LevelNames is a convenience method to easy return the current nlevel.Names // of the corresponding log.Provider. func (instance TemplateRenderingContext) LevelNames() nlevel.Names { - if v, ok := instance.Provider.(nlevel.NamesAware); ok { + if v, ok := instance.Provider.(level.NamesAware); ok { if names := v.GetLevelNames(); names != nil { return names } diff --git a/native/formatter/text.go b/native/formatter/text.go index 0760e24..b5f1c6c 100644 --- a/native/formatter/text.go +++ b/native/formatter/text.go @@ -306,7 +306,7 @@ func (instance *Text) formatLevelChecked(l level.Level, using log.Provider, to * } func (instance *Text) getLevelNames(using log.Provider) nlevel.Names { - if v, ok := using.(nlevel.NamesAware); ok { + if v, ok := using.(level.NamesAware); ok { return v.GetLevelNames() } if v := nlevel.DefaultNames; v != nil { diff --git a/native/level/names.go b/native/level/names.go index 6bd3de3..b11ebc2 100644 --- a/native/level/names.go +++ b/native/level/names.go @@ -1,7 +1,6 @@ package level import ( - "errors" "fmt" "strconv" "strings" @@ -9,59 +8,15 @@ import ( "github.com/echocat/slf4g/level" ) -// ErrIllegalLevel represents that an illegal level.Level value/name was -// provided. -var ErrIllegalLevel = errors.New("illegal level") - // DefaultNames is the default instance of Names which should cover the most of // the cases. var DefaultNames = NewNames() -// Names is used to make readable names out of level.Level or the other way -// around. -type Names interface { - // ToName converts a given level.Level to a human readable name. If this - // level is unknown by this instance an error is returned. Most likely - // ErrIllegalLevel. - ToName(level.Level) (string, error) - - // ToLevel converts a given human readable name to a level.Level. If this - // name is unknown by this instance an error is returned. Most likely - // ErrIllegalLevel. - ToLevel(string) (level.Level, error) -} - // NewNames creates a new default instance of a Names implementation. -func NewNames() Names { +func NewNames() level.Names { return &defaultNames{} } -// NamesAware represents an object that is aware of Names. -type NamesAware interface { - // GetLevelNames returns an instance of level.Names that support by - // formatting levels in a human readable format. - GetLevelNames() Names -} - -// NewNamesFacade creates a facade of Names using the given provider. -func NewNamesFacade(provider func() Names) Names { - return namesFacade(provider) -} - -type namesFacade func() Names - -func (instance namesFacade) ToName(lvl level.Level) (string, error) { - return instance.Unwrap().ToName(lvl) -} - -func (instance namesFacade) ToLevel(name string) (level.Level, error) { - return instance.Unwrap().ToLevel(name) -} - -func (instance namesFacade) Unwrap() Names { - return instance() -} - type defaultNames struct{} func (instance *defaultNames) ToName(lvl level.Level) (string, error) { @@ -99,9 +54,35 @@ func (instance *defaultNames) ToLevel(name string) (level.Level, error) { return level.Fatal, nil default: if result, err := strconv.ParseUint(name, 10, 16); err != nil { - return 0, fmt.Errorf("%w: %s", ErrIllegalLevel, name) + return 0, fmt.Errorf("%w: %s", level.ErrIllegalLevel, name) } else { return level.Level(result), nil } } } + +// ErrIllegalLevel represents that an illegal level.Level value/name was +// provided. +// +// Deprecated: use level.ErrIllegalLevel instead. +var ErrIllegalLevel = level.ErrIllegalLevel + +// Names is used to make readable names out of level.Level or the other way +// around. +// +// Deprecated: use level.Names instead. +type Names level.Names + +// NamesAware represents an object that is aware of Names. +// +// Deprecated: use level.NamesAware instead. +type NamesAware level.NamesAware + +// NewNamesFacade creates a facade of Names using the given provider. +// +// Deprecated: use level.NewNamesFacade instead. +func NewNamesFacade(provider func() Names) Names { + return level.NewNamesFacade(func() level.Names { + return provider() + }) +} diff --git a/native/level/names_test.go b/native/level/names_test.go index 23435b9..f2796e0 100644 --- a/native/level/names_test.go +++ b/native/level/names_test.go @@ -1,7 +1,6 @@ package level import ( - "errors" "fmt" "testing" @@ -16,46 +15,6 @@ func Test_NewNames(t *testing.T) { assert.ToBeEqual(t, &defaultNames{}, actual) } -func Test_NewNamesFacade(t *testing.T) { - givenNames := &defaultNames{} - - actual := NewNamesFacade(func() Names { - return givenNames - }) - - assert.ToBeSame(t, givenNames, actual.(namesFacade)()) -} - -func Test_namesFacade_ToName(t *testing.T) { - givenLevel := level.Warn - givenError := errors.New("foo") - givenNames := &mockNames{onToName: func(actualLevel level.Level) (string, error) { - assert.ToBeEqual(t, givenLevel, actualLevel) - return "bar", givenError - }} - instance := namesFacade(func() Names { return givenNames }) - - actual, actualErr := instance.ToName(givenLevel) - - assert.ToBeEqual(t, "bar", actual) - assert.ToBeSame(t, givenError, actualErr) -} - -func Test_namesFacade_ToLevel(t *testing.T) { - givenLevel := level.Warn - givenError := errors.New("foo") - givenNames := &mockNames{onToLevel: func(actualName string) (level.Level, error) { - assert.ToBeEqual(t, "bar", actualName) - return givenLevel, givenError - }} - instance := namesFacade(func() Names { return givenNames }) - - actual, actualErr := instance.ToLevel("bar") - - assert.ToBeEqual(t, givenLevel, actual) - assert.ToBeSame(t, givenError, actualErr) -} - func Test_defaultNames_ToName(t *testing.T) { instance := &defaultNames{} @@ -113,7 +72,7 @@ func Test_defaultNames_ToLevel_withNonNumber(t *testing.T) { actual, actualErr := instance.ToLevel("abc") - assert.ToBeEqual(t, fmt.Errorf("%w: abc", ErrIllegalLevel), actualErr) + assert.ToBeEqual(t, fmt.Errorf("%w: abc", level.ErrIllegalLevel), actualErr) assert.ToBeEqual(t, level.Level(0), actual) }