一,安装需要用到的库:
1,安装zap日志库:
liuhongdi@ku:/data/liuhongdi/zaplog$ go get -u go.uber.org/zap
2,安装go-file-rotatelogs库
liuhongdi@ku:/data/liuhongdi/zaplog2$ go get -u github.com/lestrrat/go-file-rotatelogs
说明:刘宏缔的go森林是一个专注golang的博客,
网站:https://blog.imgtouch.com
原文: go语言web开发系列之五:gin用zap+file-rotatelogs实现日志记录及按日期切分日志 – 架构森林
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息
1,项目地址:
GitHub - liuhongdi/digv05: gin+zap+file-rotatelogs实现日志记录及按日期切分日志
2,项目的功能:集成zap和go-file-rotatelogs实现记录日志和日志切分
3,项目结构:如图:
三,go代码说明:
1,config/config.yaml
Database:
DBType: mysql
UserName: root
Password: password
Host: 127.0.0.1:3306
DBName: dig
Charset: utf8
ParseTime: True
MaxIdleConns: 10
MaxOpenConns: 30
Server:
RunMode: debug
HttpPort: 8000
ReadTimeout: 60
WriteTimeout: 60
Log:
LogFilePath: /data/gologs/logs
LogInfoFileName: info
LogWarnFileName: warn
LogFileExt: log
2,global/logger.go
package global
import (
"github.com/liuhongdi/digv05/pkg/zaplog"
"go.uber.org/zap"
)
var (
Logger *zap.SugaredLogger
)
//创建logger
func SetupLogger() (error) {
var err error
filepath:= LogSetting.LogFilePath
infofilename:= LogSetting.LogInfoFileName
warnfilename:= LogSetting.LogWarnFileName
fileext:= LogSetting.LogFileExt
//infofilename,warnfilename,fileext string
Logger,err = zaplog.GetInitLogger(filepath,infofilename,warnfilename,fileext)
if err != nil {
return err
}
defer Logger.Sync()
return nil
}
3,pkg/zaplog/zaplog.go
package zaplog
import (
rotatelogs "github.com/lestrrat/go-file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"time"
)
//get logger
func GetInitLogger(filepath,infofilename,warnfilename,fileext string) (*zap.SugaredLogger,error) {
encoder := getEncoder()
//两个判断日志等级的interface
//warnlevel以下属于info
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.WarnLevel
})
//warnlevel及以上属于warn
warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel
})
infoWriter,err := getLogWriter(filepath+"/"+infofilename,fileext)
if (err != nil) {
return nil,err
}
warnWriter,err2 := getLogWriter(filepath+"/"+warnfilename,fileext)
if (err2 != nil) {
return nil,err2
}
//创建具体的Logger
core := zapcore.NewTee(
zapcore.NewCore(encoder, infoWriter, infoLevel),
zapcore.NewCore(encoder, warnWriter, warnLevel),
)
loggerres := zap.New(core, zap.AddCaller())
return loggerres.Sugar(),nil
}
//Encoder
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
//encoderConfig := zap.NewDevelopmentEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
//LogWriter
func getLogWriter(filePath,fileext string) (zapcore.WriteSyncer,error) {
warnIoWriter,err := getWriter(filePath,fileext)
if (err != nil) {
return nil,err
}
return zapcore.AddSync(warnIoWriter),nil
}
//日志文件切割,按天
func getWriter(filename,fileext string) (io.Writer,error) {
// 保存30天内的日志,每24小时(整点)分割一次日志
hook, err := rotatelogs.New(
filename+"_%Y%m%d."+fileext,
rotatelogs.WithLinkName(filename),
rotatelogs.WithMaxAge(time.Hour*24*30),
rotatelogs.WithRotationTime(time.Hour*24),
)
/*
//供测试用,保存30天内的日志,每1分钟(整点)分割一次日志
hook, err := rotatelogs.New(
filename+"_%Y%m%d%H%M.log",
rotatelogs.WithLinkName(filename),
rotatelogs.WithMaxAge(time.Hour*24*30),
rotatelogs.WithRotationTime(time.Minute*1),
)
*/
if err != nil {
//panic(err)
return nil,err
}
return hook,nil
}
4,router/router.go
package router
import (
"github.com/gin-gonic/gin"
"github.com/liuhongdi/digv05/controller"
"github.com/liuhongdi/digv05/global"
"github.com/liuhongdi/digv05/pkg/result"
"runtime/debug"
)
func Router() *gin.Engine {
router := gin.Default()
//处理异常
router.NoRoute(HandleNotFound)
router.NoMethod(HandleNotFound)
router.Use(Recover)
// 路径映射
articlec:=controller.NewArticleController()
router.GET("/article/getone/:id", articlec.GetOne);
router.GET("/article/list", articlec.GetList);
return router
}
//404
func HandleNotFound(c *gin.Context) {
global.Logger.Errorf("handle not found: %v", c.Request.RequestURI)
//global.Logger.Errorf("stack: %v",string(debug.Stack()))
result.NewResult(c).Error(404,"资源未找到")
return
}
//500
func Recover(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
//打印错误堆栈信息
//log.Printf("panic: %v\n", r)
global.Logger.Errorf("panic: %v", r)
//log stack
global.Logger.Errorf("stack: %v",string(debug.Stack()))
//print stack
debug.PrintStack()
//return
result.NewResult(c).Error(500,"服务器内部错误")
}
}()
//继续后续接口调用
c.Next()
}
5,global/setting.go
package global
import (
"fmt"
"github.com/liuhongdi/digv05/pkg/setting"
"time"
)
//服务器配置
type ServerSettingS struct {
RunMode string
HttpPort string
ReadTimeout time.Duration
WriteTimeout time.Duration
}
//数据库配置
type DatabaseSettingS struct {
DBType string
UserName string
Password string
Host string
DBName string
Charset string
ParseTime bool
MaxIdleConns int
MaxOpenConns int
}
//日志配置
type LogSettingS struct {
LogFilePath string //保存到的目录
LogInfoFileName string //info级日志文件的名字
LogWarnFileName string //warn级日志文件的名字
LogFileExt string //文件的扩展名
}
//定义全局变量
var (
ServerSetting *ServerSettingS
DatabaseSetting *DatabaseSettingS
LogSetting *LogSettingS
)
//读取配置到全局变量
func SetupSetting() error {
s, err := setting.NewSetting()
if err != nil {
return err
}
err = s.ReadSection("Database", &DatabaseSetting)
if err != nil {
return err
}
err = s.ReadSection("Server", &ServerSetting)
if err != nil {
return err
}
err = s.ReadSection("Log", &LogSetting)
if err != nil {
return err
}
fmt.Println("setting:")
fmt.Println(ServerSetting)
fmt.Println(DatabaseSetting)
fmt.Println(LogSetting)
return nil
}
6,main.go
package main
import (
"github.com/gin-gonic/gin"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/liuhongdi/digv05/global"
"github.com/liuhongdi/digv05/router"
"log"
)
//init
func init() {
//setting
err := global.SetupSetting()
if err != nil {
log.Fatalf("init.setupSetting err: %v", err)
}
//logger
err = global.SetupLogger()
if err != nil {
log.Fatalf("init.SetupLogger err: %v", err)
}
//db
err = global.SetupDBLink()
if err != nil {
log.Fatalf("init.SetupLogger err: %v", err)
global.Logger.Fatalf("init.setupDBEngine err: %v", err)
}
global.Logger.Infof("------应用init结束")
}
func main() {
global.Logger.Infof("------应用main函数开始")
//设置运行模式
gin.SetMode(global.ServerSetting.RunMode)
//引入路由
r := router.Router()
//run
r.Run(":"+global.ServerSetting.HttpPort)
}
7,其他相关代码请参见github
四,测试效果
1,查看info级日志:
启用应用后:
root@ku:/data/gologs/logs# more info
2020-12-16T14:37:45.666+0800 INFO digv05/main.go:32 ------应用init结束
2020-12-16T14:37:45.667+0800 INFO digv05/main.go:36 ------应用main函数开始
注意:info文件其实是一个符号链接,指向到当前目录下的/data/gologs/logs/info_20201216.log
root@ku:/data/gologs/logs# ll info
lrwxrwxrwx 1 liuhongdi liuhongdi 35 12月 16 14:37 info -> /data/gologs/logs/info_20201216.log
2,查看错误日志:
访问:
http://127.0.0.1:8000/article/getone/100
引发一次内部错误:我们在代码中故意添加了一个除0操作用来引发异常
页面返回:
查看错误日志:
root@ku:/data/gologs/logs# more warn
2020-12-16T14:45:37.533+0800 ERROR router/router.go:40 panic: runtime error: integer divide by zero
2020-12-16T14:45:37.533+0800 ERROR router/router.go:42 stack: goroutine 8 [running]:
runtime/debug.Stack(0xc0000112e8, 0xaf3002, 0xba2225)
/usr/local/soft/go/src/runtime/debug/stack.go:24 +0x9f
github.com/liuhongdi/digv05/router.Recover.func1(0xc0002d4000)
/data/liuhongdi/digv05/router/router.go:42 +0xdb
panic(0xaf30a0, 0x100e790)
/usr/local/soft/go/src/runtime/panic.go:969 +0x1b9
github.com/liuhongdi/digv05/controller.(*ArticleController).GetOne(0x1053de8, 0xc0002d4000)
/data/liuhongdi/digv05/controller/articleController.go:30 +0x1ff
github.com/gin-gonic/gin.(*Context).Next(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/context.go:161 +0x3b
github.com/liuhongdi/digv05/router.Recover(0xc0002d4000)
/data/liuhongdi/digv05/router/router.go:50 +0x4e
github.com/gin-gonic/gin.(*Context).Next(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/context.go:161 +0x3b
github.com/gin-gonic/gin.RecoveryWithWriter.func1(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/recovery.go:83 +0x65
github.com/gin-gonic/gin.(*Context).Next(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/context.go:161 +0x3b
github.com/gin-gonic/gin.LoggerWithConfig.func1(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/logger.go:241 +0xe5
github.com/gin-gonic/gin.(*Context).Next(0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/context.go:161 +0x3b
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc0002b8000, 0xc0002d4000)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/gin.go:409 +0x67a
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc0002b8000, 0xc84a20, 0xc0002b61c0, 0xc000398900)
/home/liuhongdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3/gin.go:367 +0x14d
net/http.serverHandler.ServeHTTP(0xc0002b60e0, 0xc84a20, 0xc0002b61c0, 0xc000398900)
/usr/local/soft/go/src/net/http/server.go:2843 +0xa3
net/http.(*conn).serve(0xc00035d720, 0xc86e20, 0xc000396040)
/usr/local/soft/go/src/net/http/server.go:1925 +0x8ad
created by net/http.(*Server).Serve
/usr/local/soft/go/src/net/http/server.go:2969 +0x36c
...
五,查看各库的版本:
module github.com/liuhongdi/digv05
go 1.15
require (
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/universal-translator v0.17.0
github.com/go-playground/validator/v10 v10.2.0
github.com/jinzhu/gorm v1.9.16
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.4.1 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
golang.org/x/text v0.3.4 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)