ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

构造一个golang logger

2022-06-02 15:02:12  阅读:156  来源: 互联网

标签:... zapcore 构造 golang func logger 日志 zap


一个实用的logger需要提供以下这些功能:

  • 支持把日志写入多个输出流中,比如可以选择性的让测试、开发环境同时向控制台和日志文件输出日志,生产环境只输出到日志文件中
  • 支持多级别的日志等级,常见的有:TRACE、DEBUG、INFO、WARN、ERROR、PANIC等
  • 支持结构化输出,结构化输出常用的就是JSON格式,这样可以让统一日志平台通过logstash之类的组件把日志聚合到日志平台上
  • 需要支持日志切割log rotation
  • 在log entry中除了主动记录的信息外,还要包括如打印日志的函数、所在的文件、行号、记录时间等

 

1. Log日志库

使用log记录日志,默认会输出到控制台,比如下面这个例子:

func main() {
	simpleHTTPGet("www.baidu.com")
	simpleHTTPGet("https://www.baidu.com")
}

func simpleHTTPGet(url string) {
	resp, err := http.Get(url)
	if err != nil {
		log.Printf("Error fetching url %s: %s", url, err.Error())
	} else {
		log.Printf("Status Code for %s: %s", url, resp.Status)
		resp.Body.Close()
	}
	return
}

 

输出信息如下:

2022/05/31 10:40:45 Error fetching url www.baidu.com: Get "www.baidu.com": unsupported protocol scheme ""
2022/05/31 10:40:45 Status Code for https://www.baidu.com: 200 OK

 

go原生的logger也支持把日志输出到指定的文件中,通过log.SetOutput可以把任何io.Writer的实现设置成日志的输出。我们把日志输出到一个指定文件:

func main() {
	setupLogger()
	simpleHTTPGet("www.baidu.com")
	simpleHTTPGet("https://www.baidu.com")
}

func setupLogger() {
	logFileLocation, _ := os.OpenFile("/tmp/test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
	log.SetOutput(logFileLocation)
}

 

原生logger用法非常简单,对于一些简单的开发调试来讲基本是适用的,但是用在项目中存在着以下不足:

  • 仅限基本的日志级别,只有一个Print选项
  • 对于错误日志,有FatalPanic,不支持Error
  • 无结构化能力,只是简单的文本输出
  • 没有日志切割能力

 

2.  Zap日志库

zap是Uber开源的日志库,具备高性能的特性

zap高性能的一大原因是,不用反射。日志里每个要写入的字段都要携带类型

logger.Info(
    "Success...",
    zap.String("statusCode", resp.Status),
    zap.String("url",url))

 

上面向日志里写入了一条记录,Message是"Success...",另外写入了两个字符串键值对。

zap针对日志里要写入的字段,每个类型都有一个对应的方法将字段转成zap.Field类型,比如:

zap.Int('key', 123)
zap.Bool('key', true)
zap.Error('err', err)
zap.Any('arbitraryType', &User{})

 

2.1 zap的简单使用

首先需要引入依赖

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

 

之后我们做一下简单的初始化工作就可以使用zap logger了,其实zap提供了三种初始化方法,我们就使用zap.NewProduction()即可

我们简单修改一下之前的代码,引入zap.logger:

var logger *zap.Logger

func main() {
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("https://www.baidu.com")
}

func simpleHttpGet(url string) {
	resp, err := http.Get(url)
	if err != nil {
		logger.Error("Failed...", zap.String("Error", err.Error()))
	} else {
		logger.Info("Success...", zap.String("StatusCode", resp.Status), zap.String("Url", url))
		resp.Body.Close()
	}
}

func init() {
	logger, _ = zap.NewProduction()
}

 

运行程序,可以在控制台看到更加详细的输出,其中包括了go原生log不支持的一些信息,包括调用栈信息、时间戳、日志等级,json格式化输出

{"level":"error","ts":1654150127.123799,"caller":"logger/main.go:27","msg":"Failed...","Error":"Get \"www.baidu.com\": unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/pangjiping/gopath/src/blog/logger/main.go:27\nmain.main\n\t/Users/pangjiping/gopath/src/blog/logger/main.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"}
{"level":"info","ts":1654150127.293559,"caller":"logger/main.go:30","msg":"Success...","StatusCode":"200 OK","Url":"https://www.baidu.com"}

 

2.2 zap的定制化

对zap做简单定制,让其将日志输出到指定文件,并且将时间戳转为日期的格式

修改init()函数完成logger的初始化工作即可

func init() {
	encoderConfig := zap.NewProductionEncoderConfig()
	// 设置日志记录中时间的格式
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	// 日志encoder还是json encoder,把日志行格式化程json格式的
	encoder := zapcore.NewJSONEncoder(encoderConfig)

	file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	fileWriteSyncer := zapcore.AddSync(file)

	core := zapcore.NewTee(
		// 控制台输出
		zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
		// 文件输出
		zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel),
	)
	logger = zap.New(core)
}

 

现在我们在控制台得到的输出日志为:

{"level":"error","ts":"2022-06-02T14:23:19.639+0800","msg":"Failed...","Error":"Get \"www.baidu.com\": unsupported protocol scheme \"\""}
{"level":"info","ts":"2022-06-02T14:23:19.815+0800","msg":"Success...","StatusCode":"200 OK","Url":"https://www.baidu.com"}

 

2.3 日志切割

zap本身不支持日志切割,可以借助另一个库lumberjack完成日志切割

func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
	// 使用 lumberjack 实现 log rotate
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "/tmp/test.log",
		MaxSize:    100, // 单个文件最大100M
		MaxBackups: 60,  // 多于 60 个日志文件后,清理较旧的日志
		MaxAge:     1,   // 一天一切割
		Compress:   false,
	}

	return zapcore.AddSync(lumberJackLogger)
}

 

2.4 封装

package zlog

import (
	"os"
	"path"
	"runtime"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var logger *zap.Logger

func init() {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoder := zapcore.NewJSONEncoder(encoderConfig)

	fileWriteSyncer := getFileLogWriter()

	core := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel),
		zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel),
	)

	logger = zap.New(core)
}

func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./debug.log",
		MaxSize:    100,
		MaxBackups: 60,
		MaxAge:     1,
		Compress:   false,
	}

	return zapcore.AddSync(lumberJackLogger)
}

func Info(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Info(message, fields...)
}

func Debug(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Debug(message, fields...)
}

func Error(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Error(message, fields...)
}

func Warn(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Warn(message, fields...)
}

func getCallerInfoForLog() (callerFields []zap.Field) {
	pc, file, line, ok := runtime.Caller(2)
	if !ok {
		return
	}

	funcName := runtime.FuncForPC(pc).Name()
	funcName = path.Base(funcName) // 只保留函数名

	callerFields = append(callerFields, zap.String("func", funcName), zap.String("file", file), zap.Int("line", line))
	return
}

 

标签:...,zapcore,构造,golang,func,logger,日志,zap
来源: https://www.cnblogs.com/aganippe/p/16329754.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有