Merge pull request #67 from anytypeio/logger-order
Logger level determine order
This commit is contained in:
commit
b4b1e0b340
@ -1,6 +1,9 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
@ -15,14 +18,19 @@ const (
|
|||||||
JSONOutput
|
JSONOutput
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NamedLevel struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Production bool `yaml:"production"`
|
Production bool `yaml:"production"`
|
||||||
DefaultLevel string `yaml:"defaultLevel"`
|
DefaultLevel string `yaml:"defaultLevel"`
|
||||||
NamedLevels map[string]string `yaml:"namedLevels"`
|
Levels []NamedLevel `yaml:"levels"` // first match will be used
|
||||||
AddOutputPaths []string `yaml:"outputPaths"`
|
AddOutputPaths []string `yaml:"outputPaths"`
|
||||||
DisableStdErr bool `yaml:"disableStdErr"`
|
DisableStdErr bool `yaml:"disableStdErr"`
|
||||||
Format LogFormat `yaml:"format"`
|
Format LogFormat `yaml:"format"`
|
||||||
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
|
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Config) ApplyGlobal() {
|
func (l Config) ApplyGlobal() {
|
||||||
@ -68,20 +76,49 @@ func (l Config) ApplyGlobal() {
|
|||||||
conf.Level = defaultLevel
|
conf.Level = defaultLevel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var lvl = make(map[string]zap.AtomicLevel)
|
for _, v := range l.Levels {
|
||||||
for k, v := range l.NamedLevels {
|
if lev, err := zap.ParseAtomicLevel(v.Level); err == nil {
|
||||||
if lev, err := zap.ParseAtomicLevel(v); err == nil {
|
|
||||||
lvl[k] = lev
|
|
||||||
// we need to have a minimum level of all named loggers for the main logger
|
// we need to have a minimum level of all named loggers for the main logger
|
||||||
if lev.Level() < conf.Level.Level() {
|
if lev.Level() < conf.Level.Level() {
|
||||||
conf.Level.SetLevel(lev.Level())
|
conf.Level.SetLevel(lev.Level())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lg, err := conf.Build()
|
lg, err := conf.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Default().Fatal("can't build logger", zap.Error(err))
|
Default().Fatal("can't build logger", zap.Error(err))
|
||||||
}
|
}
|
||||||
SetDefault(lg)
|
SetDefault(lg)
|
||||||
SetNamedLevels(lvl)
|
SetNamedLevels(l.Levels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LevelsFromStr parses a string of the form "name1=DEBUG;prefix*=WARN;*=ERROR" into a slice of NamedLevel
|
||||||
|
// it may be useful to parse the log level from the OS env var
|
||||||
|
func LevelsFromStr(s string) (levels []NamedLevel) {
|
||||||
|
for _, kv := range strings.Split(s, ";") {
|
||||||
|
strings.TrimSpace(kv)
|
||||||
|
parts := strings.Split(kv, "=")
|
||||||
|
var key, value string
|
||||||
|
if len(parts) == 1 {
|
||||||
|
key = "*"
|
||||||
|
value = parts[0]
|
||||||
|
_, err := zap.ParseAtomicLevel(value)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
levels = append(levels, NamedLevel{Name: key, Level: value})
|
||||||
|
} else if len(parts) == 2 {
|
||||||
|
key = parts[0]
|
||||||
|
value = parts[1]
|
||||||
|
}
|
||||||
|
_, err := zap.ParseAtomicLevel(value)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
levels = append(levels, NamedLevel{Name: key, Level: value})
|
||||||
|
}
|
||||||
|
return levels
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,17 @@ var (
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
loggerConfig zap.Config
|
loggerConfig zap.Config
|
||||||
namedLevels = make(map[string]zap.AtomicLevel)
|
namedLevels []namedLevel
|
||||||
namedGlobs = make(map[string]glob.Glob)
|
namedGlobs = make(map[string]glob.Glob)
|
||||||
namedLoggers = make(map[string]CtxLogger)
|
namedLoggers = make(map[string]CtxLogger)
|
||||||
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
|
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type namedLevel struct {
|
||||||
|
name string
|
||||||
|
level zap.AtomicLevel
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
loggerConfig = zap.NewDevelopmentConfig()
|
loggerConfig = zap.NewDevelopmentConfig()
|
||||||
logger, _ = loggerConfig.Build()
|
logger, _ = loggerConfig.Build()
|
||||||
@ -35,18 +40,23 @@ func SetDefault(l *zap.Logger) {
|
|||||||
// it also supports glob patterns for names, like "app*"
|
// it also supports glob patterns for names, like "app*"
|
||||||
// can be racy in case there are existing named loggers
|
// can be racy in case there are existing named loggers
|
||||||
// so consider to call only once at the beginning
|
// so consider to call only once at the beginning
|
||||||
func SetNamedLevels(l map[string]zap.AtomicLevel) {
|
func SetNamedLevels(nls []NamedLevel) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
namedLevels = l
|
namedLevels = namedLevels[:0]
|
||||||
|
|
||||||
var minLevel = logger.Level()
|
var minLevel = logger.Level()
|
||||||
for k, l := range namedLevels {
|
for _, nl := range nls {
|
||||||
g, err := glob.Compile(k)
|
l, err := zap.ParseAtomicLevel(nl.Level)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
namedGlobs[k] = g
|
continue
|
||||||
}
|
}
|
||||||
namedLevels[k] = l
|
namedLevels = append(namedLevels, namedLevel{name: nl.Name, level: l})
|
||||||
|
g, err := glob.Compile(nl.Name)
|
||||||
|
if err == nil {
|
||||||
|
namedGlobs[nl.Name] = g
|
||||||
|
}
|
||||||
|
|
||||||
if l.Level() < minLevel {
|
if l.Level() < minLevel {
|
||||||
minLevel = l.Level()
|
minLevel = l.Level()
|
||||||
}
|
}
|
||||||
@ -81,23 +91,18 @@ func Default() *zap.Logger {
|
|||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getLevel returns the level for the given name
|
||||||
|
// it return the first matching name or glob pattern whatever comes first
|
||||||
func getLevel(name string) zap.AtomicLevel {
|
func getLevel(name string) zap.AtomicLevel {
|
||||||
level, ok := namedLevels[name]
|
for _, nl := range namedLevels {
|
||||||
if !ok {
|
if nl.name == name {
|
||||||
var found bool
|
return nl.level
|
||||||
for globName, glob := range namedGlobs {
|
|
||||||
if glob.Match(name) {
|
|
||||||
found = true
|
|
||||||
level, _ = namedLevels[globName]
|
|
||||||
// no need to check ok, because we know that globName exists
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !found {
|
if g, ok := namedGlobs[nl.name]; ok && g.Match(name) {
|
||||||
level = loggerConfig.Level
|
return nl.level
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return level
|
return zap.NewAtomicLevelAt(logger.Level())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNamed(name string, fields ...zap.Field) CtxLogger {
|
func NewNamed(name string, fields ...zap.Field) CtxLogger {
|
||||||
|
|||||||
150
app/logger/log_test.go
Normal file
150
app/logger/log_test.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getLevel1(t *testing.T) {
|
||||||
|
SetNamedLevels([]NamedLevel{
|
||||||
|
{Name: "app", Level: "debug"},
|
||||||
|
{Name: "app*", Level: "info"},
|
||||||
|
{Name: "app.sub", Level: "warn"},
|
||||||
|
{Name: "*", Level: "fatal"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
want zap.AtomicLevel
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "app",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.DebugLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.aaa",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.sub",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getLevel2(t *testing.T) {
|
||||||
|
SetNamedLevels([]NamedLevel{
|
||||||
|
{Name: "*", Level: "ERROR"},
|
||||||
|
{Name: "app", Level: "info"},
|
||||||
|
{Name: "app.sub", Level: "warn"},
|
||||||
|
{Name: "*", Level: "fatal"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
want zap.AtomicLevel
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "app",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.aaa",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.sub",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getLevel3(t *testing.T) {
|
||||||
|
SetNamedLevels([]NamedLevel{
|
||||||
|
{Name: "app", Level: "info"},
|
||||||
|
{Name: "*.sub", Level: "warn"},
|
||||||
|
{Name: "*", Level: "fatal"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
want zap.AtomicLevel
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "app",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.sub",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.WarnLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "random",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getLevel4(t *testing.T) {
|
||||||
|
SetNamedLevels([]NamedLevel{
|
||||||
|
{Name: "*", Level: "invalid"},
|
||||||
|
{Name: "app", Level: "info"},
|
||||||
|
{Name: "b", Level: "invalid"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
want zap.AtomicLevel
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "app",
|
||||||
|
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "app.sub",
|
||||||
|
want: zap.NewAtomicLevelAt(logger.Level()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "b",
|
||||||
|
want: zap.NewAtomicLevelAt(logger.Level()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user