亲手写一个gin server项目-3日志

165 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

本系列文章旨在从头到尾写一个admin的gin server项目。

整个项目包括:项目结构,项目启动,配置文件,中间件,日志,格式化响应,表单校验,检索,jwt会话,rbac鉴权,单元测试,文档生成,基于es的单体日志采集,基于prometheus的指标监控......。

所涉及到的技术栈包括:gin、zap、mysql、gorm、redis、es、prometheus 等。

上一篇文章介绍了获取参数及项目context封装。

今天介绍日志。

日志原理

日志就是一些记录。打印到控制台,存储在本地文本,存储在数据库...

日志的作无非就是事后排查用的,所以打印控制台无法存储,只能在开发阶段用用。

日志想要便捷排查那肯定是存储在数据库中,然后有个对应前台可以进行搜索。

所以现在web日志都是存储在数据库中,在进行查询。

但日志并不是直接从主程把日志写入数据库的,一个请求上来可能有十几条日志,要是放到生产上,算上并发,那日志数据库会被打爆的。

现在的做法都是先把日志输出到本地文本,然后有个采集程序,采集日志,放到队列中。再一条一条发送给日志数据库。

大名鼎鼎的ELK日志采集,就是这种原理。当然那种方案是用在微服务中的,其中的Elasticsearch Logstash kafka可都是分布式的,都需要起码三台主机去实现的,一般单体尤其像我要实现的这种项目根本无法用。

所以我的方案是 Elasticsearch + 本地采集程序,本地采集程,采集了日志放到队列中,然后直接发送给Elasticsearch。Elasticsearch采用单节点方案进行部署,经测试只需要不到0.5G的内存就可以跑起来。

这套方案在后期会专门搞一篇文章进行讲解,并实现。

日志整体方案有了,现在还有个问题,就是日志格式及日志内容。

格式分文本和json,我选用json,因为采集后对json数据好处理。

日志内容就是日志项,要打印什么数据,在项目上线前,最好确定下来,因为Elasticsearch中有个mapping概念,mapping定义了所有项及项的数据类型。在发送正式数据前,最好先发送"数据定义",当然es支持动态数据,你发送一些没有提前定义的数据,它也能存储。但对于咱们来说,超出定义的数据会造成排查的困难。本来知道哪些字段啥意思,突然多出来几个字段,你可能还要翻翻代码才能知道咋回事。

golang日志库 zap

zap是golang中常用的日志框架。我进行了初步封装,连接地址gitee.com/lambdang/pk…

该日志配置定义如下:

type LogxConfig struct {

	//分割配置

	LogName      string `default:"log.log"` // 日志文件路径,默认 os.TempDir()

	MaxSize      int    `default:"10"`      // 每个日志文件保存10M,默认 100M

	MaxBackNum   int    `default:"15" `     // 保留15个备份,默认不限

	MaxAge       int    `default:"7"`       // 保留7天,默认不限

	Compress     bool   `default:"false"`   // 是否压缩,默认不压缩

	Level        string `default:"debug" validate:"one_of=error,debug,panic,info,warn"`

	OutType      string `default:"console" validate:"one_of=console,file,all"` // 输出到哪 console all file

	Formatter    string `default:"txt" validate:"one_of=txt,json"`             //json or txt

	HasTimestamp bool   `default:"false"`

	Caller       bool   `default:"false"` //启用堆栈

	Development  bool   `default:"false"` // 记录行号

}

一般重要的参数有:

  • LogName 日志存储地方
  • Level 日志水平 开发阶段debug,上线info
  • OutType 输出类型控制台(console)还是文本(file),或者都输出(all)
  • Formatter 输出格式json还是txt

整合到项目里

配置文件整合。

type Config struct {
   Host string `default:":9000"`
   Log  logx.LogxConfig //新增加的日志配置文件
}

yaml配置文件。

Host: 0.0.0.0:8000

Log:
  LogName: ./api.log
  Level: debug
  OutType: all
  Formatter: txt

资源context整合。

func NewServerContext(c Config) *ServerContext {
   a := &ServerContext{
      Config: c,
      Src:    "这是测试资源",
   }
   lg, err := logx.NewLogx(c.Log)
   if err != nil {
      panic(err)
   }
   a.Log = lg //日志新添加
   return a
}

handler中使用。

func Handler(svc *ServerContext) gin.HandlerFunc {
    return func(c *gin.Context) {
       svc.Log.Info("请求响应") //打印日志
       c.JSON(200, gin.H{
          "Example": "Hello Gin",
          "src":     svc.Src,
          "Host":    svc.Config.Host,
       })
    }
}

整体代码:

package main

import (
   "flag"
   "fmt"
   "gitee.com/lambdang/pkg/logx"
   "gitee.com/lambdang/pkg/yamlconfig"
   "github.com/gin-gonic/gin"
   "os"
)


var config = flag.String("f", "config.yaml", "配置文件")

type Config struct {
   Host string `default:":9000"`
   Log  logx.LogxConfig
}


func main() {

   flag.Usage = Usage
   flag.Parse()
   var C Config

   if err := yamlconfig.Load(*config, &C); err != nil {
      println(err)
      os.Exit(1)
   }

   if len(os.Args) < 2 {
      flag.Usage()
      os.Exit(1)
   }
   cmder := os.Args[1]
   switch cmder {
   case "run":
      server(C)
   default:
      flag.Usage()
      os.Exit(1)
   }
}

type ServerContext struct {
   Src    string
   Config Config
   Log    *logx.Logx
}

func NewServerContext(c Config) *ServerContext {
   a := &ServerContext{
      Config: c,
      Src:    "这是测试资源",
   }
   lg, err := logx.NewLogx(c.Log)
   if err != nil {
      panic(err)
   }
   a.Log = lg
   return a
}

func server(c Config) {
   r := gin.Default()
   svc := NewServerContext(c)

   r.GET("/", Handler(svc))
   r.Run(C.Host)
}

func Handler(svc *ServerContext) gin.HandlerFunc {
   return func(c *gin.Context) {
      svc.Log.Info("请求响应")
      c.JSON(200, gin.H{
         "Example": "Hello Gin",
         "src":     svc.Src,
         "Host":    svc.Config.Host,
      })
   }
}

func Usage() {
   fmt.Println("http server v1.0")
   fmt.Println("main run -f config.yaml")
   fmt.Println()
   fmt.Println("参数:")
   flag.PrintDefaults()
}

结尾

web日志最主要记录的就是http请求和响应,这些都在中间件里进行记录。后边介绍完中间件时候再进行日志中间件讲解。