GO语言框架快速集成日志模块的操作方法

目录
  • 前言
  • zap包的集成
    • 简介
    • 最基础的使用
    • 定制化
    • 进阶封装

前言

在我们的日常开发中, 日志模块永远是最基础且最重要的一个模块, 它可以有效的帮我们发现问题, 定位问题, 最后去解决问题;

zap包的集成

简介

zap是一个可以在go项目中进行快速, 结构化且分级的日志记录包, git star数高达16.3k, Git 项目地址, 在各大公司项目中被广泛使用;

最基础的使用

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, 当然这是用的默认配置, 所以格式和输出可能不太符合我们的要求, 我们可以自己修改配置来完成定制化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的方法其实就是用 configbuild 来构造一个记录器, 我们试试自定义一下;

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结构的话可以直接复制解析器里面去看, 没有json编码的各种转义;

zap包还是很灵活的, 基本上配置参数都支持自定义(输出键名, 格式等等), 大家可以按需配置使用;

// 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"`
	LevelKey      string `json:"levelKey" yaml:"levelKey"`
	TimeKey       string `json:"timeKey" yaml:"timeKey"`
	NameKey       string `json:"nameKey" yaml:"nameKey"`
	CallerKey     string `json:"callerKey" yaml:"callerKey"`
	FunctionKey   string `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"`
	EncodeCaller   CallerEncoder   `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"`
}

进阶封装

项目里面生产环境为了方便收集日志可能都比较喜欢用json, 开发环境中大家又都比较喜欢console方便调试, 还有比如希望接口/服务日志带上属于自己的唯一请求标识这种, 以及对日志进行分类保存等等, 就需要对zap包进行再一次的封装;

下面是我自己封装的log包代码, 可以很方便的解决上面的问题, 大家可以当做参考;

index.go 记录器初始化, 根据配置选择编码输出方式及日志文件保存逻辑, 以及一些自己自定义的输出标准;

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  // 保留最大天数
	_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 (
		cfg   zapcore.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)
	}

	// 开启开发模式,堆栈跟踪
	caller := zap.AddCaller()
	// 开发模式
	development := zap.Development()
	// 二次封装
	skip := zap.AddCallerSkip(1)
	// 构造日志
	_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.go json编码输出, 支持自定义;

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 标准输出方法, 方便直接调用;

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...))
}

上面的进阶代码摘自我开发的一个git项目中, 主要是一个Go的标准项目布局, 封装了一些常用的组件, 有兴趣的朋友可以了解一下, 对新手还是很友好的;

到此这篇关于GO语言框架快速集成日志模块的操作方法的文章就介绍到这了,更多相关go语言日志模块内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Django日志模块logging的配置详解

    前言 Django对于日志输出的信息是很完善的,request的信息,setting配置,trackback的信息,一应俱全,足够我们调试了.但是在线上环境,如果让用户看到这些信息,是很不安全的(暴露代码).所以在线上我们要关闭Debug,但是又不能扔掉这些调试信息,这就要用到logging模块. logging模块其实是Python的模块,在Django中有很多本地化的支持. 理解Logger 首先要理解logging的工作,这里面主要有四个东西:格式器formatter,过滤器filter,

  • 详解MongoDB中的日志模块

    今天简单研究了一下MongoDB里面的日志模块,写篇文章记录下. 01 MongoDB日志组件种类及日志等级 每种数据库都有自己的日志模块,MongoDB也不例外,通常情况下,一个数据库的日志中,记录的是数据库的连接信息.存储信息.网络信息.索引信息以及查询信息等.从MongoDB3.0版本开始,MongoDB在日志中引入了日志等级和日志组件的概念,作为DBA来讲,关注的最多的应该是慢查询日志和连接日志. 在MongoDB中,我们可以通过下面的命令,来获取所有的日志组件种类和对应的日志等级: d

  • Django日志及中间件模块应用案例

    基于邮件通知的服务监控和告警系统 主要功能点: 配置专用日志格式记录耗时 日志格式: 'simple':{ 'format':'%(asctimme)s %(message)s' } 处理器 'statistics_handler':{ 'level':'DEBUG', 'class':'logging.handlers.RotatingFileHandler', 'filename':os.path.join(LOG_DIR,'backend.log') 'maxBytes':'1024*10

  • Django实现的自定义访问日志模块示例

    本文实例讲述了Django实现的自定义访问日志模块.分享给大家供大家参考,具体如下: 在Django默认没有访问日志模块,但是我们可以通过Django的Middleware来实现一个自己的访问日志模块. 首先在Django的工程下创建一个middleware.py文件,内容如下: #!/usr/bin/env python # -*- coding: utf-8 -*- import time class AccessMiddleware(object): def process_request

  • GO语言框架快速集成日志模块的操作方法

    目录 前言 zap包的集成 简介 最基础的使用 定制化 进阶封装 前言 在我们的日常开发中, 日志模块永远是最基础且最重要的一个模块, 它可以有效的帮我们发现问题, 定位问题, 最后去解决问题; zap包的集成 简介 zap是一个可以在go项目中进行快速, 结构化且分级的日志记录包, git star数高达16.3k, Git 项目地址, 在各大公司项目中被广泛使用; 最基础的使用 package main import ( "go.uber.org/zap" "time&q

  • Go语言框架快速集成限流中间件详解

    目录 前言 分布式版 简介 算法 实现 注意 单机版 简介 算法 实现 结语 前言 在我们的日常开发中, 常用的中间件有很多, 今天来讲一下怎么集成限流中间件, 它可以很好地用限制并发访问数来保护系统服务, 避免系统服务崩溃, 资源占用过大甚至服务器崩溃进而影响到其他应用! 分布式版 简介 通常我们的服务会同时存在多个进程, 也就是负载来保证服务的性能和稳定性, 那么就需要走一个统一的限流, 这个时候就需要借助我们的老朋友-redis, 来进行分布式限流; 算法 漏桶算法 即一个水桶, 进水(接

  • php的laravel框架快速集成微信登录的方法

    本文面向的是php语言laravel框架的用户,介绍的是基于该框架实现的一个简易集成微信登录的方法.使用方法如下: 1. 安装php_weixin_provider 在项目下运行composer require thirdproviders/weixin,即可完成安装.安装成功后,在项目的vendor目录下应该能看到php_weixin_provider的库文件: 2. 配置微信登录的参数 一共有7个参数可以配置,分别是: client_id:对应公众号创建的应用appid client_sec

  • SpringBoot快速集成Logback日志组件

    目录 前言 引入: 配置: 开发: 结语 前言 在前一节的分享中,慕歌向大家介绍了如何使用spring boot 实现简单的邮寄发送服务,用于验证码服务或者是通知服务.如果大家有兴趣,慕歌还想向大家进一步分享,如何在使用第三方服务,实现手机短信通知服务,就是那个我们每天都会使用到的短信验证码,通知服务.这一节慕歌想带来spring boot日志系统的分享,以及慕歌自己的实现的简易日志记录,慕歌会将日志同时保存在文件和数据库之中. 引入: 如果我们使用 logback 就无需额外引入依赖,在spr

  • Go语言入门Go Web Fiber框架快速了解

    目录 Fiber 创建一个 HelloWorld Fiber 路由 Go Fiber 状态码 Go Fiber 发送短信 Go Fiber headers Go Fiber 发送文件 Go Fiber 教程展示了如何使用 Fiber 框架在 Golang 中创建简单的 Web 应用程序. Fiber是一个简单快速的 Go Web 框架. Fiber 专注于极致性能和低内存占用.它的灵感来自流行的 Express JS 框架. Fiber 创建一个 HelloWorld package main

  • Python使用日志模块快速调试代码并记录异常信息

    目录 一.日志层级 二.创建模块 三.使用日志的优点 大家好,为了进行调试和错误跟踪,人们在整个代码库中广泛使用日志,今天来看看如何在代码中定义日志,并探讨日志的权限. 一.日志层级 在开始之前,需要注意的是,在日志记录中存在一个层次结构,称为日志树或日志者层次结构.该层次结构由几个级别组成,每个级别代表了日志信息的不同严重程度.最常见的层次是: CRITICAL #A critical error occurred, the program may not be able to continu

  • Java Apache Shiro安全框架快速开发详解流程

    目录 一.Shiro简介: shiro功能: Shiro架构(外部) Shiro架构(内部) 二.快速入门 1.拷贝案例 2.分析代码 三.SpringBoot 集成 Shiro 1.编写测试环境 2.使用 1.登录拦截 2.用户认证 四.Shiro整合Mybatis 五.实现请求授权 六.Shiro整合Thymeleaf 一.Shiro简介: Apache Shiro是一个Java的安全(权限)框架. Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在Ja

  • 详解 python logging日志模块

    目录 1.日志简介 2.日志级别 3.修改日志级别 4.日志记录到文件 5.指定日志格式 6.记录器(logger) 7.处理器(Handler) 8.处理器操作 9.格式器(formatter) 10.logging.basicConfig 11.日志配置 转自微信公众号: Python之禅 1.日志简介 说到日志,无论是写框架代码还是业务代码,都离不开日志的记录,他能给我们定位问题带来极大的帮助. 记录日志最简单的方法就是在你想要记录的地方加上一句 print , 我相信无论是新手还是老鸟都

  • 详解 python logging日志模块

    目录 1.日志简介 2.日志级别 3.修改日志级别 4.日志记录到文件 5.指定日志格式 6.记录器(logger) 7.处理器(Handler) 8.处理器操作 9.格式器(formatter) 10.logging.basicConfig 11.日志配置 转自微信公众号: Python之禅 1.日志简介 说到日志,无论是写框架代码还是业务代码,都离不开日志的记录,他能给我们定位问题带来极大的帮助. 记录日志最简单的方法就是在你想要记录的地方加上一句 print , 我相信无论是新手还是老鸟都

  • Android实现基于ZXing快速集成二维码扫描功能

    二维码扫描现在是一直比较多的应用场景,android的开源项目ZXing提供了完整.成熟的解决方案,如果仅仅是出于快速开发的目的,可以根据自己的项目需要,把ZXing官方提供的项目稍加裁剪,就可以快速的集成到自己的项目中.下面详细演示和介绍如何实现基于ZXing官方提供的源码,快速集成二维码扫描功能到自己项目中的解决方案. (第1步):到ZXing官方主页下载最新的项目代码包,ZXing在github的官方主页:https://github.com/zxing,下载后解压.解压后根目录下有若干项

随机推荐