GO語言框架快速集成日志模塊的操作方法
在我們的日常開發(fā)中, 日志模塊永遠(yuǎn)是最基礎(chǔ)且最重要的一個模塊, 它可以有效的幫我們發(fā)現(xiàn)問題, 定位問題, 最后去解決問題;
zap包的集成
簡介
zap是一個可以在go項(xiàng)目中進(jìn)行快速, 結(jié)構(gòu)化且分級的日志記錄包, git star數(shù)高達(dá)16.3k, Git 項(xiàng)目地址, 在各大公司項(xiàng)目中被廣泛使用;
最基礎(chǔ)的使用
package main
import (
"go.uber.org/zap"
"time"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info(" log info msg",
zap.String("name", "掘金"),
zap.Int("num", 3),
zap.Duration("timer", time.Minute),
)
}{"level":"info","ts":1657600159.826612,"caller":"log/main.go:11","msg":" log info msg","name":"腳本","num":3,"timer":60}
{"level":"warn","ts":1657600159.8266969,"caller":"log/main.go:16","msg":" this is err msg","msg":"code err"}可以看到上面就是打印出來的log, 當(dāng)然這是用的默認(rèn)配置, 所以格式和輸出可能不太符合我們的要求, 我們可以自己修改配置來完成定制化log;
定制化
// NewProduction builds a sensible production Logger that writes InfoLevel and
// above logs to standard error as JSON.
//
// It's a shortcut for NewProductionConfig().Build(...Option).
func NewProduction(options ...Option) (*Logger, error) {
return NewProductionConfig().Build(options...)
}可以看到生成log的方法其實(shí)就是用 config 和 build 來構(gòu)造一個記錄器, 我們試試自定義一下;
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"time"
)
func main() {
conf := zap.NewProductionConfig()
// 可以把輸出方式改為控制臺編碼, 更容易閱讀
conf.Encoding = "console"
// 時間格式自定義
conf.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]")
}
// 打印路徑自定義
conf.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString("[" + caller.TrimmedPath() + "]")
}
// 級別顯示自定義
conf.EncoderConfig.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString("[" + level.String() + "]")
}
logger, _ := conf.Build()
logger.Info("service start")
logger.Info("info msg",
zap.String("name", "掘金"),
zap.Int("num", 3),
zap.Duration("timer", time.Minute),
)
}[2022-07-12 14:57:18][info] [log/main.go:28] service start
[2022-07-12 14:57:18][info] [log/main.go:30] info msg {"name": "掘金", "num": 3, "timer": 60}這種日志一般大家看起來就比較舒服了, 特別是打印json結(jié)構(gòu)的話可以直接復(fù)制解析器里面去看, 沒有json編碼的各種轉(zhuǎn)義;
zap包還是很靈活的, 基本上配置參數(shù)都支持自定義(輸出鍵名, 格式等等), 大家可以按需配置使用;
// An EncoderConfig allows users to configure the concrete encoders supplied by
// zapcore.
type EncoderConfig struct {
// Set the keys used for each log entry. If any key is empty, that portion
// of the entry is omitted.
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKeystring `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKeystring `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// Configure the primitive representations of common complex types. For
// example, some users may want all time.Times serialized as floating-point
// seconds since epoch, while others may prefer ISO8601 strings.
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCallerCallerEncoder`json:"callerEncoder" yaml:"callerEncoder"`
// Unlike the other primitive type encoders, EncodeName is optional. The
// zero value falls back to FullNameEncoder.
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// Configures the field separator used by the console encoder. Defaults
// to tab.
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}進(jìn)階封裝
項(xiàng)目里面生產(chǎn)環(huán)境為了方便收集日志可能都比較喜歡用json, 開發(fā)環(huán)境中大家又都比較喜歡console方便調(diào)試, 還有比如希望接口/服務(wù)日志帶上屬于自己的唯一請求標(biāo)識這種, 以及對日志進(jìn)行分類保存等等, 就需要對zap包進(jìn)行再一次的封裝;
下面是我自己封裝的log包代碼, 可以很方便的解決上面的問題, 大家可以當(dāng)做參考;
index.go記錄器初始化, 根據(jù)配置選擇編碼輸出方式及日志文件保存邏輯, 以及一些自己自定義的輸出標(biāo)準(zhǔn);
package logging
import (
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"path"
"strings"
)
type (
Conf struct {
Path string // 日志路徑
Encoder string // 編碼器選擇
}
logItem struct {
FileName string
Level zap.LevelEnablerFunc
}
Encoder interface {
Config() zapcore.Encoder
WithKey(key string) Encoder
WithField(key, val string) Encoder
Debug(msg string)
Debugf(format string, v ...interface{})
Info(msg string)
Infof(format string, v ...interface{})
Warn(msg string)
Warnf(format string, v ...interface{})
Error(msg string)
Errorf(format string, v ...interface{})
Fatal(msg string)
Fatalf(format string, v ...interface{})
}
)
var (
maxSize = 200 // 每個日志文件最大尺寸200M
maxBackups = 20 // 日志文件最多保存20個備份
maxAge = 30 // 保留最大天數(shù)
_logger *zap.Logger
_pool= buffer.NewPool()
c Conf
ConsoleEncoder = "console" // 控制臺輸出
JsonEncoder = "json" // json輸出
)
// Init 初始化日志.
func Init(conf Conf) {
c = conf
prefix, suffix := getFileSuffixPrefix(c.Path)
infoPath := path.Join(prefix + ".info" + suffix)
errPath := path.Join(prefix + ".err" + suffix)
items := []logItem{
{
FileName: infoPath,
Level: func(level zapcore.Level) bool {
return level <= zap.InfoLevel
},
},
{
FileName: errPath,
Level: func(level zapcore.Level) bool {
return level > zap.InfoLevel
},
},
}
NewLogger(items)
}
// NewLogger 日志.
func NewLogger(items []logItem) {
var (
cfgzapcore.Encoder
cores []zapcore.Core
)
switch c.Encoder {
case JsonEncoder:
cfg = NewJsonLog().Config()
case ConsoleEncoder:
cfg = NewConsoleLog().Config()
default:
cfg = NewConsoleLog().Config()
}
for _, v := range items {
hook := lumberjack.Logger{
Filename:v.FileName,
MaxSize: maxSize, // 每個日志文件保存的最大尺寸 單位:M
MaxBackups: maxBackups, // 日志文件最多保存多少個備份
MaxAge: maxAge, // 文件最多保存多少天
Compress:true, // 是否壓縮
LocalTime: true, // 備份文件名本地/UTC時間
}
core := zapcore.NewCore(
cfg, // 編碼器配置;
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // 打印到控制臺和文件
v.Level, // 日志級別
)
cores = append(cores, core)
}
// 開啟開發(fā)模式,堆棧跟蹤
caller := zap.AddCaller()
// 開發(fā)模式
development := zap.Development()
// 二次封裝
skip := zap.AddCallerSkip(1)
// 構(gòu)造日志
_logger = zap.New(zapcore.NewTee(cores...), caller, development, skip)
return
}
// GetEncoder 獲取自定義編碼器.
func GetEncoder() Encoder {
switch c.Encoder {
case JsonEncoder:
return NewJsonLog()
case ConsoleEncoder:
return NewConsoleLog()
default:
return NewConsoleLog()
}
}
// GetLogger 獲取日志記錄器.
func GetLogger() *zap.Logger {
return _logger
}
// getFileSuffixPrefix 文件路徑切割
func getFileSuffixPrefix(fileName string) (prefix, suffix string) {
paths, _ := path.Split(fileName)
base := path.Base(fileName)
suffix = path.Ext(fileName)
prefix = strings.TrimSuffix(base, suffix)
prefix = path.Join(paths, prefix)
return
}
// getFilePath 自定義獲取文件路徑.
func getFilePath(ec zapcore.EntryCaller) string {
if !ec.Defined {
return "undefined"
}
buf := _pool.Get()
buf.AppendString(ec.Function)
buf.AppendByte(':')
buf.AppendInt(int64(ec.Line))
caller := buf.String()
buf.Free()
return caller
}
console.go控制臺編碼輸出, 支持自定義;
package logging
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"time"
)
type ConsoleLog struct {
val string
}
func NewConsoleLog() Encoder {
return new(ConsoleLog)
}
// Config 自定義配置.
func (slf *ConsoleLog) Config() zapcore.Encoder {
var (
cfg = zap.NewProductionEncoderConfig()
)
// 時間格式自定義
cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + t.Format("2006-01-02 15:04:05") + "]")
}
// 打印路徑自定義
cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString("[" + getFilePath(caller) + "]")
}
// 級別顯示自定義
cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString("[" + level.String() + "]")
}
return zapcore.NewConsoleEncoder(cfg)
}
// WithKey 添加單個鍵.
func (slf *ConsoleLog) WithKey(key string) Encoder {
slf.val = slf.val + "[" + key + "] "
return slf
}
// WithField 添加字段.
func (slf *ConsoleLog) WithField(key, val string) Encoder {
slf.val = slf.val + fmt.Sprintf("[%s:%s] ", key, val)
return slf
}
func (slf *ConsoleLog) Debug(msg string) {
_logger.Debug(slf.val + msg)
}
func (slf *ConsoleLog) Debugf(format string, v ...interface{}) {
_logger.Debug(fmt.Sprintf(slf.val+format, v...))
}
func (slf *ConsoleLog) Info(msg string) {
_logger.Info(slf.val + msg)
}
func (slf *ConsoleLog) Infof(format string, v ...interface{}) {
_logger.Info(fmt.Sprintf(slf.val+format, v...))
}
func (slf *ConsoleLog) Warn(msg string) {
_logger.Warn(slf.val + msg)
}
func (slf *ConsoleLog) Warnf(format string, v ...interface{}) {
_logger.Warn(fmt.Sprintf(slf.val+format, v...))
}
func (slf *ConsoleLog) Error(msg string) {
_logger.Error(slf.val + msg)
}
func (slf *ConsoleLog) Errorf(format string, v ...interface{}) {
_logger.Error(fmt.Sprintf(slf.val+format, v...))
}
func (slf *ConsoleLog) Fatal(msg string) {
_logger.Fatal(slf.val + msg)
}
func (slf *ConsoleLog) Fatalf(format string, v ...interface{}) {
_logger.Fatal(fmt.Sprintf(slf.val+format, v...))
}
json.gojson編碼輸出, 支持自定義;
package logging
import (
"fmt"
"go.uber.org/zap/zapcore"
"time"
"go.uber.org/zap"
)
type JsonLog struct {
fields []zap.Field
val string
}
// NewJsonLog 自定義添加log field.
func NewJsonLog() Encoder {
return &JsonLog{fields: make([]zap.Field, 0)}
}
// Config 自定義配置.
func (slf *JsonLog) Config() zapcore.Encoder {
var (
cfg = zap.NewProductionEncoderConfig()
)
// 時間格式自定義
cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
// 打印路徑自定義
cfg.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(getFilePath(caller))
}
// 級別顯示自定義
cfg.EncodeLevel = func(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(level.String())
}
return zapcore.NewJSONEncoder(cfg)
}
// WithKey 添加單個鍵.
func (slf *JsonLog) WithKey(key string) Encoder {
slf.val = slf.val + key + " "
return slf
}
// WithField 添加字段.
func (slf *JsonLog) WithField(key, val string) Encoder {
slf.fields = append(slf.fields, zap.String(key, val))
return slf
}
func (slf *JsonLog) Debug(msg string) {
_logger.Debug(slf.val+msg, slf.fields...)
}
func (slf *JsonLog) Debugf(format string, v ...interface{}) {
_logger.Debug(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
func (slf *JsonLog) Info(msg string) {
_logger.Info(slf.val+msg, slf.fields...)
}
func (slf *JsonLog) Infof(format string, v ...interface{}) {
_logger.Info(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
func (slf *JsonLog) Warn(msg string) {
_logger.Warn(slf.val+msg, slf.fields...)
}
func (slf *JsonLog) Warnf(format string, v ...interface{}) {
_logger.Warn(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
func (slf *JsonLog) Error(msg string) {
_logger.Error(slf.val+msg, slf.fields...)
}
func (slf *JsonLog) Errorf(format string, v ...interface{}) {
_logger.Error(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
func (slf *JsonLog) Fatal(msg string) {
_logger.Fatal(slf.val+msg, slf.fields...)
}
func (slf *JsonLog) Fatalf(format string, v ...interface{}) {
_logger.Fatal(fmt.Sprintf(slf.val+format, v...), slf.fields...)
}
service.go標(biāo)準(zhǔn)輸出方法, 方便直接調(diào)用;
package logging
import (
"fmt"
)
func Sync() {
_ = _logger.Sync()
}
func Debug(msg string) {
_logger.Debug(msg)
}
func Debugf(format string, v ...interface{}) {
_logger.Debug(fmt.Sprintf(format, v...))
}
func Info(msg string) {
_logger.Info(msg)
}
func Infof(format string, v ...interface{}) {
_logger.Info(fmt.Sprintf(format, v...))
}
func Warn(msg string) {
_logger.Warn(msg)
}
func Warnf(format string, v ...interface{}) {
_logger.Warn(fmt.Sprintf(format, v...))
}
func Error(msg string) {
_logger.Error(msg)
}
func Errorf(format string, v ...interface{}) {
_logger.Error(fmt.Sprintf(format, v...))
}
func Fatal(msg string) {
_logger.Fatal(msg)
}
func Fatalf(format string, v ...interface{}) {
_logger.Fatal(fmt.Sprintf(format, v...))
}上面的進(jìn)階代碼摘自我開發(fā)的一個git項(xiàng)目中, 主要是一個Go的標(biāo)準(zhǔn)項(xiàng)目布局, 封裝了一些常用的組件, 有興趣的朋友可以了解一下, 對新手還是很友好的;
到此這篇關(guān)于GO語言框架快速集成日志模塊的操作方法的文章就介紹到這了,更多相關(guān)go語言日志模塊內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非maisonbaluchon.cn所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。
關(guān)注官方微信