Gin框架入门(2)--异常捕获与日志实现

news2024/12/25 14:24:41

异常捕获

Go语言的异常捕获采用的是延迟处理的方法实现的,实际上就是利用defer,panic和recover三个关键字和函数来实现的。

关键字

defer关键字(函数)

这个关键字在控制语句中就有所涉及,本质上是采用一个栈的存储结构,在整个函数执行完之后,再启用这个栈,依次执行这个函数。需要注意的是以下几点:

  1. 由于是栈的结构,我们首先想到的便是先入后出,defer也是如此

    func f(){
    defer A
    defer B
    D
    defer C
    
    }
    

    执行的顺序是D,C,B,A

  2. 被defer标记的语句是放在最后执行的,所以也就衍生出defer的玩法——放在开头加一个打印语句,来判断go程的结束(原理如此,至于稍复杂的结合等待组或者响应等也大同小异)

panic函数

让程序直接崩溃的函数,用法panic("<报错信息>")

Recover

Recover只在延时函数中有效,效果是将崩溃的程序恢复过来;如果是在正常的语句中使用Recover,就会返回一个nil并且没有任何效果。

实例(没意义):

func main() {

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
		}
	}()

	r := router.Router()

	r.Run(":9999")
	panic("雪豹毁了我的程序")

}

运行结果:

服务端正常运行!

实例:

使用这个方法,使得程序不崩溃的情况下获得异常,并且保证只是使得前端无返回内容,而引起这个服务器的崩溃

改写user.go

func (u UserController) GetList(c *gin.Context) {
	//ReturnError(c *gin.Context, code int, msg string)
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获异常:", err)
		}
	}()

	num1, num2 := 1, 0
	num3 := num1 / num2
	ReturnUserGetListError(c, 404, num3)
    //<common.go>
    //func ReturnUserGetListError(c *gin.Context, code int, msg int) {
	//	json := &JsonErrStruct{Code: code, Msg: msg}
	//	c.JSON(http.StatusOK, json)
	//}

}

运行结果

捕获异常: runtime error: integer divide by zero
[GIN] 2024/09/22 - 20:19:09 | 200 |            0s |       127.0.0.1 | POST     "/user/list"

在这里插入图片描述

日志

日志就是记录事件的记录表,主要分为以下三种

  1. 项目的请求日志
  2. 程序出现错误日志
  3. 程序员开发的时候自己保存的日志

封装保存日志的包

  1. 首先,创建一个pkg包,这个包的用途是防止开发时需要的工具。在pkg包下面创建一个子文件夹logger,用来存放日志工具,在这个子文件夹下创建logger.go文件
  2. 之后,导入一下我们将要使用的日志工具包,在终端命令行输入go get github.com/sirupsen/logrus

文件结构

中间件logger.go内容
在这里插入图片描述

package logger

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"io"
	"os"
	"path"
	"path/filepath"
	"runtime/debug"
	"time"
)

// 初始化日志设置
func init() {
	// 设置日志的 JSON 格式
	logrus.SetFormatter(&logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
	logrus.SetReportCaller(false)
}

// 写入程序员自定义的日志
func Write(msg string, filename string) {
	setOutPutFile(logrus.InfoLevel, filename)
	logrus.Info(msg)
}

// Debug 级别日志
func Debug(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.DebugLevel, "debug")
	logrus.WithFields(fields).Debug(args)
}

// Info 级别日志
func Info(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.InfoLevel, "info")
	logrus.WithFields(fields).Info(args)
}

// Warn 级别日志
func Warn(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.WarnLevel, "warn")
	logrus.WithFields(fields).Warn(args)
}

// Fatal 级别日志
func Fatal(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.FatalLevel, "fatal")
	logrus.WithFields(fields).Fatal(args)
}

// Error 级别日志
func Error(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.ErrorLevel, "error")
	logrus.WithFields(fields).Error(args)
}

// Panic 级别日志
func Panic(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.PanicLevel, "panic")
	logrus.WithFields(fields).Panic(args)
}

// Trace 级别日志
func Trace(fields logrus.Fields, args ...interface{}) {
	setOutPutFile(logrus.TraceLevel, "trace")
	logrus.WithFields(fields).Trace(args)
}

// 设置日志输出文件 在各个函数方法中调用
func setOutPutFile(level logrus.Level, logName string) {
	// 创建日志目录
	logDir := "./runtime/log"
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
		err = os.MkdirAll(logDir, 0777)
		if err != nil {
			panic(fmt.Errorf("create log dir '%s' error: %s", logDir, err))
		}
	}

	// 获取当前日期字符串
	timeStr := time.Now().Format("2006-01-02")
	fileName := filepath.Join(logDir, logName+"_"+timeStr+".log")

	// 打开日志文件,如果不存在则创建
	var err error
	os.Stderr, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("open log file err:", err)
	}

	// 设置日志输出到文件
	logrus.SetOutput(os.Stderr)
	logrus.SetLevel(level)
	return
}

// 创建了success开头的文件 在logger中以中间件的形式调用
func LoggerToFile() gin.LoggerConfig {
	logDir := "./runtime/log"
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
		err = os.MkdirAll(logDir, 0777)
		if err != nil {
			panic(fmt.Errorf("create log dir '%s' error: %s", logDir, err))
		}
	}

	// 获取当前日期字符串
	timeStr := time.Now().Format("2006-01-02")
	fileName := path.Join(logDir, "success_"+timeStr+".log")

	os.Stdout, _ = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)

	var conf = gin.LoggerConfig{
		Formatter: func(param gin.LogFormatterParams) string {
			return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
				param.TimeStamp.Format(time.RFC1123),
				param.ClientIP,
				param.Method,
				param.Path,
				param.Request.Proto,
				param.StatusCode,
				param.Latency,
				param.Request.UserAgent(),
				param.ErrorMessage,
			)
		},
		Output: io.MultiWriter(os.Stdout, os.Stderr),
	}
	return conf
}

// 将报错放在报文中返回回来 在logger中以中间件的形式调用
func Recover(c *gin.Context) {
	defer func() {
		if err := recover(); err != nil {
			if _, errDir := os.Stat("./runtime/log"); os.IsNotExist(errDir) {
				errDir := os.MkdirAll("./runtime/log", 0777)
				if errDir != nil {
					panic(fmt.Errorf("create log dir '%s' error: %s", "./runtime/log", err))
				}
			}
			timeStr := time.Now().Format("2006-01-02")
			//文件名
			fileName := path.Join("./runtime/log", timeStr+".log")

			f, errFile := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
			if errFile != nil {
				fmt.Println(errFile)
			}
			timeFileStr := time.Now().Format("2006-01-02 15:04:05")
			//写入信息
			f.WriteString("panic error tome:" + timeFileStr + "\n")
			f.WriteString(fmt.Sprintf("%v", err) + "\n")
			f.WriteString("stacktrace from panic" + string(debug.Stack()) + "\n")
			f.Close()
			c.JSON(200, gin.H{
				"code": 500,
				"msg":  fmt.Sprintf("%v", err),
			})
			//终止后续接口调用,不加入recover到异常之后,还会继续执行接口中的后续代码
			c.Abort()
		}
	}()

	c.Next()
}

可以结合注释简单了解以下,然后cv使用即可

调用方式

对于前两种:项目的请求日志(LoggerToFile)和 程序出现错误日志(Recover)而言,调用的方式是在路由(router.go)的函数中以中间件的方式调用

//日志
	r.Use(gin.LoggerWithConfig(logger.LoggerToFile()))
	r.Use(logger.Recover)

而对于 程序员开发的时候自己保存的日志(setOutPutFile),则是以方法的形式在想要记录的函数(方法)中调用logger.Write("日志信息", "user")

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

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

相关文章

ffmpeg面向对象——参数配置机制探索及其设计模式

目录概览 0.参数配置对象流程图0.1 用到的设计模式0.2 与朴素思想的对比 1.参数传递部分1.1 AVDictionary字典容器类1.1.1 类定义及类图1.1.2 构造函数1.1.3 析构函数1.1.4 设置/读取等配置参数 1.2 参数配置实例 2.参数配置生效部分2.1 参数过滤模块2.1.1 AVOption类2.1.1.1 类…

2024-09-18 实操层面理解进程

一、进程初探 # ps ajx | head -1PPID PID PGID SID TTY TPGID STAT UID TIME COMMANDroothcss-ecs:~# ps ajx | head -1; ps ajx | grep procPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND24696 24707 24707 24679 …

串的存储实现方法(与链表相关)

一、 定义 字符串是由零个&#xff08;空串&#xff09;或多个字符组成的有限序列。 eg:S"Hello World!" 串相等&#xff1a;两个串长度相等并且对应位置的字符都相等时&#xff0c;两个串才相等。 二、串的存储实现 2.1 定长顺序串 2.2 堆串 和定长顺序串的…

【速成Redis】01 Redis简介及windows上如何安装redis

前言&#xff1a; 适用于&#xff1a;需要快速掌握redis技能的人&#xff08;比如我&#xff09;&#xff0c;在b站&#xff0c;找了个课看。 01.课程简介_哔哩哔哩_bilibili01.课程简介是【GeekHour】一小时Redis教程的第1集视频&#xff0c;该合集共计19集&#xff0c;视频…

乐橙云平台接入SVMSPro平台

乐橙云平台接入SVMSPro平台 步骤一&#xff1a;进入乐橙开放平台&#xff1a;https://open.imoulife.com/ &#xff0c;点右上角的登陆&#xff0c;填写自己的用户名密码&#xff0c;进入控制台&#xff1b; 步骤二&#xff1a;登陆进去后&#xff0c;我的应用—>应用信息&a…

Fyne ( go跨平台GUI )中文文档- 架构 (八)完结

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne ( go跨平台GUI…

Java集合HashSet——HashSet在底层原理

可点击此处&#xff1a;HashSet在底层原理 创建一个默认长度16&#xff0c;默认加载因子为0.75的数组&#xff0c;数组名table 16*0.75 12&#xff0c;如果存入的数据达到12&#xff0c;则数组自动扩容为原来的2倍 根据元素的哈希值跟数组的长度计算出应存入的位置 int index…

JAVA基础:正则表达式,String的intern方法,StringBuilder可变字符串特点与应用,+连接字符串特点

1 String中的常用方法2 1.1 split方法 将字符串按照指定的内容进行分割&#xff0c;将分割成的每一个子部分组成一个数组 分割内容不会出现在数组中 实际上该方法不是按照指定的简单的符号进行分割的&#xff0c;而是按照正则表达式进行分割 1.2 正则表达式 用简单的符号组合…

思维商业篇(4)—产业上下游定

思维商业篇(4)—产业上下游定位(微笑曲线) 产业上下游定位&#xff0c;帮助我们去观察一个企业在产业上下游中处于一个什么样的生态位。 上游 处于产业链开始端&#xff0c;百川东到海&#xff0c;百川的的起始端就是上游&#xff0c;东到海的海就是下游。 处在上游的企业一…

用友网络交付总监刘伟伟受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 用友网络科技股份有限公司区域交付总监刘伟伟先生受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“如何有效提升项目经理领导力”。大会将于10月26-27日在北京举办&#xff0c;主…

UE学习篇ContentExample解读-----------Blueprint_Overview

文章目录 总览描述批次阅览1.1 Blueprint- Hello World1.2 Blueprint- Components1.3 Blueprint- Variables1.4 Blueprint- ConstructionScript1.5 Blueprint- Event Graph1.6 Blueprint- Simple Math1.7 Blueprint- Flow Control 概念总结致谢&#xff1a; 总览描述 打开关卡后…

机械设计中倒角与倒圆角

我们常说&#xff0c;机械设计要做到“一切尽在掌握中”。 包含两层意思&#xff1a;一是所有的结构细节都是仔细思考过并且完整表达&#xff0c;不能靠在制造过程中猜测设计意图、由制造人员再设计或自由发挥。 二是所有的设计都是有根据的&#xff0c;不能靠拍脑袋任意发挥…

【路径规划】自动泊车的 Simulink 模型

摘要 本文介绍了一个用于自主机器人路径规划和导航的 Simulink 模型&#xff0c;该模型结合了路径跟踪算法&#xff08;如 Pure Pursuit&#xff09;和动态机器人模型&#xff0c;实现了复杂环境中的路径跟随和导航控制。实验结果表明&#xff0c;模型能够在给定路径上精确控制…

【neo4j】neo4j和Cypher 查询语言相关知识点

【neo4j】neo4j和Cypher 查询语言相关知识点 1.什么是neo4j Neo4j 是一个广泛使用的图形数据库管理系统&#xff08;Graph Database Management System&#xff09;。它是一种NoSQL数据库&#xff0c;专为存储和查询图形数据而设计。Neo4j 支持图形数据模型&#xff0c;允许用…

误差评估,均方误差、均方根误差、标准差、方差

均方根误差 RMSE/RMS 定义 RMSE是观察值与真实值偏差的平方&#xff0c;对于一组观测值 y i y_i yi​ 和对应的真值 t i t_i ti​ R M S E 1 n ∑ i 1 n ( y i − t i ) &#xff0c;其中n是观测次数 RMSE\sqrt{\frac1n \sum_{i1}^n (y_i-t_i)} \text{&#xff0c;其中n是…

Python|OpenCV-实现识别目标图像中的圆圈(20)

前言 本文是该专栏的第22篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在处理图像检测项目的时候,可能会遇到需要检测目标图像中的“圆圈”需求。笔者在这里举个例子,如下图所示: 在图中有一个篮球,但是我们要找的目标对象并不是篮球,而是篮球它本身的这个…

智能BI平台项目

1.项目介绍 BI商业智能&#xff1a;数据可视化、报表可视化系统 4&#xff09;发布订阅 Resource 是基于名称进行查找的&#xff0c;而Spring框架中更常用的 Autowired 则是基于类型进行查找的。如果找不到匹配的bean&#xff0c;Autowired 会抛出异常&#xff0c;而 Resource…

java项目之基于spring boot的多维分类的知识管理系统的设计与实现源码

项目简介 基于spring boot的多维分类的知识管理系统的设计与实现实现了以下功能&#xff1a; 基于spring boot的多维分类的知识管理系统的设计与实现的主要使用者管理员可以管理用户信息&#xff0c;知识分类&#xff0c;知识信息等&#xff0c;用户可以查看和下载管理员发布…

如何创建标准操作规程(SOP)[+模板]

创建、分发和管理流程文档和逐步说明的能力是确定企业成功的关键因素。许多组织依赖标准操作规程&#xff08;SOP&#xff09;作为基本形式的文档&#xff0c;指导他们的工作流程操作。 然而&#xff0c;SOP不仅仅是操作路线图&#xff1b;它们就像高性能车辆中的先进GPS系统一…

01_RabbitMQ安装及工作模式

一、消息队列MQ 中间件 1.1 什么是消息队列 消息&#xff08;Message&#xff09;是指在应用间传送的数据。消息可以非常简单&#xff0c;比如只包含文本字符串&#xff0c;也可以更复杂&#xff0c;可能包含嵌入对象。 消息队列&#xff08;Message Queue&#xff09;是一…