Zap 日志库
常见的日志级别
- panic:(error)错误已经发生,会导致系统退出
- error:错误虽然发生,但是不影响系统的继续进行
- warn:表明会出现潜在的错误
- info:通知你一声
- debug:调试输出,对于调试应用程序非常用帮助
基本配置
登录测试
package main
import (
"net/http"
"go.uber.org/zap"
)
func InitLogger() {
logger, _ = zap.NewProduction() // 生产级别的库
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
//zap.String是个key-value的键值对
logger.Error("打开url失败", zap.String("url", url), zap.Error(err))
} else {
logger.Info("Success...", zap.String("访问成功", resp.Status), zap.String("url", url))
resp.Body.Close() //关闭访问
}
}
var logger *zap.Logger
func main() {
InitLogger()
defer logger.Sync() // 把日志记录到磁盘上面
simpleHttpGet("www.baidu.com")
simpleHttpGet("http://www.baidu.com")
}
运行出来的结果
输出结果为json
格式
[
{
"level": "error",
"ts": 1625745481.5386593,
"caller": "Log/main.go:16",
"msg": "打开url失败",
"url": "www.baidu.com",
"error": "Get \"www.baidu.com\": unsupported protocol scheme \"\"",
"stacktrace": "main.simpleHttpGet\n\tE:/DesKtop/go/Module/Log/main.go:16\nmain.main\n\tE:/DesKtop/go/Module/Log/main.go:28\nruntime.main\n\tD:/Go/src/runtime/proc.go:225"
},
{
"level": "info",
"ts": 1625745481.5898762,
"caller": "Log/main.go:18",
"msg": "Success...",
"访问成功": "200 OK",
"url": "http://www.baidu.com"
}
]
配置自定义的日志文件
包括写到文件中,定义写入格式,定义备份,还有日志分割等功能。
package main
import (
"net/http"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func InitLogger() {
//logger, _ = zap.NewProduction() // 生产级别的库
codeconfig := zapCoreConfig() //调用自定义的记录格式
encoder := zapcore.NewJSONEncoder(codeconfig) //使用json的方式编码
//日志写到哪里,这种是原始的
//file, _ := os.OpenFile("./logtest.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0744)
//带有日志分割功能的记录
file := &lumberjack.Logger{
Filename: "./testzap.log",
MaxSize: 1, //最大多大
MaxBackups: 3, //最大备份数量
MaxAge: 30, //3天
Compress: false, //不压缩
}
writeSyncer := zapcore.AddSync(file) //写入到哪个文件
//传入配置,并声明是debug文件
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
logger = zap.New(core, zap.AddCaller()) //配置完成,第二个参数表示,要添加调用者的信息
}
func zapCoreConfig() zapcore.EncoderConfig { //自定义的格式书写类型
return zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder, //更改成is08601格式,而不是时间戳
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
func simpleHttpGet(url string) {
resp, err := http.Get(url)
for i := 0; i < 5000; i++ {
if err != nil {
//zap.string是个 json 的键值对
logger.Error("打开url失败", zap.String("url", url), zap.Error(err))
} else {
logger.Info("Success...", zap.String("访问成功", resp.Status), zap.String("url", url))
resp.Body.Close() //关闭访问
}
}
}
var logger *zap.Logger
func main() {
InitLogger()
defer logger.Sync() // 把日志记录到磁盘上面
simpleHttpGet("www.baidu.com")
simpleHttpGet("http://www.baidu.com")
}
在gin框架中集成loger组件
其实就是相当于写了两个中间件,执行不同的操作
var logger *zap.Logger
func main() {
r := gin.New()
//启动了两个日志库
r.Use(GinLogger(logger), GinRecovery(logger, true))
r.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "哈哈,你好")
})
r.Run(":5002")
}
// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost), //花费的时间
)
}
}
// GinRecovery recover掉项目可能出现的panic
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can‘t write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
配置管理神器-Viper
Viper
的几个特性
- 设置默认值
- 从 JSON、TOML、YAML、格式的配置文件中读取配置信息
- 实时监控和重读配置文件
- 从远程系统中,读取并监控配置变化
- 从命令行参数读取配置
- 从buffer中读取配置信息
- 显示配置值
Viper的先后顺序
- 显示配置
set
设置值 - 命令行参数
- 环境变量
- 配置文件
- key/value存储
- 默认值
重要: 目前的 Viper的配置key
大小写是不敏感的。
package main
import (
"fmt"
"net/http"
"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)
func ReadConfig() {
viper.SetDefault("filedir", "./") //设置默认值,优先级别最低的
// viper.SetConfigName("config") //配置文件名称,无扩展名
// viper.SetConfigType("yaml") //如果配置文件中没有扩展名,就默认使用这一个
viper.SetConfigFile("./config.json") //直接指定要读取的配置w文件
viper.AddConfigPath("./etc/appname/") //配置文件所在的路径
viper.AddConfigPath("./etc/appname2/") //多次调用,以添加多个搜索路径
viper.AddConfigPath(".") //还可以在当前多个目录查找
err := viper.ReadInConfig() //查找并读取配置文件
if err != nil {
panic(fmt.Errorf("配置文件错误: %s", err)) //输出错误信息,通常用这种方式来自定义errorf信息
}
}
func WriteConfig() {
viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
viper.SafeWriteConfig() //不会覆盖你的文件,没有的话,就要不错
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")
}
func main() {
ReadConfig()
viper.WatchConfig() //实时加载配置文件
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("配置文件变化后的回调函数", e.Name, e.Op) //可以动态的更改文件
})
r := gin.Default()
r.GET("/version", func(c *gin.Context) {
c.String(http.StatusOK, viper.GetString("student.student1"))
})
r.Run(":5002")
}
优雅关机和重启
优雅的关机就是服务器关机命令发出不是立即关机,而是等待还在处理的请求,全部处理完毕以后,再退出去程序,是一种对客户端友好的关机方式,而执行 Ctrl + C
关闭服务器时,会强制结束进程,让访问的请求出现问题