Quick experience
The following is the usage method of logger after replacing zap with slog in the project. It is the same as before replacing, without any perception.
package main import "/webws/go-moda/logger" func main() { // Format printing {"time":"2023-09-08T01:25:21.313463+08:00","level":"INFO","msg":"info hello slog","key":"value","file":"/Users/xxx/w/pro/go-moda/example/logger/","line":6} ("info hello slog", "key", "value") // Print json ("debug hello slog", "key", "value") // Not displayed () // Set level ("debug hello slog", "key", "value") // Show debug after setting the level // with newLog := ("newkey", "newValue") ("new hello slog") // Will print newkey:newValue ("old hello slog") // Will not print newkey:newValue}
Basic use of slog
In Go version 1.21, /x/exp/slog was introduced into the go standard library. The path is log/slog.
If you do not use third-party packages, you can use slog as your logger directly
slog simple example
The default output level is info or above, so debug cannot be printed.
import "log/slog" func main() { ("finished", "key", "value") ("finished", "key", "value") }
Output
2023/09/08 00:27:24 INFO finished key=value
slog formatting
HandlerOptions Level: Set log level AddSource: Print file-related information
func main() { opts := &{AddSource: true, Level: } logger := ((, opts)) ("finished", "key", "value") }
Output
{"time":"2023-09-08T00:34:22.035962+08:00","level":"INFO","source":{"function":"callvis/","file":"/Users/websong/w/pro/go-note/slog/main_test.go","line":39},"msg":"finished","key":"value"}
slog switch log level
Look at the slog source code. The Level of HandlerOptions is an interface. Slog comes with this interface. You can also define it yourself. The following are some source codes.
type Leveler interface { Level() Level } type LevelVar struct { val atomic.Int64 } // Level returns v's level. func (v *LevelVar) Level() Level { return Level(int(())) } // Set sets v's level to l. func (v *LevelVar) Set(l Level) { (int64(l)) }
After setting the debug level, the second debug log can be printed out
func main() { levelVar := &{} () opts := &{AddSource: true, Level: levelVar} logger := ((, opts)) ("finished", "key", "value") () ("finished", "key", "value") }
To implement the beginning of the article, you can choose to encapsulate the same structure as the article, for example
type SlogLogger struct { logger * level * }
The following slog replaces zap with detailed code
Original logger zap implementation
The original project has implemented a set of loggers, and the following codes are all under the logger package
Original zap code
logger interface LoggerInterface
package logger type LoggerInterface interface { Debugw(msg string, keysAndValues ...interface{}) Infow(msg string, keysAndValues ...interface{}) Errorw(msg string, keysAndValues ...interface{}) Fatalw(msg string, keysAndValues ...interface{}) SetLevel(level Level) With(keyValues ...interface{}) LoggerInterface }
zap log implementation LoggerInterface
type ZapSugaredLogger struct { logger * zapConfig * } func buildZapLog(level Level) LoggerInterface { encoderConfig := { TimeKey: "ts", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: , EncodeDuration: , EncodeTime: zapcore.ISO8601TimeEncoder, EncodeLevel: , EncodeCaller: , } zapConfig := &{ Level: ((level)), Development: true, DisableCaller: false, DisableStacktrace: true, Sampling: &{Initial: 100, Thereafter: 100}, Encoding: "json", EncoderConfig: encoderConfig, OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } l, err := ((2)) if err != nil { ("zap build logger fail err=%v", err) return nil } return &ZapSugaredLogger{ logger: (), zapConfig: zapConfig, } func (l *ZapSugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { (msg, keysAndValues...) } func (l *ZapSugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { (msg, keysAndValues...) } // ...Omit other methods to implement interfaces such as info}
Global initialization logger, because the code volume is too large, the following is pseudo-code, which mainly provides ideas
package logger // Global log, you can also get new instances with NewLogger alonevar globalog = newlogger(DebugLevel) func newlogger(level Level) *Logger { l := &Logger{logger: buildZapLog(level)} return l } func Infow(msg string, keysAndValues ...interface{}) { (msg, keysAndValues...) } // ...Omit other global methods,for exampleDebugW What's more
In the project, use logger as follows
import "/webws/go-moda/logger" func main() { ("hello", "key", "value") // Print json}
slog does not intrude into business Replace zap
The logger interface remains unchanged
slog implementation code
package logger import ( "log/slog" "os" "runtime" ) var _ LoggerInterface = (*SlogLogger)(nil) type SlogLogger struct { logger * level * // true means to use slog to print the file path, false will use a custom method to add fields to the log file line addSource bool } // newSlog func newSlog(level Level, addSource bool) LoggerInterface { levelVar := &{} () opts := &{AddSource: addSource, Level: levelVar} logger := ((, opts)) return &SlogLogger{ logger: logger, level: levelVar, } } func (l *SlogLogger) Fatalw(msg string, keysAndValues ...interface{}) { keysAndValues = (keysAndValues...) (msg, keysAndValues...) (1) } func (l *SlogLogger) Infow(msg string, keysAndValues ...interface{}) { keysAndValues = (keysAndValues...) (msg, keysAndValues...) } //Omit other methods of inheriting interfaces such as DebugWfunc (l *SlogLogger) SetLevel(level Level) { zapLevelToSlogLevel(level) ((zapLevelToSlogLevel(level))) } // func (l *SlogLogger) With(keyValues ...interface{}) LoggerInterface { newLog := (keyValues...) return &SlogLogger{ logger: newLog, level: , } } // ApppendFileLine Gets the caller's file and file number// Slog is native and does not support callerSkip. Using this function to chew on the root will cause performance problems. It is best to wait for slog to provide the parameters of CallerSkipfunc (l *SlogLogger) ApppendFileLine(keyValues ...interface{}) []interface{} { = false if ! { var pc uintptr var pcs [1]uintptr // skip [, this function, this function's caller] (4, pcs[:]) pc = pcs[0] fs := ([]uintptr{pc}) f, _ := () keyValues = append(keyValues, "file", , "line", ) return keyValues } return keyValues }
Global initialization logger, the following pseudo-code
package logger // Global log, you can also get new instances with NewLogger alonevar globalog = newlogger(DebugLevel) func newlogger(level Level) *Logger { l := &Logger{logger: newSlog(level, false)} return l } func Infow(msg string, keysAndValues ...interface{}) { (msg, keysAndValues...) } // ...Omit other global methods,for exampleDebugW What's more
You can also use logger as follows, just like when using zap
import "/webws/go-moda/logger" func main() { ("hello", "key", "value") // Print json}
slog implements callerSkip function
The addsource parameter of slog will print the file name and line number, but it cannot support callerSkip like zap. That is to say, if slog is encapsulated under a file in the logger directory and printed with logger, the displayed file will be just
I read the source code of slog and used the callerSkip function internally, but did not expose the callerSkip parameters to the outside world.
You can see my code above and encapsulate a method myself: AppendFileLine, use AppendFileLine to get the file name and line number, and add the key value of file and line to the log
There may be performance problems, I hope slog can provide a callerSkip parameter to the outside world
illustrate
There are not many codes posted in the article, mainly providing ideas, although some methods and global logger implementation methods are omitted
To view logger implementation details, you can view the package quoted at the beginning of the article Quick experience
You can also check out my warehouse directlygo-modaUse slog and zap encapsulation
The above is the detailed explanation of go logger's business code that does not invade business code and uses slog to replace zap and implement callerSkip. For more information about go logger slog to replace zap, please follow my other related articles!