diff --git a/next/logger/encoder.go b/next/logger/encoder.go index f7e69e4..b9eda34 100644 --- a/next/logger/encoder.go +++ b/next/logger/encoder.go @@ -1,36 +1,77 @@ package logger import ( + "bytes" "fmt" "github.com/gookit/color" "go.uber.org/zap/zapcore" + "path/filepath" + "runtime" + "strconv" + "strings" "time" ) +// getGid get goroutine ID only for debugging. +// -> https://blog.sgmansfield.com/2015/12/goroutine-ids/ +func getGid() uint64 { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + n, _ := strconv.ParseUint(string(b), 10, 64) + return n +} + +// getCaller calculate relative source path of caller. +func getCaller(ec zapcore.EntryCaller, verbose bool) string { + file, err := filepath.Rel(logHandle.path, ec.File) + if err != nil { + return "undefined" + } + if verbose { + return file + ":" + strconv.Itoa(ec.Line) + } + file, _ = strings.CutSuffix(file, ".go") // remove `.go` suffix + return file +} + // timeEncoder formats the time as a string. func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05.000")) } // timeColoredEncoder formats the time as a colored string -// with `[XProxy]` prefix. +// with custom prefix. func timeColoredEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(fmt.Sprintf( "%s %s", - color.Cyan.Render("[XProxy]"), + color.Cyan.Render(logHandle.prefix), // colored prefix color.Gray.Render(t.Format("2006-01-02 15:04:05.000")), )) } // callerEncoder formats caller in square brackets. -func callerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { - enc.AppendString("[" + caller.TrimmedPath() + "]") +func callerEncoder(ec zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { + if !logHandle.verbose { + enc.AppendString("[" + getCaller(ec, false) + "]") + return + } + enc.AppendString(fmt.Sprintf("[%d] [%s]", getGid(), getCaller(ec, true))) } -// callerColoredEncoder formats caller in square brackets -// with magenta color. -func callerColoredEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { - enc.AppendString(color.Magenta.Render("[" + caller.TrimmedPath() + "]")) +// callerColoredEncoder formats caller in square brackets with +// magenta color. +func callerColoredEncoder(ec zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { + if !logHandle.verbose { + enc.AppendString(color.Magenta.Render("[" + getCaller(ec, false) + "]")) + return + } + enc.AppendString(fmt.Sprintf( + "%s %s", + color.Blue.Render(fmt.Sprintf("[%d]", getGid())), + color.Magenta.Render("["+getCaller(ec, true)+"]"), + )) } // levelEncoder formats log level using square brackets. diff --git a/next/logger/interface.go b/next/logger/interface.go index 8e34adb..f58b811 100644 --- a/next/logger/interface.go +++ b/next/logger/interface.go @@ -13,35 +13,43 @@ const ( PanicLevel = zapcore.PanicLevel ) -func GetLevel() zapcore.Level { - return handle.level.Level() -} - -func SetLevel(level zapcore.Level) { - handle.level.SetLevel(level) -} - func Debugf(template string, args ...interface{}) { - handle.sugar.Debugf(template, args...) + logHandle.sugar.Debugf(template, args...) } func Infof(template string, args ...interface{}) { - handle.sugar.Infof(template, args...) + logHandle.sugar.Infof(template, args...) } func Warnf(template string, args ...interface{}) { - handle.sugar.Warnf(template, args...) + logHandle.sugar.Warnf(template, args...) } func Errorf(template string, args ...interface{}) { - handle.sugar.Errorf(template, args...) + logHandle.sugar.Errorf(template, args...) } func Panicf(template string, args ...interface{}) { - handle.sugar.Panicf(template, args...) + logHandle.sugar.Panicf(template, args...) +} + +// GetLevel return the current logger level. +func GetLevel() zapcore.Level { + return logHandle.level.Level() +} + +// SetLevel configure logger output level. Note that debug level +// will output more information and reduce performance. +func SetLevel(level zapcore.Level) { + logHandle.level.SetLevel(level) + if level == DebugLevel { + logHandle.verbose = true + } else { + logHandle.verbose = false + } } -// AddOutputs adds more plain log outputs. +// AddOutputs adds more plain output channel to the logger module. func AddOutputs(outputs ...io.Writer) { var writers []zapcore.WriteSyncer for _, output := range outputs { diff --git a/next/logger/logger.go b/next/logger/logger.go index 15b5fd3..8e792a5 100644 --- a/next/logger/logger.go +++ b/next/logger/logger.go @@ -4,17 +4,22 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" + "path" + "runtime" ) -type Logger struct { +type logger struct { logger *zap.Logger level *zap.AtomicLevel sugar *zap.SugaredLogger writers []zapcore.WriteSyncer - stdCore zapcore.Core + stderr zapcore.Core // fixed stderr output + prefix string // custom output prefix + path string // project absolute path + verbose bool // show goroutine id and caller line } -var handle Logger +var logHandle *logger // singleton logger handle // logConfig generates log config for XProxy. func logConfig(colored bool) zapcore.EncoderConfig { @@ -37,32 +42,37 @@ func logConfig(colored bool) zapcore.EncoderConfig { } func init() { - level := zap.NewAtomicLevelAt(DebugLevel) - core := zapcore.NewCore( - zapcore.NewConsoleEncoder(logConfig(true)), - zapcore.Lock(os.Stderr), level, + zapLevel := zap.NewAtomicLevelAt(InfoLevel) // using info level in default + zapCore := zapcore.NewCore( + zapcore.NewConsoleEncoder(logConfig(true)), // colorful output + zapcore.Lock(os.Stderr), + zapLevel, ) - logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) - handle = Logger{ - logger: logger, - level: &level, - sugar: logger.Sugar(), + zapLogger := zap.New(zapCore, zap.AddCaller(), zap.AddCallerSkip(1)) + _, src, _, _ := runtime.Caller(0) // absolute path of current code + logHandle = &logger{ + logger: zapLogger, + level: &zapLevel, + stderr: zapCore, + sugar: zapLogger.Sugar(), writers: []zapcore.WriteSyncer{}, - stdCore: core, + path: path.Join(path.Dir(src), "../"), + prefix: "[XProxy]", + verbose: false, } } // addWrites adds more plain log writers. func addWrites(writers ...zapcore.WriteSyncer) { - handle.writers = append(handle.writers, writers...) + logHandle.writers = append(logHandle.writers, writers...) plainCore := zapcore.NewCore( - zapcore.NewConsoleEncoder(logConfig(false)), - zap.CombineWriteSyncers(handle.writers...), - handle.level, + zapcore.NewConsoleEncoder(logConfig(false)), // without colored + zap.CombineWriteSyncers(logHandle.writers...), + logHandle.level, ) - handle.logger = zap.New( - zapcore.NewTee(handle.stdCore, plainCore), + logHandle.logger = zap.New( + zapcore.NewTee(logHandle.stderr, plainCore), zap.AddCaller(), zap.AddCallerSkip(1), ) - handle.sugar = handle.logger.Sugar() + logHandle.sugar = logHandle.logger.Sugar() }