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.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
)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老刘你真牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值