go语言web开发系列之六:gin使用zap记录http服务的访问日志(access log)并按日期分割

一,安装用到的库:

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.csdn.net/weixin_43881017

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息:

1,项目地址:

https://github.com/liuhongdi/digv06

2,功能说明:记录http服务的访问日志access log

3,项目结构:如图:

三,go代码说明

1,config/config.yaml

  1.  
    Database:
  2.  
    DBType: mysql
  3.  
    UserName: root
  4.  
    Password: password
  5.  
    Host: 127.0.0.1:3306
  6.  
    DBName: dig
  7.  
    Charset: utf8
  8.  
    ParseTime: True
  9.  
    MaxIdleConns: 10
  10.  
    MaxOpenConns: 30
  11.  
    Server:
  12.  
    RunMode: debug
  13.  
    HttpPort: 8000
  14.  
    ReadTimeout: 60
  15.  
    WriteTimeout: 60
  16.  
    Log:
  17.  
    LogFilePath: /data/gologs/logs
  18.  
    LogInfoFileName: info
  19.  
    LogWarnFileName: warn
  20.  
    LogFileExt: log
  21.  
    AccessLog:
  22.  
    LogFilePath: /data/gologs/logs
  23.  
    LogFileName: access
  24.  
    LogFileExt: log

2,global/setting.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "github.com/liuhongdi/digv06/pkg/setting"
  6.  
    "time"
  7.  
    )
  8.  
    //服务器配置
  9.  
    type ServerSettingS struct {
  10.  
    RunMode string
  11.  
    HttpPort string
  12.  
    ReadTimeout time.Duration
  13.  
    WriteTimeout time.Duration
  14.  
    }
  15.  
    //数据库配置
  16.  
    type DatabaseSettingS struct {
  17.  
    DBType string
  18.  
    UserName string
  19.  
    Password string
  20.  
    Host string
  21.  
    DBName string
  22.  
    Charset string
  23.  
    ParseTime bool
  24.  
    MaxIdleConns int
  25.  
    MaxOpenConns int
  26.  
    }
  27.  
    //日志配置
  28.  
    type LogSettingS struct {
  29.  
    LogFilePath string //保存到的目录
  30.  
    LogInfoFileName string //info级日志文件的名字
  31.  
    LogWarnFileName string //warn级日志文件的名字
  32.  
    LogAccessFileName string //Access日志文件的名字
  33.  
    LogFileExt string //文件的扩展名
  34.  
    }
  35.  
    //访问日志配置
  36.  
    type AccessLogSettingS struct {
  37.  
    LogFilePath string //保存到的目录
  38.  
    LogFileName string //Access日志文件的名字
  39.  
    LogFileExt string //文件的扩展名
  40.  
    }
  41.  
    //定义全局变量
  42.  
    var (
  43.  
    ServerSetting *ServerSettingS
  44.  
    DatabaseSetting *DatabaseSettingS
  45.  
    LogSetting *LogSettingS
  46.  
    AccessLogSetting *AccessLogSettingS
  47.  
    )
  48.  
     
  49.  
    //读取配置到全局变量
  50.  
    func SetupSetting() error {
  51.  
    s, err := setting.NewSetting()
  52.  
    if err != nil {
  53.  
    return err
  54.  
    }
  55.  
    err = s.ReadSection("Database", &DatabaseSetting)
  56.  
    if err != nil {
  57.  
    return err
  58.  
    }
  59.  
     
  60.  
    err = s.ReadSection("Server", &ServerSetting)
  61.  
    if err != nil {
  62.  
    return err
  63.  
    }
  64.  
     
  65.  
    err = s.ReadSection("Log", &LogSetting)
  66.  
    if err != nil {
  67.  
    return err
  68.  
    }
  69.  
     
  70.  
    err = s.ReadSection("AccessLog", &AccessLogSetting)
  71.  
    if err != nil {
  72.  
    return err
  73.  
    }
  74.  
     
  75.  
    fmt.Println("setting:")
  76.  
    fmt.Println(ServerSetting)
  77.  
    fmt.Println(DatabaseSetting)
  78.  
    fmt.Println(LogSetting)
  79.  
    fmt.Println(AccessLogSetting)
  80.  
    return nil
  81.  
    }

3,global/accessLog.go

  1.  
    package global
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "github.com/liuhongdi/digv06/pkg/setting"
  6.  
    "time"
  7.  
    )
  8.  
    //服务器配置
  9.  
    type ServerSettingS struct {
  10.  
    RunMode string
  11.  
    HttpPort string
  12.  
    ReadTimeout time.Duration
  13.  
    WriteTimeout time.Duration
  14.  
    }
  15.  
    //数据库配置
  16.  
    type DatabaseSettingS struct {
  17.  
    DBType string
  18.  
    UserName string
  19.  
    Password string
  20.  
    Host string
  21.  
    DBName string
  22.  
    Charset string
  23.  
    ParseTime bool
  24.  
    MaxIdleConns int
  25.  
    MaxOpenConns int
  26.  
    }
  27.  
    //日志配置
  28.  
    type LogSettingS struct {
  29.  
    LogFilePath string //保存到的目录
  30.  
    LogInfoFileName string //info级日志文件的名字
  31.  
    LogWarnFileName string //warn级日志文件的名字
  32.  
    LogAccessFileName string //Access日志文件的名字
  33.  
    LogFileExt string //文件的扩展名
  34.  
    }
  35.  
    //访问日志配置
  36.  
    type AccessLogSettingS struct {
  37.  
    LogFilePath string //保存到的目录
  38.  
    LogFileName string //Access日志文件的名字
  39.  
    LogFileExt string //文件的扩展名
  40.  
    }
  41.  
    //定义全局变量
  42.  
    var (
  43.  
    ServerSetting *ServerSettingS
  44.  
    DatabaseSetting *DatabaseSettingS
  45.  
    LogSetting *LogSettingS
  46.  
    AccessLogSetting *AccessLogSettingS
  47.  
    )
  48.  
     
  49.  
    //读取配置到全局变量
  50.  
    func SetupSetting() error {
  51.  
    s, err := setting.NewSetting()
  52.  
    if err != nil {
  53.  
    return err
  54.  
    }
  55.  
    err = s.ReadSection("Database", &DatabaseSetting)
  56.  
    if err != nil {
  57.  
    return err
  58.  
    }
  59.  
     
  60.  
    err = s.ReadSection("Server", &ServerSetting)
  61.  
    if err != nil {
  62.  
    return err
  63.  
    }
  64.  
     
  65.  
    err = s.ReadSection("Log", &LogSetting)
  66.  
    if err != nil {
  67.  
    return err
  68.  
    }
  69.  
     
  70.  
    err = s.ReadSection("AccessLog", &AccessLogSetting)
  71.  
    if err != nil {
  72.  
    return err
  73.  
    }
  74.  
     
  75.  
    fmt.Println("setting:")
  76.  
    fmt.Println(ServerSetting)
  77.  
    fmt.Println(DatabaseSetting)
  78.  
    fmt.Println(LogSetting)
  79.  
    fmt.Println(AccessLogSetting)
  80.  
    return nil
  81.  
    }

4,middelware/accesslog.go

  1.  
    package middleware
  2.  
     
  3.  
    import (
  4.  
    "bytes"
  5.  
    "github.com/liuhongdi/digv06/global"
  6.  
    "github.com/liuhongdi/digv06/pkg/util"
  7.  
    "time"
  8.  
    "github.com/gin-gonic/gin"
  9.  
    )
  10.  
     
  11.  
    type AccessLogWriter struct {
  12.  
    gin.ResponseWriter
  13.  
    body *bytes.Buffer
  14.  
    }
  15.  
     
  16.  
    func (w AccessLogWriter) Write(p []byte) (int, error) {
  17.  
    if n, err := w.body.Write(p); err != nil {
  18.  
    return n, err
  19.  
    }
  20.  
    return w.ResponseWriter.Write(p)
  21.  
    }
  22.  
     
  23.  
    func AccessLog() gin.HandlerFunc {
  24.  
    return func(c *gin.Context) {
  25.  
    bodyWriter := &AccessLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
  26.  
    c.Writer = bodyWriter
  27.  
     
  28.  
    beginTime := time.Now().UnixNano()
  29.  
    c.Next()
  30.  
    endTime := time.Now().UnixNano()
  31.  
    duration:=endTime-beginTime
  32.  
     
  33.  
    s := "%s %s "%s %s" "+
  34.  
    "%s %d %d %dµs "+
  35.  
    ""%s""
  36.  
     
  37.  
    layout := "2006-01-02 15:04:05"
  38.  
    timeNow := time.Now().Format(layout)
  39.  
     
  40.  
    global.AccessLogger.Infof(s,
  41.  
    util.GetRealIp(c),
  42.  
    timeNow,
  43.  
    c.Request.Method,
  44.  
    c.Request.RequestURI,
  45.  
    c.Request.Proto,
  46.  
    bodyWriter.Status(),
  47.  
    bodyWriter.body.Len(),
  48.  
    duration/1000,
  49.  
    c.Request.Header.Get("User-Agent"),
  50.  
     
  51.  
    );
  52.  
    }
  53.  
    }

5,pkg/zaplog/zaplog.go

  1.  
    package zaplog
  2.  
     
  3.  
    import (
  4.  
    rotatelogs "github.com/lestrrat/go-file-rotatelogs"
  5.  
    "go.uber.org/zap"
  6.  
    "go.uber.org/zap/zapcore"
  7.  
    "io"
  8.  
    "time"
  9.  
    )
  10.  
     
  11.  
    //get logger
  12.  
    func GetInitLogger(filepath,infofilename,warnfilename,fileext string) (*zap.SugaredLogger,error) {
  13.  
    encoder := getEncoder()
  14.  
    //两个判断日志等级的interface
  15.  
    //warnlevel以下属于info
  16.  
    infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  17.  
    return lvl < zapcore.WarnLevel
  18.  
    })
  19.  
    //warnlevel及以上属于warn
  20.  
    warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  21.  
    return lvl >= zapcore.WarnLevel
  22.  
    })
  23.  
     
  24.  
    infoWriter,err := getLogWriter(filepath+"/"+infofilename,fileext)
  25.  
    if (err != nil) {
  26.  
    return nil,err
  27.  
    }
  28.  
    warnWriter,err2 := getLogWriter(filepath+"/"+warnfilename,fileext)
  29.  
    if (err2 != nil) {
  30.  
    return nil,err2
  31.  
    }
  32.  
     
  33.  
    //创建具体的Logger
  34.  
    core := zapcore.NewTee(
  35.  
    zapcore.NewCore(encoder, infoWriter, infoLevel),
  36.  
    zapcore.NewCore(encoder, warnWriter, warnLevel),
  37.  
    )
  38.  
    loggerres := zap.New(core, zap.AddCaller())
  39.  
     
  40.  
    return loggerres.Sugar(),nil
  41.  
    }
  42.  
     
  43.  
    //get logger
  44.  
    func GetInitAccessLogger(filepath,filename,fileext string) (*zap.SugaredLogger,error) {
  45.  
     
  46.  
    warnWriter,err2 := getLogWriter(filepath+"/"+filename,fileext)
  47.  
    if (err2 != nil) {
  48.  
    return nil,err2
  49.  
    }
  50.  
     
  51.  
    var cfg zap.Config
  52.  
    cfg =zap.Config{
  53.  
    Level: zap.NewAtomicLevelAt(zap.DebugLevel),
  54.  
    Development: true,
  55.  
    Encoding: "console",
  56.  
    EncoderConfig: zapcore.EncoderConfig{
  57.  
    MessageKey: "msg",
  58.  
    },
  59.  
    OutputPaths: []string{"stdout", "./log.txt"},
  60.  
    ErrorOutputPaths: []string{"stderr"},
  61.  
    }
  62.  
     
  63.  
    l, err := cfg.Build(SetOutput(warnWriter, cfg))
  64.  
    if err != nil {
  65.  
    panic(err)
  66.  
    }
  67.  
     
  68.  
    return l.Sugar(),nil
  69.  
    }
  70.  
     
  71.  
     
  72.  
    func SetOutput(ws zapcore.WriteSyncer, conf zap.Config) zap.Option {
  73.  
    var enc zapcore.Encoder
  74.  
    switch conf.Encoding {
  75.  
    case "json":
  76.  
    enc = zapcore.NewJSONEncoder(conf.EncoderConfig)
  77.  
    case "console":
  78.  
    enc = zapcore.NewConsoleEncoder(conf.EncoderConfig)
  79.  
    default:
  80.  
    panic("unknown encoding")
  81.  
    }
  82.  
    return zap.WrapCore(func(core zapcore.Core) zapcore.Core {
  83.  
    return zapcore.NewCore(enc, ws, conf.Level)
  84.  
    })
  85.  
    }
  86.  
     
  87.  
     
  88.  
    //Encoder
  89.  
    func getEncoder() zapcore.Encoder {
  90.  
    encoderConfig := zap.NewProductionEncoderConfig()
  91.  
    encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
  92.  
    encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
  93.  
    return zapcore.NewConsoleEncoder(encoderConfig)
  94.  
    }
  95.  
     
  96.  
    //LogWriter
  97.  
    func getLogWriter(filePath,fileext string) (zapcore.WriteSyncer,error) {
  98.  
    warnIoWriter,err := getWriter(filePath,fileext)
  99.  
    if (err != nil) {
  100.  
    return nil,err
  101.  
    }
  102.  
    return zapcore.AddSync(warnIoWriter),nil
  103.  
    }
  104.  
     
  105.  
    //日志文件切割,按天
  106.  
    func getWriter(filename,fileext string) (io.Writer,error) {
  107.  
    // 保存30天内的日志,每24小时(整点)分割一次日志
  108.  
    hook, err := rotatelogs.New(
  109.  
    filename+"_%Y%m%d."+fileext,
  110.  
    rotatelogs.WithLinkName(filename),
  111.  
    rotatelogs.WithMaxAge(time.Hour*24*30),
  112.  
    rotatelogs.WithRotationTime(time.Hour*24),
  113.  
    )
  114.  
    /*
  115.  
    //供测试用,保存30天内的日志,每1分钟(整点)分割一次日志
  116.  
    hook, err := rotatelogs.New(
  117.  
    filename+"_%Y%m%d%H%M.log",
  118.  
    rotatelogs.WithLinkName(filename),
  119.  
    rotatelogs.WithMaxAge(time.Hour*24*30),
  120.  
    rotatelogs.WithRotationTime(time.Minute*1),
  121.  
    )
  122.  
    */
  123.  
    if err != nil {
  124.  
    //panic(err)
  125.  
    return nil,err
  126.  
    }
  127.  
    return hook,nil
  128.  
    }

6,router/router.go

  1.  
    package router
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    "github.com/liuhongdi/digv06/controller"
  6.  
    "github.com/liuhongdi/digv06/global"
  7.  
    "github.com/liuhongdi/digv06/middleware"
  8.  
    "github.com/liuhongdi/digv06/pkg/result"
  9.  
    "runtime/debug"
  10.  
    )
  11.  
     
  12.  
    func Router() *gin.Engine {
  13.  
    router := gin.Default()
  14.  
    //处理异常
  15.  
    router.NoRoute(HandleNotFound)
  16.  
    router.NoMethod(HandleNotFound)
  17.  
    router.Use(middleware.AccessLog())
  18.  
    router.Use(Recover)
  19.  
     
  20.  
    // 路径映射
  21.  
    articlec:=controller.NewArticleController()
  22.  
    router.GET("/article/getone/:id", articlec.GetOne);
  23.  
    router.GET("/article/list", articlec.GetList);
  24.  
    return router
  25.  
    }
  26.  
     
  27.  
    //404
  28.  
    func HandleNotFound(c *gin.Context) {
  29.  
    global.Logger.Errorf("handle not found: %v", c.Request.RequestURI)
  30.  
    //global.Logger.Errorf("stack: %v",string(debug.Stack()))
  31.  
    result.NewResult(c).Error(404,"资源未找到")
  32.  
    return
  33.  
    }
  34.  
     
  35.  
    //500
  36.  
    func Recover(c *gin.Context) {
  37.  
    defer func() {
  38.  
    if r := recover(); r != nil {
  39.  
    //打印错误堆栈信息
  40.  
    //log.Printf("panic: %v ", r)
  41.  
    global.Logger.Errorf("panic: %v", r)
  42.  
    //log stack
  43.  
    global.Logger.Errorf("stack: %v",string(debug.Stack()))
  44.  
    //print stack
  45.  
    debug.PrintStack()
  46.  
    //return
  47.  
    result.NewResult(c).Error(500,"服务器内部错误")
  48.  
    }
  49.  
    }()
  50.  
    //继续后续接口调用
  51.  
    c.Next()
  52.  
    }

7,main.go

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "github.com/gin-gonic/gin"
  5.  
    _ "github.com/jinzhu/gorm/dialects/mysql"
  6.  
    "github.com/liuhongdi/digv06/global"
  7.  
    "github.com/liuhongdi/digv06/router"
  8.  
    "log"
  9.  
    )
  10.  
     
  11.  
    //init
  12.  
    func init() {
  13.  
    //setting
  14.  
    err := global.SetupSetting()
  15.  
    if err != nil {
  16.  
    log.Fatalf("init.setupSetting err: %v", err)
  17.  
    }
  18.  
     
  19.  
    //logger
  20.  
    err = global.SetupLogger()
  21.  
    if err != nil {
  22.  
    log.Fatalf("init.SetupLogger err: %v", err)
  23.  
    }
  24.  
     
  25.  
    //access logger
  26.  
    err = global.SetupAccessLogger()
  27.  
    if err != nil {
  28.  
    log.Fatalf("init.SetupAccessLogger err: %v", err)
  29.  
    }
  30.  
     
  31.  
    //db
  32.  
    err = global.SetupDBLink()
  33.  
    if err != nil {
  34.  
    log.Fatalf("init.SetupLogger err: %v", err)
  35.  
    global.Logger.Fatalf("init.setupDBEngine err: %v", err)
  36.  
    }
  37.  
     
  38.  
    global.Logger.Infof("------应用init结束")
  39.  
    //global.Logger.
  40.  
    }
  41.  
     
  42.  
    func main() {
  43.  
    global.Logger.Infof("------应用main函数开始")
  44.  
    //设置运行模式
  45.  
    gin.SetMode(global.ServerSetting.RunMode)
  46.  
    //引入路由
  47.  
    r := router.Router()
  48.  
    //run
  49.  
    r.Run(":"+global.ServerSetting.HttpPort)
  50.  
    }

8,其他代码请参见github

四,测试效果

访问url:

http://127.0.0.1:8000/article/getone/1

然后查看日志:

  1.  
    root@ku:/data/gologs/logs# more access_20201216.log
  2.  
    127.0.0.1 2020-12-16 15:41:08 "GET /article/getone/1" HTTP/1.1 200 571 13710µs "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0"
  3.  
    127.0.0.1 2020-12-16 15:41:11 "GET /article/getone/1" HTTP/1.1 200 571 2162µs "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0"
  4.  
    127.0.0.1 2020-12-16 15:41:16 "GET /article/getone/100" HTTP/1.1 200 52 770µs "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0"

五,查看各个库的版本:

  1.  
    module github.com/liuhongdi/digv06
  2.  
     
  3.  
    go 1.15
  4.  
     
  5.  
    require (
  6.  
    github.com/gin-gonic/gin v1.6.3
  7.  
    github.com/go-playground/universal-translator v0.17.0
  8.  
    github.com/go-playground/validator/v10 v10.2.0
  9.  
    github.com/jinzhu/gorm v1.9.16
  10.  
    github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
  11.  
    github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
  12.  
    github.com/magiconair/properties v1.8.4 // indirect
  13.  
    github.com/mitchellh/mapstructure v1.3.3 // indirect
  14.  
    github.com/pelletier/go-toml v1.8.1 // indirect
  15.  
    github.com/pkg/errors v0.9.1 // indirect
  16.  
    github.com/spf13/afero v1.4.1 // indirect
  17.  
    github.com/spf13/cast v1.3.1 // indirect
  18.  
    github.com/spf13/jwalterweatherman v1.1.0 // indirect
  19.  
    github.com/spf13/pflag v1.0.5 // indirect
  20.  
    github.com/spf13/viper v1.7.1
  21.  
    go.uber.org/multierr v1.6.0 // indirect
  22.  
    go.uber.org/zap v1.16.0
  23.  
    golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
  24.  
    golang.org/x/text v0.3.4 // indirect
  25.  
    gopkg.in/ini.v1 v1.62.0 // indirect
  26.  
    gopkg.in/yaml.v2 v2.3.0 // indirect
  27.  
    )
原文地址:https://www.cnblogs.com/ExMan/p/14312182.html