Go开发中配置一个Logger日志的功能实现

news2024/12/28 6:41:33

为什么需要Logger

一般在开发项目的时候我们都是需要一个存储日志的文件,因为在部署项目以后,我们只能通过去筛查日志进行检索问题,这时候日志是否可以呈现清晰这个对于我们进行排查工作是十分重要的,所以Logger能否展示出我们最想要的错误展示方式是很有必要的!本章节的案例是基于gin框架和viper进行编写一个Logger的日志文件,日志会根据yaml文件定义的 mode进行判断是否是开发环境还是线上环境进行写的。

实现Logger.go

首先看一下yaml文件的配置

在yaml文件中定义了一个app,app下面有一个mode,这个mode就是用来识别我们是开发环境还是线上环境的。

app:
  name: "web_app"
  mode: "dev"
  port: 8080
  version: "v0.01"
log:
  level: "debug"
  filename: "web_app.log"
  max_size: 200
  max_age: 30
  max_backups: 7

settings的定义

var Conf AppConfig

type AppConfig struct {
	App   App         `mapstructure:"app" yaml:"app"`
	Log   LogConfig   `mapstructure:"log"`
}

type App struct {
	Name      string `mapstructure:"name"`
	Mode      string `mapstructure:"mode"`
	Version   string `mapstructure:"version"`
	Port      int    `mapstructure:"port" yaml:"port"`
}

type LogConfig struct {
	Level      string `mapstructure:"level"`
	Filename   string `mapstructure:"filename"`
	MaxSize    int    `mapstructure:"max_size"`
	MaxAge     int    `mapstructure:"max_age"`
	MaxBackups int    `mapstructure:"max_backups"`
}

Logger.go

这里需要结合自己的实际进行修改,如果复制了import中的依赖,需要执行一下:go mod tidy

package logger

import(
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"

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

var logger *zap.Logger

// Init 的两个参数: cfg 和 mode
func Init(cfg *settings.LogConfig, mode string) (err error) {
	writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
	encoder := getEncoder()
	var l = new(zapcore.Level)
	err = l.UnmarshalText([]byte(cfg.Level))
	if err != nil {
		return err
	}

	var core zapcore.Core
	// 开发模式,日志输出终端
	if mode == "dev" {
		consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
		core = zapcore.NewTee(
			zapcore.NewCore(encoder, writeSyncer, l), // 保存在日志中
			// 错误信息展示在终端
			zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), 
		)
	} else {
		core = zapcore.NewCore(encoder, writeSyncer, l)
	}

	logger = zap.New(core, zap.AddCaller())

	lg := zap.New(core, zap.AddCaller())
	// 替换zap库中的全局
	zap.ReplaceGlobals(lg)
	return err
}

func getEncoder() zapcore.Encoder {
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	encodeConfig := zapcore.EncoderConfig{
		LevelKey:       "level",
		TimeKey:        "ts",
		NameKey:        "logger",
		CallerKey:      "caller",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}
	//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
	return zapcore.NewConsoleEncoder(encodeConfig)
}

// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next()

		cost := time.Since(start)
		zap.L().Info(path,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
		)
	}
}

func getLogWriter(string, int, int, int) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   viper.GetString("log.filename"),
		MaxSize:    viper.GetInt("log.max_size"),    // 单位是M
		MaxBackups: viper.GetInt("log.max_backups"), // 备份数量
		MaxAge:     viper.GetInt("log.max_age"),     // 最大备份天数
		Compress:   false,                           // 是否压缩
	}
	return zapcore.AddSync(lumberJackLogger)
}

func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					zap.L().Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					zap.L().Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					zap.L().Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

最后在main.go中进行注册方法

func main() {
	//	1、加载配置文件
	if err := settings.Init(); err != nil {
		fmt.Println("init setting failed", err)
		return
	}
	//  2、初始化日志
	if err := logger.Init(&settings.Conf.Log, settings.Conf.App.Mode); err != nil {
		fmt.Printf("init logger failed, err:%v\n", err)
	}
	defer zap.L().Sync()
	zap.L().Debug("logger init success...")
}

在开发环境(dev)中的测试结果

与自己想要的结果是一致的:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux基础——git和gdb的使用

前言 我们在平时维护和上传代码时会用到git,但是这个git具体是什么,往往又说不清楚。其实git其实就是一个版本管理工具,有了这个工具就能方便快捷地查询自己上传代码的不同版本,对每一次上传的改动了如指掌。 那gdb又是什么呢&a…

文本生成自回归解码策略总结

当今文本生成的主流方式还是自回归式的语言建模,本篇文章对文本生成常用的几种自回归采样&解码策略进行总结(以下统称为采样)。 采样方式Argmax Decoding(贪婪采样)Greedy Search(贪心搜索)…

Neo4J入门笔记[2]---Neo4J GDS 图数据科学库

Neo4J 提供了GDS的库,里面包括了很多算法。GDS的英语全称是Graph Data Science(图数据科学库),其句法流程如下: stream Returns the result of the algorithm as a stream of records. stats Returns a single recor…

【VUE3】保姆级基础讲解(二)计算属性,vue组件,vue-cli脚手架,组件通讯,插槽slot

目录 计算属性computed 侦听器watch 对象监听 组件 注册全局组件 注册局部组件 Vue-CLI脚手架 安装和使用 .browserslistrc main.js jsconfig.json 组件通讯 父组件传递给子组件 props基础 非prop的attribute 子组件传递给父组件 插槽slot 基础使用 具名插槽 …

前端整合ECharts

1、简介 ECharts是百度的一个项目,后来百度把Echart捐给apache,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可…

图数据库 Neo4j 学习之SpringBoot整合

Neo4j 系列 1、图数据库 Neo4j 学习随笔之基础认识 2、图数据库 Neo4j 学习随笔之核心内容 3、图数据库 Neo4j 学习随笔之基础操作 4、图数据库 Neo4j 学习随笔之高级操作 5、图数据库 Neo4j 学习之JAVA-API操作 6、图数据库 Neo4j 学习之SpringBoot整合 文章目录Neo4j 系列前…

Docker与Linux之间的关系——Namespace,Cgroups, 网络通信总结

文章目录一、前言二、NamespaceNamespace Linux内核操作方法容器隔离性与 Linux Namespace 关系Linux Namespace 常用操作三、CgroupsCgroups 子系统CPU 子系统cpuacct 子系统Memory 子系统Linux 调度器使用 cgroup 限额实践四、DockerDocker的文件系统OCI 容器标准Docker 引擎…

php+vue基于微信小程序的房屋租赁小程序

当今社会房屋租赁买卖是必不可少的,人们不管走到哪里都需要有一个温馨的家,有一个落脚之地,所以房屋租赁市场也是非常火爆!不管是房屋租赁公司或者是个人都需要一套完整的管理系统来掌握整个市场信息。针对这一需求,本…

微信小程序框架(三)-全面详解(学习总结---从入门到深化)

目录 事件系统 什么是事件 事件的使用方式 Event对象 事件分类 冒泡事件(bind) 非冒泡事件(catch) 事件类型 事件类型列表 事件携带参数 currentTarget 携带参数 mark 携带参数 条件渲染 wx:if wx:else wx:elif hidden wx:if vs hidden 区别 列表渲染 基本使用 复杂数…

C++用unordered_map查表代替if else/switch case多判断语句

一、引言 在C中我们写判断逻辑一般会用if else或switch case语句&#xff0c;比如以下例子&#xff1a; #include <iostream>using namespace std;class CTest { public:enum class ConditionType{TYPE1 0,TYPE2,TYPE3,};CTest() default;~CTest() default;void exe…

双倍NB!阿里一线架构师花7天肝出的这份620页“MyBatis源码解析绝密文档” 太震撼了!

前言 都知道MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff08;Plain Old Jav…

非零基础自学计算机操作系统 第1章 操作系统概述 1.1 操作系统的概念

非零基础自学计算机操作系统 文章目录非零基础自学计算机操作系统第1章 操作系统概述1.1 操作系统的概念1.1.1 操作系统的地位1.1.2 操作系统的作用1.1.3 操作系统的定义第1章 操作系统概述 1.1 操作系统的概念 关于什么是操作系统&#xff0c;目前尚无统一的定义。这里只能从…

基于Java的JSP电动车维修管理系统

随着我国电动车数量的不断增加&#xff0c;如果能够在电动车出现故障的时候及时的解决这些故障问题&#xff0c;并且能够让电动车的维修人员更好的对维修信息进行管理是本系统主要研究的问题&#xff61; 本项目利用软件工程原理&#xff0c;采用面向对象的编程方法&#xff0c…

会话技术

会话技术 今日目标 理解什么是会话跟踪技术 掌握Cookie的使用 掌握Session的使用 完善用户登录注册案例的功能 1&#xff0c;会话跟踪技术的概述 对于会话跟踪这四个词&#xff0c;我们需要拆开来进行解释&#xff0c;首先要理解什么是会话&#xff0c;然后再去理解什么是…

二叉树的构造(如何唯一确定一棵二叉树?附证明)

二叉树的构造(如何唯一确定一棵二叉树?附证明) 一些直观的认识 ▪ 同一棵二叉树具有唯一先序序列、中序序列和后序序列。 ▪ 不同的二叉树可能具有相同的先序序列、中序序列和后序序列。 通过上面两个例子的验证&#xff1a; ▪ 仅有一个先序序列&#xff08;或中序序列、后…

C++类和对象--封装

目录 1.封装 1.1封装的意义一--将属性和行为作为一个整体 1.1.1成员属性&#xff0c;成员变量&#xff0c;成员函数&#xff0c;成员方法 1.2封装的意义二--将属性和行为加以控制&#xff0c;公共权限&#xff0c;保护权限&#xff0c;私有权限 1.3struct和class区别 1.4成员…

物联网-异步控制多个设备

物联网-异步控制设备 背景 在这个万物互联的时代&#xff0c;物联网设备起到了关键性的作用&#xff0c;那我们怎么去联动一个个物联网设备。比如一个家庭&#xff0c;里面有很多的设备&#xff0c;比如洗衣机&#xff0c;加湿器、空调、除湿机、灯光等等这些智能设备。假如在…

攻击类型分布

执行摘要 从 1987 年 9 月 14 日&#xff0c;中国向世界发出第一封电子邮件到如今&#xff0c;中国的互联网 发展已过去整整 31 个年头。从消费互联、产业互联到万物互联&#xff0c;互联网正在加速改变我们的交流方式和交易方式&#xff0c;一次次 004.重塑了国家的经济形态和…

【Flutter 组件】001-关于 Widget 的一切

【Flutter 组件】001-关于 Widget 的一切 文章目录【Flutter 组件】001-关于 Widget 的一切一、概述1、Widget 基本概述2、Flutter Framework 里的 Widget架构图说明3、根 Widget二、Widget 类1、Widget 的功能2、Widget 类源码说明Widget 的标识符&#xff1a;KeyFlutter 中如…

【云原生 | Kubernetes 实战】10、K8s 控制器 Deployment 入门到企业实战应用(上)

目录 一、Deployment 控制器&#xff1a;概念、原理解读 1.1 Deployment 概述 1.2 Deployment 工作原理&#xff1a;如何管理 rs 和 Pod&#xff1f; 补充&#xff1a;什么叫做更新节奏和更新逻辑呢&#xff1f; 二、Deployment 资源清单文件编写技巧 三、Deployment 使…