小哥之哥 小哥之哥
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小哥之哥

运维扫地僧
首页
    • Prometheus
    • Kubertenes
    • Docker
    • MySQL
  • Go
  • Python
  • Vue
  • Jenkins
  • ELK
  • LDAP
  • 随笔
  • 最佳实践
  • 博客搭建
  • 问题杂谈
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • GO

    • GO语言基础

      • GO语言学习总结系列
      • GO语言HTTP标准库
      • GO语言gin框架总结
      • Gin框架集成Swagger
      • zap日志框架
      • GO语言项目实战

    • 编程
    • GO
    • GO语言基础
    tchua
    2023-03-29
    目录

    zap日志框架

    # 一、概述

    zap (opens new window)是uber开源的一个go语言日志框架,根据官方介绍性能比类似的结构化日志包更好——也比标准库更快。

    # 二、安装

    go get -u go.uber.org/zap
    
    1

    # 三、Zap框架学习

    # 3.1 创建实例

    zap提供两种类型的日志记录器SugaredLogger和Logger,接下来,我们针对这两种日志记录器,进行演示说明。

    注意

    默认情况下日志都会打印到控制台界面

    # 3.1.1 SugaredLogger

    主要用于性能很好但不是很关键的上下文中,他比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
        
    	"go.uber.org/zap"
    )
    
    
    func zapHttpGet(url string) {
        
    	log := zapSugaredLogger()
        // Debug日志格式
    	log.Debugf("开始请求url: %s", url)
    	resp, err := http.Get(url)
    	if err != nil {
    		log.Errorf("url请求失败:%s", err.Error())
    	} else {
    		log.Infof("HTTP Successfully")
    		resp.Body.Close()
    	}
    }
    
    
    func zapSugaredLogger() *zap.SugaredLogger {
    	// 通过Logger实例化sugaredLogger对象
    	logger, err := zap.NewDevelopment()
    	sugaredLogger := logger.Sugar()
    	if err != nil {
    		fmt.Printf("创建zap logger 失败 %s\n", err.Error())
    		return nil
    	}
    	return sugaredLogger
    }
    
    func main() {
    	SetupLogger()
    	zapHttpGet("https://www.baidu.com")
    	zapHttpGet("https://www.g.com")
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    打印结果

    2023-03-29T15:27:12.788+0800    DEBUG   base/main.go:18 开始请求url: https://www.baidu.com
    2023-03-29T15:27:12.850+0800    INFO    base/main.go:23 HTTP Successfully
    2023-03-29T15:27:12.851+0800    DEBUG   base/main.go:18 开始请求url: https://www.g.com
    2023-03-29T15:27:12.856+0800    ERROR   base/main.go:21 地址请求失败:Get "https://www.g.com": dial tcp: lookup www.g.com: no such host
    main.zapHttpGet
            D:/工作/goapps/base/main.go:21
    main.main
            D:/工作/goapps/base/main.go:51
    runtime.main
            C:/Program Files/Go/src/runtime/proc.go:250
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 3.1.2 Logger

    在每一微秒和每一次内存分配都很重要的上下文中,使用 Logger 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录

    Logger 有三种方式创建,不同的方法只是打印的日志信息不一样,我们一一举例说明

    • NewProduction()
    package main
    
    import (
    	"fmt"
    	"go.uber.org/zap"
    	"net/http"
    )
    
    func zapHttpGet(url string) {
    	log := zapLogger()
    	resp, err := http.Get(url)
    	if err != nil {
    		log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
    	} else {
    		log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
    		resp.Body.Close()
    	}
    }
    
    func zapLogger() *zap.Logger {
    	logger, err := zap.NewProduction()
    	if err != nil {
    		fmt.Printf("创建zap logger 失败 %s\n", err.Error())
    		return nil
    	}
    	return logger
    }
    
    func main() {
    	SetupLogger()
    	zapHttpGet("https://www.baidu.com")
    	zapHttpGet("https://www.g.com")
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    打印结果

    {"level":"info","ts":1680075750.81108,"caller":"base/main.go:22","msg":"请求成功","url":"https://www.baidu.com","Status":"200 OK"}
    {"level":"error","ts":1680075750.815755,"caller":"base/main.go:20","msg":"url请求失败:%s","url":"https://www.g.com","error":"Get \"https://www.g.com\": dial tcp: up www.g.com: no such host","stacktrace":"main.zapHttpGet\n\tD:/工作/goapps/base/main.go:20\nmain.main\n\tD:/工作/goapps/base/main.go:50\nruntime.main\n\tC:/Progriles/Go/src/runtime/proc.go:250"}
    
    1
    2
    • NewDevelopment()
    package main
    
    import (
    	"fmt"
    	"go.uber.org/zap"
    	"net/http"
    )
    
    func zapHttpGet(url string) {
    	log := zapLogger()
    	resp, err := http.Get(url)
    	if err != nil {
    		log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
    	} else {
    		log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
    		resp.Body.Close()
    	}
    }
    
    func zapLogger() *zap.Logger {
    	logger, err := zap.NewDevelopment()
    	if err != nil {
    		fmt.Printf("创建zap logger 失败 %s\n", err.Error())
    		return nil
    	}
    	return logger
    }
    
    
    func main() {
    	zapHttpGet("https://www.baidu.com")
    	zapHttpGet("https://www.g.com")
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    打印结果

    2023-03-29T15:44:54.855+0800    INFO    base/main.go:15 请求成功        {"url": "https://www.baidu.com", "Status": "200 OK"}
    2023-03-29T15:44:54.865+0800    ERROR   base/main.go:13 url请求失败:%s  {"url": "https://www.g.com", "error": "Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
    main.zapHttpGet
            D:/工作/goapps/base/main.go:13
    main.main
            D:/工作/goapps/base/main.go:42
    runtime.main
            C:/Program Files/Go/src/runtime/proc.go:250
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    • NewExample()
    package main
    
    import (
    	"go.uber.org/zap"
    	"net/http"
    )
    
    func zapHttpGet(url string) {
    	log := zapLogger()
    	resp, err := http.Get(url)
    	if err != nil {
    		log.Error("url请求失败:%s", zap.String("url", url), zap.Error(err))
    	} else {
    		log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
    		resp.Body.Close()
    	}
    }
    
    func zapLogger() *zap.Logger {
    	logger := zap.NewExample()
    	return logger
    }
    
    
    func main() {
    	zapHttpGet("https://www.baidu.com")
    	zapHttpGet("https://www.g.com")
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    打印结果

    {"level":"info","msg":"请求成功","url":"https://www.baidu.com","Status":"200 OK"}
    {"level":"error","msg":"url请求失败:%s","url":"https://www.g.com","error":"Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
    
    1
    2
    # 3.1.3 总结

    总结

    • SugaredLogger 不但可以基于Logger的.Sugar()创建,还可以直接定义一个全局SugaredLogger直接调用
    • Logger 的三种模式,NewExample和 NewProduction 使用的是json格式输出,NewDevelopment使用行的形式输出。

    # 3.2 自定义日志格式

    上面介绍的几种日志是zap框架帮我们定义好的配置,我们只是实例化后就可以使用,日志也是直接输出到控制台,如果我们想自定义日志格式,并且想把日志写入到日志文件,就需要我们使用自定义的方式,定义Logger。(当然,这也是生产中常用到的模式)

    # 3.2.1 zap.New()

    zap框架中,使用zap.New()方法手动传递自定义日志格式配置。

    • 源码预览

    参数zapcore.Core,是Core的一个接口类型,通过查看源码,可以了解到有一个ioCore的实例实现了Core提供的几个接口,这里我就不去探究源码,因为水平也有限,比较核心的就是Encoder、WriteSyncer、LogLevel三个核心参数。

    func New(core zapcore.Core, options ...Option) *Logger {
    	if core == nil {
    		return NewNop()
    	}
    	log := &Logger{
    		core:        core,
    		errorOutput: zapcore.Lock(os.Stderr),
    		addStack:    zapcore.FatalLevel + 1,
    		clock:       zapcore.DefaultClock,
    	}
    	return log.WithOptions(options...)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 3.2.2 核心参数Encoder

    Encoder编码器(如何写入日志)。这里我们可以直接使用zap包中的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()。

    package main
    
    import (
    	"go.uber.org/zap"
    	"go.uber.org/zap/zapcore"
    )
    
    func main() {
        // 这样是直接使用zap库默认配置
    	zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    }
    
    // 源码NewProductionEncoderConfig函数体 主要控制以下参数 因此我们可以按需修改这些配置
    func NewProductionEncoderConfig() zapcore.EncoderConfig {
    	return zapcore.EncoderConfig{
    		TimeKey:        "ts",
    		LevelKey:       "level",
    		NameKey:        "logger",
    		CallerKey:      "caller",
    		FunctionKey:    zapcore.OmitKey,
    		MessageKey:     "msg",
    		StacktraceKey:  "stacktrace",
    		LineEnding:     zapcore.DefaultLineEnding,
    		EncodeLevel:    zapcore.LowercaseLevelEncoder,
    		EncodeTime:     zapcore.EpochTimeEncoder,
    		EncodeDuration: zapcore.SecondsDurationEncoder,
    		EncodeCaller:   zapcore.ShortCallerEncoder,
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    示例

    // 自定义函数 可以修改zap库中默认的配置,后面调用时,就是我们自定义后的配置
    func getEncoder() zapcore.Encoder {
            
    	//自定义编码配置
    	encoderConfig := zap.NewProductionEncoderConfig()
    	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //修改时间编码器
    	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别
        
    	return zapcore.NewJSONEncoder(encoderConfig)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 3.2.3 核心参数WriteSyncer

    控制日志输出到哪里

    // 指定写入的文件
    file, _ := os.Create("./app.log")
    // 把os.Create的类型转换为所需要的WriteSyncer类型
    writeSyncer := zapcore.AddSync(file)
    
    1
    2
    3
    4

    示例

    func getWriterSyncer() zapcore.WriteSyncer {
    	file, _ := os.Create("./app.log")
        // 如果想要同时输入到文件和控制台 则需要使用io.MultiWriter
    	//ws := io.MultiWriter(file,os.Stdout)
    	return zapcore.AddSync(file)
    }
    
    1
    2
    3
    4
    5
    6
    # 3.2.4 核心参数LevelEnabler

    设置日志级别,日志级别zap库中直接定义好多种级别类型,直接通过zapcore.***Level调用即可

    # 3.2.5 完整示例

    通过上面三个参数的介绍,我们自定义一个完整的demo示例

    package main
    
    import (
    	"net/http"
    	"os"
        
        "go.uber.org/zap"
    	"go.uber.org/zap/zapcore"
    )
    
    func zapHttpGet(url string) {
        // 实例化 logger对象
    	log := initLogger()
    	resp, err := http.Get(url)
    	if err != nil {
    		log.Error("url请求失败:", zap.String("url", url), zap.Error(err))
    	} else {
    		log.Info("请求成功", zap.String("url", url), zap.String("Status", resp.Status))
    		resp.Body.Close()
    	}
    }
    
    func main() {
    	zapHttpGet("https://www.baidu.com")
    	zapHttpGet("https://www.g.com")
    }
    
    // 初始化 Logger 对象
    func initLogger() *zap.Logger {
    	encoder := getEncoder()
    	writerSyncer := getLogWriter()
        // 第一种方式: 同时写入文件可控制台 使用以下配置
        // core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel)
    	core := zapcore.NewCore(encoder, writerSyncer, zapcore.DebugLevel)
    	
        // AddCaller 日志显示函数调用
    	logger := zap.New(core, zap.AddCaller())
        // 如果使用SugaredLogger 则可以打开,也可以在调用initLogger函数的时候,再次调用.Sugar()
    	//SugaredLogger = logger.Sugar() 
    	return logger
    }
    
    // getEncoder 使用zap库默认字段
    // NewJSONEncoder json格式 NewConsoleEncoder文本格式
    func getEncoder() zapcore.Encoder {
    	encoderConfig := zap.NewProductionEncoderConfig()
        // 时间格式化
    	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
        // 日志级别转大写
    	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
    	return zapcore.NewConsoleEncoder(encoderConfig)
    	//return zapcore.NewJSONEncoder(zap.encoderConfig())
    }
    
    // getLogWriter 定义日志文件
    func getLogWriter() zapcore.WriteSyncer {
    	file, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
        // 第二种方式: 同时写入文件可控制台 则需要使用io.MultiWriter
    	//ws := io.MultiWriter(file,os.Stdout)
    	return zapcore.AddSync(file)
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62

    日志文件内容

    2023-03-29T17:10:31.783+0800	INFO	base/main.go:22	请求成功	{"url": "https://www.baidu.com", "Status": "200 OK"}
    2023-03-29T17:10:31.796+0800	ERROR	base/main.go:23	url请求失败:	{"url": "https://www.g.com", "error": "Get \"https://www.g.com\": dial tcp: lookup www.g.com: no such host"}
    
    
    1
    2
    3

    # 四、日志切割

    日志切割这里使用另一个库Lumberjack,因为zap库默认不支持日志切割

    # 4.1 安装

    gopkg.in/natefinch/lumberjack.v2
    
    1

    # 4.2 集成至zap

    直接修改getWriterSyncer

    // getLogWriter 定义日志文件
    func getLogWriter() zapcore.WriteSyncer {
    	//file, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
    	// 如果想要同时输入到文件和控制台 则需要使用io.MultiWriter
    	//ws := io.MultiWriter(file,os.Stdout)
    	lumberWriteSyncer := &lumberjack.Logger{
    		Filename:   "./app.log",
    		MaxSize:    50,
    		MaxAge:     7,
    		MaxBackups: 10,
    		LocalTime:  false,
    		Compress:   false,
    	}
    	return zapcore.AddSync(lumberWriteSyncer)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    编辑 (opens new window)
    #Go
    上次更新: 2023/07/03, 15:10:18
    Gin框架集成Swagger
    Prometheus客户端Exporter开发

    ← Gin框架集成Swagger Prometheus客户端Exporter开发→

    最近更新
    01
    cert-manager自动签发Lets Encrypt
    09-05
    02
    Docker构建多架构镜像
    08-02
    03
    Prometheus数据迁移至VMstorage
    08-01
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 |豫ICP备2021026650号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式