日志是程序开发中必不可少的模块,同时也是日常运维定位故障的最重要环节之一。一般日志类的操作包括日志采集,日志查询,日志监控、日志统计等等。本文,我们将介绍日志模块在Gin中的使用。

Golang如何打印日志

  • 日志打印需要满足几个条件
  1. 重定向到日志文件
  2. 区分日志级别,一般有DEBUG,INFO,WARNING,ERROR,CRITICAL
  3. 日志分割,按照日期分割或者按照大小分割
  • Golang中使用logrus打印日志
var LevelMap = map[string]logrus.Level{
"DEBUG": logrus.DebugLevel,
"ERROR": logrus.ErrorLevel,
"WARN": logrus.WarnLevel,
"INFO": logrus.InfoLevel,
} // 创建 @filePth: 如果路径不存在会创建 @fileName: 如果存在会被覆盖 @std: os.stdout/stderr 标准输出和错误输出
func New(filePath string, fileName string, level string, std io.Writer, count uint) (*logrus.Logger, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
if err := os.MkdirAll(filePath, 755); err != nil {
return nil, err
}
}
fn := path.Join(filePath, fileName) logger := logrus.New()
//timeFormatter := &logrus.TextFormatter{
// FullTimestamp: true,
// TimestampFormat: "2006-01-02 15:04:05.999999999",
//}
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05.999999999",
}) // 设置日志格式为json格式 if logLevel, ok := LevelMap[level]; !ok {
return nil, errors.New("log level not found")
} else {
logger.SetLevel(logLevel)
} //logger.SetFormatter(timeFormatter) /* 根据文件大小分割日志
// import "gopkg.in/natefinch/lumberjack.v2"
logger := &lumberjack.Logger{
// 日志输出文件路径
Filename: "D:\\test_go.log",
// 日志文件最大 size, 单位是 MB
MaxSize: 500, // megabytes
// 最大过期日志保留的个数
MaxBackups: 3,
// 保留过期文件的最大时间间隔,单位是天
MaxAge: 28, //days
// 是否需要压缩滚动日志, 使用的 gzip 压缩
Compress: true, // disabled by default
}
*/
if 0 == count {
count = 90 // 0的话则是默认保留90天
}
logFd, err := rotatelogs.New(
fn+".%Y-%m-%d",
// rotatelogs.WithLinkName(fn),
//rotatelogs.WithMaxAge(time.Duration(24*count)*time.Hour),
rotatelogs.WithRotationTime(time.Duration(24)*time.Hour),
rotatelogs.WithRotationCount(count),
)
if err != nil {
return nil, err
}
defer func() {
_ = logFd.Close() // don't need handle error
}() if nil != std {
logger.SetOutput(io.MultiWriter(logFd, std)) // 设置日志输出
} else {
logger.SetOutput(logFd) // 设置日志输出
}
// logger.SetReportCaller(true) // 测试环境可以开启,生产环境不能开,会增加很大开销
return logger, nil
}

Gin中间件介绍

Gin中间件的是Gin处理Http请求的一个模块或步骤,也可以理解为Http拦截器。

我们将Http请求拆分为四个步骤

1、服务器接到客户端的Http请求

2、服务器解析Http请求进入到路由转发系统

3、服务器根据实际路由执行操作并得到结果

4、服务器返回结果给客户端

Gin中间件的执行包括2个部分(first和last),分布对应的就是在步骤1-2之间(first)和3-4之间(last)的操作。常见的Gin中间件包括日志、鉴权、链路跟踪、异常捕捉等等

  • 默认中间件
router := gin.Default()

查看源码可以看到

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery()) // 包含 Logger、Recovery 中间件
return engine
}
  • 自定义中间件方式1
func Middleware1(c *gin.Context)  {
... // do something first
c.Next() // 继续执行后续的中间件
// c.Abort() 不再执行后面的中间件
... // do something last
}
  • 自定义中间件方式2
func Middleware2()  gin.HandlerFunc {
return func(c *gin.Context) {
... // do something first
c.Next() // 继续执行后续的中间件
// c.Abort() 不再执行后面的中间件
... // do something last
}
}
  • 全局使用中间件
route := gin.Default()
route.Use(Middleware1)
route.Use(Middleware2())
  • 指定路由使用中间件
route := gin.Default()
route.Get("/test", Middleware1)
route.POST("/test", Middleware2())
  • 多个中间件执行顺序

Gin里面多个中间件的执行顺序是按照调用次序来执行的。

无论在全局使用还是指定路由使用,Gin都支持多个中间件顺序执行

Gin中间件之日志模块

  • 模块代码
type BodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
} func (w BodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func (w BodyLogWriter) WriteString(s string) (int, error) {
w.body.WriteString(s)
return w.ResponseWriter.WriteString(s)
} var SnowWorker, _ = uuid.NewSnowWorker(100) // 随机生成一个uuid,100是节点的值(随便给一个) // 打印日志
func Logger() gin.HandlerFunc {
accessLog, _ := mylog.New(
configure.GinConfigValue.AccessLog.Path, configure.GinConfigValue.AccessLog.Name,
configure.GinConfigValue.AccessLog.Level, nil, configure.GinConfigValue.AccessLog.Count)
detailLog, _ := mylog.New(
configure.GinConfigValue.DetailLog.Path, configure.GinConfigValue.DetailLog.Name,
configure.GinConfigValue.DetailLog.Level, nil, configure.GinConfigValue.DetailLog.Count)
return func(c *gin.Context) {
var buf bytes.Buffer
tee := io.TeeReader(c.Request.Body, &buf)
requestBody, _ := ioutil.ReadAll(tee)
c.Request.Body = ioutil.NopCloser(&buf) user := c.Writer.Header().Get("X-Request-User")
bodyLogWriter := &BodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = bodyLogWriter start := time.Now() c.Next() responseBody := bodyLogWriter.body.Bytes()
response := route_response.Response{}
if len(responseBody) > 0 {
_ = json.Unmarshal(responseBody, &response)
}
end := time.Now()
responseTime := float64(end.Sub(start).Nanoseconds()) / 1000000.0 // 纳秒转毫秒才能保留小数
logField := map[string]interface{}{
"user": user,
"uri": c.Request.URL.Path,
"start_timestamp": start.Format("2006-01-02 15:04:05"),
"end_timestamp": end.Format("2006-01-02 15:04:05"),
"server_name": c.Request.Host,
"server_addr": fmt.Sprintf("%s:%d", configure.GinConfigValue.ApiServer.Host,
configure.GinConfigValue.ApiServer.Port), // 无法动态读取
"remote_addr": c.ClientIP(),
"proto": c.Request.Proto,
"referer": c.Request.Referer(),
"request_method": c.Request.Method,
"response_time": fmt.Sprintf("%.3f", responseTime), // 毫秒
"content_type": c.Request.Header.Get("Content-Type"),
"status": c.Writer.Status(),
"user_agent": c.Request.UserAgent(),
"trace_id": SnowWorker.GetId(),
}
accessLog.WithFields(logField).Info("Request Finished")
detailLog.WithFields(logField).Info(c.Request.URL)
detailLog.WithFields(logField).Info(string(requestBody)) // 不能打印GET请求参数
if response.Code != configure.RequestSuccess {
detailLog.WithFields(logField).Errorf("code=%d, message=%s", response.Code, response.Message)
} else {
detailLog.WithFields(logField).Infof("total=%d, page_size=%d, page=%d, size=%d",
response.Data.Total, response.Data.PageSize, response.Data.Page, response.Data.Size)
}
}
}
  • 启用全局日志中间件
route := gin.New()  // 不用默认的日志中间件
route.Use(route_middleware.Logger())

异步打印日志

由于我们的日志中间件使用的是全局中间件,在高并发处理请求时日志落地会导致大量的IO操作,这些操作会拖慢整个服务器,所以我们需要使用异步打印日志

  • 异步函数
var logChannel = make(chan map[string]interface{}, 300)

func logHandlerFunc() {
accessLog, _ := mylog.New(
configure.GinConfigValue.AccessLog.Path, configure.GinConfigValue.AccessLog.Name,
configure.GinConfigValue.AccessLog.Level, nil, configure.GinConfigValue.AccessLog.Count)
detailLog, _ := mylog.New(
configure.GinConfigValue.DetailLog.Path, configure.GinConfigValue.DetailLog.Name,
configure.GinConfigValue.DetailLog.Level, nil, configure.GinConfigValue.DetailLog.Count)
for logField := range logChannel {
var (
msgStr string
levelStr string
detailStr string
)
if msg, ok := logField["msg"]; ok {
msgStr = msg.(string)
delete(logField, "msg")
}
if level, ok := logField["level"]; ok {
levelStr = level.(string)
delete(logField, "level")
}
if detail, ok := logField["detail"]; ok {
detailStr = detail.(string)
delete(logField, "detail")
}
accessLog.WithFields(logField).Info("Request Finished")
if "info" == levelStr {
detailLog.WithFields(logField).Info(detailStr)
detailLog.WithFields(logField).Info(msgStr)
} else {
detailLog.WithFields(logField).Error(detailStr)
detailLog.WithFields(logField).Error(msgStr)
}
}
}
  • 调用方法
go logHandlerFunc()
... // 省略
logChannel <- logField

至此,我们完成了Gin中间件的介绍和日志模块的设计,接下来,我们将使用更多的中间件,完善我们的Api服务。

Github 代码

请访问 Gin-IPs 或者搜索 Gin-IPs

【Gin-API系列】Gin中间件之日志模块(四)的更多相关文章

  1. Gin框架系列03:换个姿势理解中间件

    什么是中间件 中间件,英译middleware,顾名思义,放在中间的物件,那么放在谁中间呢?本来,客户端可以直接请求到服务端接口. 现在,中间件横插一脚,它能在请求到达接口之前拦截请求,做一些特殊处理 ...

  2. Gin框架系列01:极速上手

    Gin是什么? Gin是Go语言编写的web框架,具备中间件.崩溃处理.JSON验证.内置渲染等多种功能. 准备工作 本系列演示所有代码都在Github中,感兴趣的同学可以自行查阅,欢迎大家一起完善. ...

  3. openresty开发系列36--openresty执行流程之6日志模块处理阶段

    openresty开发系列36--openresty执行流程之6日志模块处理阶段 一)header_filter_by_lua 语法:header_filter_by_lua <lua-scri ...

  4. gin中如何自定义中间件

    package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 新建一个没有 ...

  5. 【腾讯Bugly干货分享】微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ff5932cde42f1f03de29b1 本文来源: 微信客户端开发团队 ...

  6. Gin实战:Gin+Mysql简单的Restful风格的API(二)

    上一篇介绍了Gin+Mysql简单的Restful风格的API,但代码放在一个文件中,还不属于restful风格,接下来将进行进一步的封装. 目录结构 ☁ gin_restful2 tree . ├─ ...

  7. go系列(6)- beego日志模块的使用

    1.安装日志模块 切换到工作目录,下载日志模块 cd /data/work/go/ go get github.com/astaxie/beego/logs 2.导入日志模块 使用的时候,需要导入模块 ...

  8. Mysql 系列 | 日志模块

    了解了 SQL 执行的流程,知道每一条语句都经过连接器.查询存储.分析器.优化器.执行器最后到存储引擎的过程.查询语句是如此,更新语句也不例外. 不同的是,更新语句会修改表数据,这里就涉及到两个重要的 ...

  9. python基础系列教程——Python3.x标准模块库目录

    python基础系列教程——Python3.x标准模块库目录 文本 string:通用字符串操作 re:正则表达式操作 difflib:差异计算工具 textwrap:文本填充 unicodedata ...

随机推荐

  1. 如何在Python对Excel进行读取

    在python自动化中,经常会遇到对数据文件的操作,比如添加多名员工,但是直接将员工数据写在python文件中,不但工作量大,要是以后再次遇到类似批量数据操作还会写在python文件中吗? 应对这一问 ...

  2. windows docker xshell 默认登录密码

    boot2docker用户和密码 用户 密码 进入方式 docker tcuser ssh root   command:sudo -i (docker用户下执行)

  3. 图片懒加载、ajax异步调用数据、lazyload插件的使用

    关于这个效果还是很简单的,样式部分我就不多说了,我就简单的写了一下布局, 这是css样式 我们先说一下实现的原理. 我们都知道在于图片的引入,我们都是用src来引入图片地址.从而实现图片的显示.那我们 ...

  4. PHPSTORM常用插件

    Translation 最好用的翻译插件 .env files support 可以在env函数使用是提示.env文件中所有的key值的自动完成功能 PHP composer.json support ...

  5. Redis在Linux下的安装

    一.下载地址 ①redis中文网下载地址:http://www.redis.cn/ ②百度云网盘下载地址:https://pan.baidu.com/s/1UQcF9V3lwA0fxquM_JFMZw ...

  6. 05_MySQL数据库

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"软件测试"获取视频和教程资料! b站在线视频 软件测试 ...

  7. 图解HTTP 6/11

    第三章 HTTP报文内的HTTP信息 1.用于http协议交互的信息被称为HTTP报文.请求端(客户端)的HTTP报文叫做请求报文,响应端(服务器端 )的叫做响应报文. 2.请求报文的结构 请求行:包 ...

  8. KNN算法基本原理与sklearn实现

    ''' KNN 近邻算法,有监督学习算法 用于分类和回归 思路: 1.在样本空间中查找 k 个最相似或者距离最近的样本 2.根据这 k 个最相似的样本对未知样本进行分类 步骤: 1.对数据进行预处理 ...

  9. 汇编语言从键盘输入一个字符串(串长不大于80)以十进制输出字符串中非字母字符的个数(不是a to z或 A to Z)

    (1)从键盘输入一个字符串(串长不大于80). (2)以十进制输出字符串中非字母字符的个数(不是a to z或 A to Z). (3)输出原字符串且令非字母字符闪烁显示. (4)找出字符串中ASCI ...

  10. Python os.fpathconf() 方法

    概述 os.fpathconf() 方法用于返回一个打开的文件的系统配置信息.高佣联盟 www.cgewang.com Unix上可用. 语法 fpathconf()方法语法格式如下: os.fpat ...