十三.日志包设计

本文探讨了在Go语言项目中设计日志包的重要性,包括提高代码可读性、支持调试、性能分析和安全审计。介绍了如何基于Zap封装日志,以及日志包的基本需求、等级别使用场景和生产环境架构,强调了封装、扩展性和安全性等因素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

十三.日志包设计

一.前言
在 Go 语言项目中自己设计日志包是非常重要的,原因如下:

提高代码可读性和可维护性:良好的日志设计可以让代码更加易读和易于维护。日志可以帮助开发人员理解代码的运行过程,方便调试和错误排查。

支持调试和错误排查:日志可以帮助开发人员跟踪代码的执行路径,从而更容易发现潜在的问题和错误。通过在不同的位置记录不同的日志信息,可以更精确地定位问题所在。

支持性能分析和优化:日志可以记录代码执行的时间和资源使用情况,从而帮助开发人员进行性能分析和优化。例如,可以记录代码中每个函数的执行时间和调用次数,以及内存使用情况等信息。

支持安全审计:日志可以记录系统中的操作行为和事件,从而帮助开发人员进行安全审计和漏洞分析。例如,可以记录用户登录和操作行为等信息,以便跟踪恶意行为或异常情况。

总之,自己设计日志包可以帮助开发人员更好地理解和管理代码,提高代码质量和效率。
二.自己设计log包的重要性
开发,debug,故障排查,数据分析,监控告警,保存现场
我们需要设计一个优秀的日志包,如果我们要扩展就比较麻烦,1.基于zap封装,2.自己实现3.改zap的源码

1.是否可以替换后期我们想要替换成另一个日志框架
2.我们要考虑扩展性,log打印的时候是否支持打印当前的goroutine的id是否支持打印当前的context
3.我们给大家提供的日志包,还能支持集成tracing(open-telemetry, metrics,logging),就可以集成jaeger
4.是否每个日志打印都能知道这个日志是哪个请求的
封装日志包很重要!最好是自己封装

gorm,go-redis、我们自己业务代码
三.日志包的基本需求
3.1. 全局logger和传递参数的logger的用法
1.全局的Logger
全局 logger 的设计思想是在整个应用程序中都可以方便地使用同一个 logger,避免了在不同的代码段中都要创建 logger 的麻烦。这个 logger 通常是在程序启动时初始化,并通过包级别的变量暴露出来,以便其他代码使用。

全局 logger 的优点是简单易用,可以方便地在整个应用程序中记录日志,但缺点是不能很好地控制日志输出的格式、级别和目标。
2.传递参数的logger
传递参数的 logger 的设计思想是通过将 logger 作为参数传递给需要记录日志的函数,让函数可以控制日志的格式、级别和目标。这个 logger 可以是标准库的 log 包中的 logger,也可以是自己定义的 logger。

传递参数的 logger 的优点是可以更灵活地控制日志输出,但缺点是需要在每个函数调用时都传递 logger,代码可能会变得更复杂。
3.2日志包的基本需求

logger最基本的功能

1.日志基本debug、 info、warn、error . fatal、panic
2.打印方式2020-12-02T01:16:18+08:00 INF0 example.go:11 std log json (zap)
3.日志是否支持轮转、单文件不能太大,压缩,切割
4.日志包是否支持hook,gorm
其他的需求:

是否支持颜色显示是否兼容表中的Log
error打印到error文件,info打印到info文件
error能否发送到其他的监控软件,统计一个metrics错误指标error是否能支持发送到jaeger

其他需求:
高性能
并发安全
插件化:错误告警,发邮件 sentry
参数控制

我会使用基于zap封装
3.3日志debug、info、error等级别的使用场景
log使用经验:

1.if分支的时候可以打印日志
2.写操作要尽量写日志 gorm,要记录数据
3.for循环打印日志的时候要慎重,for+上万次
4.错误产生的原始位置打印日志 A(这里打印行不行)->B->C(error,应该在此处打印日志) 这样做比较保险,所有error一律采用记录stack 同时采用fail fast

debug:
我们为了方便排查错误很多时候会在很多地方使用debug,debug往往很多,上了生产如果开启debvug会导致性能受影响,在上线的时候尽量关闭到debug

info:
关键的地方打印一些信息,这些信息数据可以交给大数据进行分析,info量来说相对比较适中。如果你发现了你的info使用量特别大,你就该考虑是不是可以换成debug

warn(警告):
warn往往不会导致一个请求失败,但是我们还是应该关注的一些数据,
比如:服务端页面要求请求1才是第一页,结果客户端传递的是a,这时,我正常返回 但是打印一次warn,如果有大量的warn,这时我们就能知道 应该是一种爬虫行为

error:
这就是程序失败,我们的函数没有做好错误兼容,由于业务运行过程中的bug,请求第三方资源,创建数据库记录,这种错误一定要关注

panic:
panic会导致整个系统直接挂掉,我们一开始项目启动的时候会链接数据库,可以使用panic去结束掉程序,panic是可以被recover住的
有一些情况 比如slice越界 2/0,业务中遇到这种panic你的程序挂了 这就要命了

Fatal:
最高级别错误,当你使用这个方法的时候你心里应该清楚,这个错误不应该被原谅,就应该导致程序挂掉

日志打印的实践经验
写日志的注意事项

日志中不能记录敏感数据,密码、token等
日志打印的时候音量写清楚错误的原因 log.Warnf(“[getDB] init database:%v”,err)
如果可以,每一条日志尽量和请求的id关联起来
info和error不要乱用,很常见 - 要注意
实践

好的日志不可能一开始就设计的很好,这是一个演进的过程,日志打印要重视
日志不是越多越好,越少越好,关键信息要打印
日志要兼容本地打印
能否支持动态调整日志级别(能不能拿到nacos中?)
四.生产环境中的日志系统架构

在这里插入图片描述

五.自定义log包
5.1自定义options
package log

import (
	"fmt"
	"github.com/spf13/pflag"
	"go.uber.org/zap/zapcore"
	"strings"
)

const (
	FORAMT_CONSOLE = "console"
	FORAMT_JSON    = "json"
	OUTPUT_STD     = "stdout"
	OUTPUT_STD_ERR = "stderr"

	flagLevel = "log.level"
)

type Options struct {
	OutputPaths      []string `json:"output-paths" mapstructure:"output-paths"`             //输出文件
	ErrorOutputPaths []string `json:"error-output-paths" mapstructure:"error-output-paths"` //err输出文件
	Level            string   `json:"level" mapstructure:"level"`                           //日志级别
	Format           string   `json:"format" mapstructure:"format"`                         //日志打印格式
	Name             string   `json:"name" mapstructure:"name"`                             //名称
}

type Option func(o *Options)

func NewOptions() *Options {
	return nil
}

func New(opts ...Option) *Options {
	options := &Options{
		Level:            zapcore.InfoLevel.String(),
		Format:           FORAMT_CONSOLE,
		OutputPaths:      []string{OUTPUT_STD},
		ErrorOutputPaths: []string{OUTPUT_STD_ERR},
	}
	for _, opt := range opts {
		opt(options)
	}
	return options
}

func WithLevel(level string) Option {
	return func(o *Options) {
		o.Level = level
	}
}

// Validate 就可以自定义检查规则
func (o *Options) Validate() []error {
	var errs []error
	format := strings.ToLower(o.Format)
	if format != FORAMT_CONSOLE && format != FORAMT_JSON {
		errs = append(errs, fmt.Errorf("not supppor format %s", o.Format))
	}
	return errs
}

// AddFloags 可以自己将options具体的列映射到flog的字段上
func (o *Options) AddFloags(fs pflag.FlagSet) *Options {
	fs.StringVar(&o.Level, flagLevel, o.Level, "log level")
	return o
}

5.2自定义log接口
package log

import (
	"context"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"sync"
)

type Field = zapcore.Field
type Logger interface {
	Debug(msg string)
	DebugC(context context.Context, msg string)
	Debugf(format string, args ...interface{})
	DebugfC(context context.Context, format string, args ...interface{})
	DebugW(msg string, keysAndValues ...interface{})
	DebugWC(context context.Context, msg string, keysAndValues ...interface{})
}

var _ Logger = &zapLogger{}

type zapLogger struct {
	zapLogger *zap.Logger
}

func (z *zapLogger) Debug(msg string) {
	z.zapLogger.Debug(msg)
}

func (z *zapLogger) DebugC(context context.Context, msg string) {
	//TODO implement me
	panic("implement me")
}

func (z *zapLogger) Debugf(format string, args ...interface{}) {
	//TODO implement me
	panic("implement me")
}

func (z *zapLogger) DebugfC(context context.Context, format string, args ...interface{}) {
	//TODO implement me
	panic("implement me")
}

func (z *zapLogger) DebugW(msg string, keysAndValues ...interface{}) {
	//TODO implement me
	panic("implement me")
}

func (z *zapLogger) DebugWC(context context.Context, msg string, keysAndValues ...interface{}) {
	//TODO implement me
	panic("implement me")
}

var (
	defaultLogger = NewLog(NewOptions())
	mu            sync.Mutex
)

func Logs() *zapLogger {
	return defaultLogger
}

func Debug(msg string) {
	defaultLogger.Debug(msg)
}

func NewLog(opts *Options) *zapLogger {
	if opts == nil {
		opts = NewOptions()
	}
	//实例化zap
	var zapLevel zapcore.Level
	if err := zapLevel.UnmarshalText([]byte(opts.Level)); err != nil {
		zapLevel = zapcore.InfoLevel
	}
	loggerConfig := zap.Config{
		Level: zap.NewAtomicLevelAt(zapLevel),
	}
	l, err := loggerConfig.Build(zap.AddStacktrace(zapcore.PanicLevel))
	if err != nil {
		panic(err)
	}
	logger := &zapLogger{
		zapLogger: l.Named(opts.Name),
	}
	return logger
}

func Init(opt *Options) {
	//看起来没有问题,并发问题,因为我们后面可能希望我们这个全局logger是动态的
	mu.Lock()
	defer mu.Unlock()
	defaultLogger = NewLog(opt)
}


//调用日志
package main

import "GoStart/log"

func main() {
	//初始化日志
	log.Init(log.NewOptions())
	log.Debug("hello")
	/*
		我们自己封装了一个options,用于隔开zap.config
		日志初始化,Init(options),
		整个过程中调用法看不到zap的信息,
	*/
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值