十三.日志包设计

十三.日志包设计

一.前言
在 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的信息,
	*/
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/160162.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言二进制数(ZZULIOJ1068:二进制数)

题目描述 将一个二进制数,转换为对应的十进制数。 输入:输入一个只含有’0’和’1’的字符串,以回车结束,表示一个二进制数。该二进制数无符号位,长度不超过31。 输出:输出一个整数,为该二进制数…

实在智能携手中国电信翼支付,全球首款Agent智能体亮相2023数字科技生态大会

11月10日-13日,中国电信与广东省人民政府联合主办的“2023数字科技生态大会”在广州隆重举行。本届大会以“数字科技焕新启航”为主题,邀请众多生态合作伙伴全方位展示数字科技新成果,包括数字新消费、产业数字化、智能电子、人工智能大模型等…

阿里云服务器 手动搭建WordPress(CentOS 8)

前提条件 已创建Linux操作系统的ECS实例,并且手动部署LNMP环境,具体操作,请参见手动部署LNMP环境(CentOS 8)。本教程使用的相关资源版本如下。 实例规格:ecs.c6.large 操作系统:公共镜像CentO…

【C++初阶】内存管理 初识模板

目录 一、C/C内存分布二、C/C动态内存管理方式2.1 new和delete的用法2.2 new与malloc、delete与free比较2.3 较复杂场景分析 三、operator new与operator delete函数四、 new和delete的实现原理五、初识模板5.1 泛型编程5.2 函数模板5.2.1 概念5.2.2 写法5.2.3 不同类型时使用函…

excel怎么能锁住行 和/或 列的自增长,保证粘贴公式的时候不自增长或者只有部分自增长

例如在C4单元格中输入了公式: 现在如果把C4拷贝到C5,D3会自增长为D4: 现在如果想拷贝的时候不自增长,可以先把光标放到C4单元格,然后按F4键,行和列的前面加上了$符号,锁定了: …

MySQL优化(1):B+树与索引

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 对于60%的程序员而言&a…

【智能家居】4、智能家居框架设计和代码文件工程建立

目录 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 SourceInsight创建新工程步骤 一、智能家居项目框架 二、智能家居工厂模式示意 三、代码文件工程建立 创建一个名为si的文件夹用于保存SourceInsight生成的文件信息,然后在SourceInsig…

.Net6 部署到IIS示例

基于FastEndpoints.Net6 框架部署到IIS 环境下载与安装IIS启用与配置访问网站 环境下载与安装 首先下载环境安装程序,如下图所示,根据系统位数选择x86或者x64进行下载安装,网址:Download .NET 6.0。 IIS启用与配置 启用IIS服务 打开控制面板&#xff…

学习css过渡动画-transition

文章目录 前言transition属性语法宽度改变效果透明度改变效果位置改变效果如有启发,可点赞收藏哟~ 前言 通常,当一个元素的样式属性值发生变化时,会立即看到页面发生变化。 css属性transition能让页面元素不是立即的、而是慢慢的从一种状态变…

【Proteus仿真】【STM32单片机】公交车报站系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器,使用LCD12864显示模块、DS18B20温度传感器、DS1302时钟模块、按键、LED蜂鸣器、ULN2003、28BYJ48步进电机模块等。 主要功能: 系统运行…

2022年6月 电子学会青少年软件编程 中小学生Python编程 等级考试一级真题答案解析(选择题)

2022年6月Python编程等级考试一级真题解析 选择题(共25题,每题2分,共50分) 1、在Python编辑器中写好程序代码后,在Run菜单中,下列哪个命令可以用来执行程序 A、Check Module B、Run Module C、Python shell D、任意一个都可以 答案:B 考点分析:考查python编辑的…

表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学

🧸欢迎来到dream_ready的博客,📜相信你对这篇博客也感兴趣o (ˉ▽ˉ;) 📜表白墙/留言墙初级Spring Boot项目(此篇博客的简略版,不带MyBatis数据库开发) 目录 1、项目前端页面及项目…

ER 图是什么

文章目录 前言什么是 ER图ER 图实例简化的 ER 图总结 前言 产品经理在梳理产业业务逻辑的过程中,非常重要的一项工作就是梳理各个业务对象之间的关系。如果涉及对象很对的时候,没有工具支持的话很难处理清楚。今天我们就来介绍一个梳理业务对象关系的工…

前置语音群呼与语音机器人群呼哪个更好

最近通过观察自己接到的营销电话,通过语音机器人外呼的量应该有所下降。同时和客户交流获取到的信息,也是和这个情况类似,很多AI机器人群呼的量转向了OKCC前置语音群呼。询问原因,说是前置语音群呼转化更快,AI机器人群…

头歌 MySQL数据库 - 初识MySQL

本章内容是为了完成老师布置的作业,同时也是为了以后考试的时候方便复习。 数据库部分一条一条的写,可鼠标手动粘贴,除特定命令外未分大小写。 第1关:创建数据库 在操作数据库之前,需要连接它,输入命令&a…

《Deep learning for fine-grained image analysis: A survey》阅读笔记

论文标题 《Deep learning for fine-grained image analysis: A survey》 作者 魏秀参,旷世研究院 初读 摘要 细粒度图像分析(FGIA)的任务是分析从属类别的视觉对象。 细粒度性质引起的类间小变化和类内大变化使其成为一个具有挑战性的…

一起学docker系列之五docker的常用命令--操作容器的命令

目录 前言1 启动容器2 查看容器3 退出容器4 启动已经停止的容器5 重启容器6 停止容器7 删除已经停止的容器8 启动容器说明和举例9 查看容器日志10 查看容器内运行的进程11 查看容器内部细节12 进入正在运行的容器并进行交互13 导入和导出容器结语 前言 当涉及到容器化技术&…

对话芯动科技 | 助力云游戏 4K级服务器显卡的探索与创新

2021年芯动科技推出了基于IMG BXT GPU IP的风华1号显卡。单块风华1号显卡可在台式机和云游戏中实现4K级别的性能,渲染能力达到5 TFLOPS,如果在服务器中同时运行两块显卡,性能还可翻倍。该显卡是为不断扩大的安卓云游戏市场量身定制的&#xf…

代码随想录算法训练营第三十八天【动态规划part01】 | 动态规划理论基础、509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

动态规划理论基础 什么是动态规划 动态规划 (Dynamic Programming, DP),是求解决策过程最优化的过程。 如果某一问题有很多重叠子问题,使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪…

Spring IOC - 推断构造方法

一、前言 上文解析了Bean生命周期的实例化阶段,其中bean真正开始实例化的核心代码位于方法AbstractAutowireCapableBeanFactory#createBeanInstance中,这里也是spring推断构造方法的核心所在。 二、整体介绍 首先看下方法的源码及注释如下,下…