Why do you need to log out the file?
- Currently, most developers when developing functions are mostly just logging to the console, then on environments like dev, pro, .. then using tools that will support the collector without concern at all.
- When you dev in systems that do not have applications that support collectors, you need to log out the file.
- I need to install 2 libraries: zap and lumberjack
Code
- Simple config logger:
1 2 3 4 5 6 7 8 9 | type ConfigLogger struct { Mode string `yaml:"mode" mapstructure:"mode"` DisableCaller bool `yaml:"disable_caller" mapstructure:"disable_caller"` DisableStacktrace bool `yaml:"disable_stacktrace" mapstructure:"disable_stacktrace"` Encoding string `yaml:"encoding" mapstructure:"encoding"` Level string `yaml:"level" mapstructure:"level"` ZapType string `yaml:"zap_type" mapstructure:"zap_type"` } |
- Map log levels:
1 2 3 4 5 6 7 8 9 10 | var loggerLevelMap = map[string]zapcore.Level{ "debug": zapcore.DebugLevel, "info": zapcore.InfoLevel, "warn": zapcore.WarnLevel, "error": zapcore.ErrorLevel, "dpanic": zapcore.DPanicLevel, "panic": zapcore.PanicLevel, "fatal": zapcore.FatalLevel, } |
- Create interfaces for functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | type ILogger interface { Debug(args ...interface{}) Debugf(template string, args ...interface{}) Info(args ...interface{}) Infof(template string, args ...interface{}) Warn(args ...interface{}) Warnf(template string, args ...interface{}) Error(args ...interface{}) Errorf(template string, args ...interface{}) DPanic(args ...interface{}) DPanicf(template string, args ...interface{}) Fatal(args ...interface{}) Fatalf(template string, args ...interface{}) } |
- Create instance logger:
1 2 | var Logger *logger = &logger{} |
- Get the path of the log file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | func getPath() string { path := "" dir, err := os.Getwd() if err != nil { panic(err) } path = dir + "/logs" if _, err := os.Stat(path); os.IsNotExist(err) { err := os.Mkdir(path, os.ModeDir) if err != nil { panic(err) } } if strings.Contains(runtime.GOOS, "window") { path = path + "\" } else { path = path + "/" } return path + "log.log" } |
- Getwd function is to get the current path when running the program.
- The os.Stat function checks if the path exists or not.
- os.Mkdir function creates folder with above path.
- When logging, each api needs a uuid.
1 2 3 4 | func (l *logger) SetLogID(key string) { l.key = key } |
- Later when writing the API, in an api we log out 5 messages for example, then set SetLogID so that when the filter relies on this id to search.
- This function will mostly be used in middleware
- Implement the functions in the interface: this code is long you see in the link .
- The configure function to log to the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | func configure() zapcore.WriteSyncer { path := getPath() fmt.Println(path) w := zapcore.AddSync(&lumberjack.Logger{ Filename: path, MaxSize: 1, // megabytes MaxBackups: 3, }) return zapcore.NewMultiWriteSyncer( zapcore.AddSync(os.Stderr), zapcore.AddSync(w), ) } |
- MaxSize is the size of the file, default is 100 megabytes, demo should be 1 megabytes.
- MaxBackups is the total number of files that will be saved, currently leaving 3 means that in the logs folder there will be 3 archived files: 1.log, 2.log, 3.log . When there is a new file 4.log, the file 1.log will be lost.
- Finally, the New function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | func Newlogger(cfg ConfigLogger) ILogger { logLevel, exist := loggerLevelMap[cfg.Level] if !exist { logLevel = zapcore.DebugLevel } var encoderCfg zapcore.EncoderConfig if cfg.Mode == "pro" { encoderCfg = zap.NewProductionEncoderConfig() } else { encoderCfg = zap.NewDevelopmentEncoderConfig() } encoderCfg.LevelKey = "LEVEL" encoderCfg.CallerKey = "CALLER" encoderCfg.TimeKey = "TIME" encoderCfg.NameKey = "NAME" encoderCfg.MessageKey = "MESSAGE" encoderCfg.EncodeDuration = zapcore.NanosDurationEncoder encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder encoderCfg.FunctionKey = "FUNC" var encoder zapcore.Encoder if cfg.Encoding == "console" { encoder = zapcore.NewConsoleEncoder(encoderCfg) } else { encoder = zapcore.NewJSONEncoder(encoderCfg) } core := zapcore.NewCore(encoder, configure(), zap.NewAtomicLevelAt(logLevel)) loggerzap := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(0)) sugarLogger := loggerzap.Sugar() logging := &logger{ sugarLogger: sugarLogger, logger: loggerzap, key: uuid.NewString(), zapSugar: strings.Contains(cfg.ZapType, "sugar"), } Logger = logging return logging } |
Kiểm TRA
- Create main.go function:
1 2 3 4 5 6 7 8 9 10 | func main() { forever := make(chan int) logger.Newlogger(logger.ConfigLogger{}) log := logger.GetLogger() for tick := range time.Tick(time.Millisecond) { log.Debug(tick) } <-forever } |
- create a forever chan to keep the main function from exiting.
- a time.Tick loop every 1 millisecond will write to the file.
- Create logs folder:
1 2 | mkdir logs |
- run:
1 2 | go run main.go |
- folder logs will automatically split files when the size is enough 1MB