Go--日志
一、Logger
go语言默认提供的日志功能,包为ttps://golang.org/pkg/log/
优势:
- 使用非常简单,可以设置任何io.Writer作为日志记录输出并向其发送要写入的日志
劣势:
- 仅限基本的日志级别
- 只有一个Print选项。不支持INFO/DEBUG等多个级别
- 对于错误日志,它有Fatal和Panic
- Fatal日志通过调用os.Exit(1)来结束程序
- Panic日志在写入日志消息之后抛出一个panic
- 但是它缺少一个ERROR日志级别,这个级别可以在不抛出panic或退出程序的情况下记录错误
- 缺乏日志格式化的能力——例如记录调用者的函数名和行号,格式化日期和时间格式等
- 不提供日志切割的能力
实例:
package main import ( "fmt" "log" "os" "time" ) func main() { //创建输出日志文件 logFile, err := os.Create("./" + time.Now().Format("20060102") + ".txt") if err != nil { fmt.Println(err) } //创建一个Logger //参数1:日志写入目的地 //参数2:每条日志的前缀 //参数3:日志属性 loger := log.New(logFile, "test_", log.Ldate|log.Ltime|log.Lshortfile) //Flags返回Logger的输出选项 fmt.Println(loger.Flags()) //SetFlags设置输出选项 loger.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) //返回输出前缀 fmt.Println(loger.Prefix()) //设置输出前缀 loger.SetPrefix("test_") //输出一条日志 loger.Output(2, "打印一条日志信息") //格式化输出日志 // loger.Printf("第%d行 内容:%s", 11, "我是错误") //等价于print();os.Exit(1); // loger.Fatal("我是错误") //等价于print();panic(); // loger.Panic("我是错误33333333") //log的导出函数 //导出函数基于std,std是标准错误输出 //var std = New(os.Stderr, "", LstdFlags) //获取输出项 fmt.Println(log.Flags()) //获取前缀 fmt.Printf(log.Prefix()) }
二、Zap
2.1 安装
go get -u go.uber.org/zap
Zap提供了两种类型的日志记录器—Sugared Logger和Logger。
在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。
在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录
2.2 创建一个logger
创建logger,使用zap.NewDevelopment()、zap.NewExample()、zap.NewProduction(),各自会有一些预定义的设置,适合不同的场景,依次为开发,测试,生产环境
或者用zap.New(),可定制化
//设置全局变量 var logger *zap.Logger var sugarLogger *zap.SugaredLogger func InitLogger() { logger, _ = zap.NewProduction() } func InitSugarLogger() { logger, _ = zap.NewProduction() sugarLogger = logger.Sugar() }
2.3 字段类型
zap.Type(Type为bool/int/uint/float64/complex64/time.Time/time.Duration/error等)就表示该类型的字段,zap.Typep以p结尾表示该类型指针的字段,zap.Types以s结尾表示该类型切片的字段,如:
- zap.Bool(key string, val bool) Field:bool字段
- zap.Boolp(key string, val *bool) Field:bool指针字段
- zap.Bools(key string, val []bool) Field:bool切片字段
- zap.Any(key string, value interface{}) Field:任意类型的字段
- zap.Binary(key string, val []byte) Field:二进制串的字段
实例:
if err != nil { logger.Error( //打印的日志等级,如Info / Error/ Debug / Panic "Error fetching url..", zap.String("url", url), //字段类型 zap.Error(err)) }
2.4 使用方法
logger
func simpleHttpGet(url string) { resp, err := http.Get(url) if err != nil { logger.Error( "Error fetching url..", zap.String("url", url), zap.Error(err)) } else { logger.Info("Success..", zap.String("statusCode", resp.Status), zap.String("url", url)) resp.Body.Close() } }
Sugared Logger
func simpleHttpGet(url string) { sugarLogger.Debugf("Trying to hit GET request for %s", url) resp, err := http.Get(url) if err != nil { sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err) } else { sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url) resp.Body.Close() } }
2.5 将日志写入文件
使用New()来定制化一个logger:zapcore.Core需要三个配置——Encoder,WriteSyncer,LogLevel
func New(core zapcore.Core, options ...Option) *Logger
- Encoder: 编码器(如何写入日志) 这里使用默认的NewJSONEncoder() 及预设置好的 ProductionEncoderConfig()
- WriterSyncer :指定日志将写到哪里去 使用zapcore.AddSync()函数并且将打开的文件句柄传进去
- Log Level: 哪种级别的日志将被写入
实例:
func InitNewSugarLogger() { //以json格式写入 encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) //创建写入的日志文件 file, _ := os.Create("./test.log") writeSyncer := zapcore.AddSync(file) core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) logger := zap.New(core) sugarLogger = logger.Sugar() }
2.6 输出格式
2.6.1 数据格式
logger默认输出JSON结构格式,如:
{"level":"info","msg":"Failed to fetch URL: http://example.org/api"}
将JSON Encoder更改为普通的Log Encoder,将2.4中实例的NewJSONEncoder()改为NewConsoleEncoder()即可
encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
输出为:
1.5731818118626883e+09 debug Trying to hit GET request for http://www.google.com
2.6.2 时间格式
默认输出时间并不适合阅读,如:
1.5731818329012108e+09 error Error fetching URL http://www.google.com
需要更改2.4实例中的zapcore函数
func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder return zapcore.NewConsoleEncoder(encoderConfig) } //在创建时添加zap.AddCaller() logger := zap.New(core, zap.AddCaller())
三、日志切割
Zap本身不支持切割归档日志文件,将使用第三方库Lumberjack来实现
安装:
go get -u github.com/natefinch/lumberjack
zap logger中加入Lumberjack
//写入的文件,并切割日志 func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./test1.log", //日志文件的位置 MaxSize: 1, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 30, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) }
四、完整案例
package main import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "net/http" ) //设置全局变量,使用sugarLogger var sugarLogger *zap.SugaredLogger //以json格式写入,并修改时间编码 func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder return zapcore.NewConsoleEncoder(encoderConfig) } //写入的文件,并切割日志 func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./test1.log", //日志文件的位置 MaxSize: 1, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 30, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) } // InitLogger 自定义logger func InitLogger() { writeSyncer := getLogWriter() encoder := getEncoder() core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) //创建时添加将调用函数信息记录到日志中的功能 logger := zap.New(core, zap.AddCaller()) sugarLogger = logger.Sugar() } //测试访问url func simpleHttpGet(url string) { sugarLogger.Debugf("Trying to hit GET request for %s", url) resp, err := http.Get(url) if err != nil { sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err) } else { sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url) resp.Body.Close() } } func main() { InitLogger() //zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中 defer sugarLogger.Sync() simpleHttpGet("www.xxx.com") simpleHttpGet("https://www.google.com") }
文件里的日志为:
2023-04-27T17:23:48.749+0800 DEBUG test/logs.go:47 Trying to hit GET request for www.xxx.com 2023-04-27T17:23:48.760+0800 ERROR test/logs.go:50 Error fetching URL www.xxx.com : Error = Get "www.xxx.com": unsupported protocol scheme "" 2023-04-27T17:23:48.760+0800 DEBUG test/logs.go:47 Trying to hit GET request for https://www.google.com 2023-04-27T17:23:49.344+0800 INFO test/logs.go:52 Success! statusCode = 429 Too Many Requests for URL https://www.google.com
logger:
package main import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "net/http" "os" ) // 设置全局变量,使用Logger var logger *zap.Logger // 以json格式写入,并修改时间编码 func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //将JSON Encode改为普通的log Encoder,使用NewConsoleEncoder() return zapcore.NewConsoleEncoder(encoderConfig) } // 写入的文件,并切割日志 func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./err.log", //日志文件的位置 MaxSize: 1, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 30, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) } // 写入的文件,并切割日志 func getInfoWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./info.log", //日志文件的位置 MaxSize: 100, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 15, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) } // Init 自定义logger func init() { writeSyncer := getLogWriter() encoder := getEncoder() //设置不同的日志等级;支持>= <= > < == infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.InfoLevel }) errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.ErrorLevel }) core := zapcore.NewTee( //开发测试时输出到控制台,便于查看 zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.InfoLevel), //生产环境将不同等级日志写入相应的日志文件 zapcore.NewCore(encoder, writeSyncer, errorLevel), zapcore.NewCore(encoder, getInfoWriter(), infoLevel), ) logger = zap.New(core) //zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中 defer logger.Sync() } // 测试访问url func simpleHttpGet(url string) { resp, err := http.Get(url) if err != nil { logger.Error("Error fetching url..") } else { logger.Info("Success..", zap.String("statusCode", resp.Status), zap.String("url", url)) resp.Body.Close() } } func main() { simpleHttpGet("www.xxx.com") simpleHttpGet("https://www.google.com") }
日志文件:
023-05-18T11:38:42.311+0800 ERROR test/logs.go:54 Error fetching url.. 2023-05-18T11:38:42.746+0800 INFO test/logs.go:56 Success.. {"statusCode": "429 Too Many Requests", "url": "https://www.google.com"} 2023-05-18T11:40:38.340+0800 ERROR test/logs.go:51 Error fetching url.. 2023-05-18T11:40:38.754+0800 INFO test/logs.go:53 Success.. {"statusCode": "429 Too Many Requests", "url": "https://www.google.com"}
参考:https://www.topgoer.com/%E9%A1%B9%E7%9B%AE/log/Logger.html
五、补充
package logs import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) // 设置全局变量,使用Logger var Logger *zap.Logger // 以json格式写入,并修改时间编码 func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //将JSON Encode改为普通的log Encoder,使用NewConsoleEncoder() return zapcore.NewConsoleEncoder(encoderConfig) } // 写入的文件,并切割日志 func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./err.log", //日志文件的位置 MaxSize: 1, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 30, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) } // 写入的文件,并切割日志 func getInfoWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: "./info.log", //日志文件的位置 MaxSize: 100, //在进行切割之前,日志文件的最大大小(以MB为单位) MaxBackups: 5, //保留旧文件的最大个 MaxAge: 15, //保留旧文件的最大天数 Compress: false, //是否压缩/归档旧文件 } //将打开的文件句柄传进去 return zapcore.AddSync(lumberJackLogger) } // Init 自定义logger func init() { writeSyncer := getLogWriter() encoder := getEncoder() //设置不同的日志等级;支持>= <= > < == infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.InfoLevel }) errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.ErrorLevel }) core := zapcore.NewTee( //开发测试时输出到控制台,便于查看 zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.InfoLevel), //生产环境将不同等级日志写入相应的日志文件 zapcore.NewCore(encoder, writeSyncer, errorLevel), zapcore.NewCore(encoder, getInfoWriter(), infoLevel), ) Logger = zap.New(core) //zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中 defer Logger.Sync() }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2022-04-27 Grafana--变量(label_values)