一,安装用到的库:
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记录http服务的访问日志(access log)并按日期分割 – 架构森林
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息:
1,项目地址:
GitHub - liuhongdi/digv06: gin使用zap记录http服务的访问日志(access log)并按日期分割
2,功能说明:记录http服务的访问日志access log
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
AccessLog:
LogFilePath: /data/gologs/logs
LogFileName: access
LogFileExt: log
2,global/setting.go
package global
import (
"fmt"
"github.com/liuhongdi/digv06/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级日志文件的名字
LogAccessFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//访问日志配置
type AccessLogSettingS struct {
LogFilePath string //保存到的目录
LogFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//定义全局变量
var (
ServerSetting *ServerSettingS
DatabaseSetting *DatabaseSettingS
LogSetting *LogSettingS
AccessLogSetting *AccessLogSettingS
)
//读取配置到全局变量
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
}
err = s.ReadSection("AccessLog", &AccessLogSetting)
if err != nil {
return err
}
fmt.Println("setting:")
fmt.Println(ServerSetting)
fmt.Println(DatabaseSetting)
fmt.Println(LogSetting)
fmt.Println(AccessLogSetting)
return nil
}
3,global/accessLog.go
package global
import (
"fmt"
"github.com/liuhongdi/digv06/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级日志文件的名字
LogAccessFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//访问日志配置
type AccessLogSettingS struct {
LogFilePath string //保存到的目录
LogFileName string //Access日志文件的名字
LogFileExt string //文件的扩展名
}
//定义全局变量
var (
ServerSetting *ServerSettingS
DatabaseSetting *DatabaseSettingS
LogSetting *LogSettingS
AccessLogSetting *AccessLogSettingS
)
//读取配置到全局变量
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
}
err = s.ReadSection("AccessLog", &AccessLogSetting)
if err != nil {
return err
}
fmt.Println("setting:")
fmt.Println(ServerSetting)
fmt.Println(DatabaseSetting)
fmt.Println(LogSetting)
fmt.Println(AccessLogSetting)
return nil
}
4,middelware/accesslog.go
package middleware
import (
"bytes"
"github.com/liuhongdi/digv06/global"
"github.com/liuhongdi/digv06/pkg/util"
"time"
"github.com/gin-gonic/gin"
)
type AccessLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w AccessLogWriter) Write(p []byte) (int, error) {
if n, err := w.body.Write(p); err != nil {
return n, err
}
return w.ResponseWriter.Write(p)
}
func AccessLog() gin.HandlerFunc {
return func(c *gin.Context) {
bodyWriter := &AccessLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyWriter
beginTime := time.Now().UnixNano()
c.Next()
endTime := time.Now().UnixNano()
duration:=endTime-beginTime
s := "%s %s \"%s %s\" "+
"%s %d %d %dµs "+
"\"%s\""
layout := "2006-01-02 15:04:05"
timeNow := time.Now().Format(layout)
global.AccessLogger.Infof(s,
util.GetRealIp(c),
timeNow,
c.Request.Method,
c.Request.RequestURI,
c.Request.Proto,
bodyWriter.Status(),
bodyWriter.body.Len(),
duration/1000,
c.Request.Header.Get("User-Agent"),
);
}
}
5,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
}
//get logger
func GetInitAccessLogger(filepath,filename,fileext string) (*zap.SugaredLogger,error) {
warnWriter,err2 := getLogWriter(filepath+"/"+filename,fileext)
if (err2 != nil) {
return nil,err2
}
var cfg zap.Config
cfg =zap.Config{
Level: zap.NewAtomicLevelAt(zap.DebugLevel),
Development: true,
Encoding: "console",
EncoderConfig: zapcore.EncoderConfig{
MessageKey: "msg",
},
OutputPaths: []string{"stdout", "./log.txt"},
ErrorOutputPaths: []string{"stderr"},
}
l, err := cfg.Build(SetOutput(warnWriter, cfg))
if err != nil {
panic(err)
}
return l.Sugar(),nil
}
func SetOutput(ws zapcore.WriteSyncer, conf zap.Config) zap.Option {
var enc zapcore.Encoder
switch conf.Encoding {
case "json":
enc = zapcore.NewJSONEncoder(conf.EncoderConfig)
case "console":
enc = zapcore.NewConsoleEncoder(conf.EncoderConfig)
default:
panic("unknown encoding")
}
return zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewCore(enc, ws, conf.Level)
})
}
//Encoder
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
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
}
6,router/router.go
package router
import (
"github.com/gin-gonic/gin"
"github.com/liuhongdi/digv06/controller"
"github.com/liuhongdi/digv06/global"
"github.com/liuhongdi/digv06/middleware"
"github.com/liuhongdi/digv06/pkg/result"
"runtime/debug"
)
func Router() *gin.Engine {
router := gin.Default()
//处理异常
router.NoRoute(HandleNotFound)
router.NoMethod(HandleNotFound)
router.Use(middleware.AccessLog())
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()
}
7,main.go
package main
import (
"github.com/gin-gonic/gin"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/liuhongdi/digv06/global"
"github.com/liuhongdi/digv06/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)
}
//access logger
err = global.SetupAccessLogger()
if err != nil {
log.Fatalf("init.SetupAccessLogger 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结束")
//global.Logger.
}
func main() {
global.Logger.Infof("------应用main函数开始")
//设置运行模式
gin.SetMode(global.ServerSetting.RunMode)
//引入路由
r := router.Router()
//run
r.Run(":"+global.ServerSetting.HttpPort)
}
8,其他代码请参见github
四,测试效果
访问url:
http://127.0.0.1:8000/article/getone/1
然后查看日志:
root@ku:/data/gologs/logs# more access_20201216.log
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"
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"
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"
五,查看各个库的版本:
module github.com/liuhongdi/digv06
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
)