开启掘金成长之旅!这是我参与「掘金日新计划 · 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请求和响应,这些都在中间件里进行记录。后边介绍完中间件时候再进行日志中间件讲解。