Go-gin-example 第三部分 编写一个简单的文件日志系统
文章目录
- 本文目标
- 新建`logging`包
- file.go
- 编写log文件
- 当前目录结构
- 接入自定义的log功能
- 验证功能
本文目标
在上一节中,我们解决了 API’s 可以任意访问的问题,那么我们现在还有一个问题,就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!
新建logging
包
我们在pkg
下新建logging
目录,用来包含自定义logger相关的文件,新建file.go
和log.go
文件
file.go
用来创建记录log的相关目录和文件,其内容为:
package loggingimport ("fmt""log""os""time"
)// 适用枚举,将所有固定的量提前列出在这里,方便后期维护 我们这里将原来的var修改为const
const (LogSavePath = "runtime/logs/"LogSaveName = "log"LogFileExt = "log"TimeFormat = "20060102"
)// 返回log文件的前缀路径,算是一个具有仪式感的函数
func getLogFilePath() string {return fmt.Sprintf("%s", LogSavePath)
}// 获得log文件的整体路径,以当前日期作为.log文件的名字
func getLogFileFullPath() string {prefixPath := getLogFilePath()suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt)return fmt.Sprintf("%s%s", prefixPath, suffixPath)
}// 打开日志文件,返回写入的句柄handle
func openLogFile(filePath string) *os.File {//根据文件目录是否存在进行判断_, err := os.Stat(filePath)switch {//目录不存在case os.IsNotExist(err):mkDir()//权限不够case os.IsPermission(err):log.Fatalf("Permission :%v", err)}//如果.log文件不存在,这里会创建一个handle, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err != nil {log.Fatalf("Fail to OpenFile :%v", err)}return handle
}// 创建log目录
func mkDir() {//获得当前目录 dir: /home/wang2/gin-exampledir, _ := os.Getwd()//适用MKdirAll会直接创建所有依赖的父目录,减少报错的可能性err := os.MkdirAll(dir+"/"+getLogFilePath(), os.ModePerm)if err != nil {panic(err)}
}
os.Stat
:返回文件信息结构描述文件。如果出现错误,会返回*PathError
type PathError struct {Op stringPath stringErr error }
os.IsNotExist
:能够接受ErrNotExist
、syscall
的一些错误,它会返回一个布尔值,能够得知文件不存在或目录不存在os.IsPermission
:能够接受ErrPermission
、syscall
的一些错误,它会返回一个布尔值,能够得知权限是否满足os.OpenFile
:调用文件,支持传入文件名称、指定的模式调用文件、文件权限,返回的文件的方法可以用于 I/O。如果出现错误,则为*PathError
。const (// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.O_RDONLY int = syscall.O_RDONLY // 以只读模式打开文件O_WRONLY int = syscall.O_WRONLY // 以只写模式打开文件O_RDWR int = syscall.O_RDWR // 以读写模式打开文件// The remaining values may be or'ed in to control behavior.O_APPEND int = syscall.O_APPEND // 在写入时将数据追加到文件中O_CREATE int = syscall.O_CREAT // 如果不存在,则创建一个新文件O_EXCL int = syscall.O_EXCL // 使用O_CREATE时,文件必须不存在O_SYNC int = syscall.O_SYNC // 同步IOO_TRUNC int = syscall.O_TRUNC // 如果可以,打开时 )
os.Getwd
:返回与当前目录对应的根路径名os.MkdirAll
:创建对应的目录以及所需的子目录,若成功则返回nil,否则返回erroros.ModePerm
:const
定义ModePerm FileMode = 0777
文件权限的知识补充:err := os.MkdirAll(“wang2/”+path, os.ModePerm)
这里的modePerm中Perm为permission的缩写
在Linux和Unix操作系统中,文件和目录的权限通常用八进制数表示,例如 0777。这个八进制数表示了文件或目录的权限位,以及哪些用户或用户组有权访问它。在 0777 中,每一位都有特定的含义:
- 最高位(左边的0)表示特殊权限位,通常不使用,所以它通 常是0。
- 下一个三位(中间的777)表示用户(文件的所有者)的权限。
- 接下来的三位表示组(文件的所属组)的权限。
- 最后三位表示其他人(不是文件所有者或所属组的用户)的权限。
每个三位权限位由三个位组成,它们可以是以下之一:
- 4:读权限(R):用户可以读取文件或列出目录中的内容。
- 2:写权限(W):用户可以编辑或写入文件,对于目录来说,用户可以在其中创建、删除或重命名文件。
- 1:执行权限(X):用户可以执行文件或进入目录。
编写log文件
log.go
:
package loggingimport ("fmt""log""os""path/filepath""runtime"
)// 类型声明,基于int建立level,方便后续维护
type Level intvar (//传入写log文件的句柄F *os.File//默认的前缀DefaultPrefix = ""//这里的定义在后续的caller中被调用,该参数指定了要跳过的调用堆栈帧数,每个调用堆栈帧代表了代码中的一个函数调用。//以info写入log为例,我们这里调用caller的函数为setPrefix,Info调用setPrefix,适用Info进行写入的函数调用info。// 0 1 2//而我们想要得到的信息就是调用info的函数的信息。所以我们这里设置的跳过调用堆栈帧数为2DefaultCallerDepth = 2//提前定义logger记录器,方便维护阅读logger *log.LoggerlogPrefix = ""//结合level的定义,方便读取维护levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)// 实现枚举
const (DEBUG Level = iotaINFOWARNINGERRORFATAL
)// 自定义logger的初始化
func init() {//获取log文件目录filePath := getLogFileFullPath()//得到log文件句柄F = openLogFile(filePath)//创建一个新的日志记录器logger = log.New(F, DefaultPrefix, log.LstdFlags)
}func Debug(v ...interface{}) {setPrefix(DEBUG)logger.Println(v)
}// 这里先设置每条log的前缀部分,首先为log模式,这里为info;然后为具体到某个函数第几行出错;接下来为时间;最后为日志信息
func Info(v ...interface{}) {setPrefix(INFO)logger.Println(v)
}func Warn(v ...interface{}) {setPrefix(WARNING)logger.Println(v)
}func Error(v ...interface{}) {setPrefix(ERROR)logger.Println(v)
}func Fatal(v ...interface{}) {setPrefix(FATAL)logger.Fatalln(v)
}// 从进程中读取当前运行的函数信息
func setPrefix(level Level) {//获取文件名,具体行数,是否读取成功_, file, line, ok := runtime.Caller(DefaultCallerDepth)if ok { //获取成功logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)} else { //获取失败,则前缀不加如具体的文件名和行号logPrefix = fmt.Sprintf("[%s]", levelFlags[level])}//将前缀写入log文件logger.SetPrefix(logPrefix)
}
log.New
:创建一个新的日志记录器。out
定义要写入日志数据的IO句柄。prefix
定义每个生成的日志行的开头。flag
定义了日志记录属性func New(out io.Writer, prefix string, flag int) *Logger {return &Logger{out: out, prefix: prefix, flag: flag} }
log.LstdFlags
:日志记录的格式属性之一,其余的选项如下const (Ldate = 1 << iota // the date in the local time zone: 2009/01/23Ltime // the time in the local time zone: 01:23:23Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.Llongfile // full file name and line number: /a/b/c/d.go:23Lshortfile // final file name element and line number: d.go:23. overrides LlongfileLUTC // if Ldate or Ltime is set, use UTC rather than the local time zoneLstdFlags = Ldate | Ltime // initial values for the standard logger )
当前目录结构
gin-blog/
├── conf
│ └── app.ini
├── main.go
├── middleware
│ └── jwt
│ └── jwt.go
├── models
│ ├── article.go
│ ├── auth.go
│ ├── models.go
│ └── tag.go
├── pkg
│ ├── e
│ │ ├── code.go
│ │ └── msg.go
│ ├── logging
│ │ ├── file.go
│ │ └── log.go
│ ├── setting
│ │ └── setting.go
│ └── util
│ ├── jwt.go
│ └── pagination.go
├── routers
│ ├── api
│ │ ├── auth.go
│ │ └── v1
│ │ ├── article.go
│ │ └── tag.go
│ └── router.go
├── runtime
接入自定义的log功能
我们自定义的logging
包,已经基本完成了,接下来让它接入到我们的项目之中吧。我们打开先前包含log
包的代码,如下:
- 打开routers目录下的
article.go
、tag.go
、auth.go
。
2。 将log包的引用删除,修改引用我们自己的日志包为github.com/kingsill/gin-example/pkg/logging
。 - 将原本的
log.Println(...)
改为logging.Info(...)
。
验证功能
修改文件后,重启服务,我们来试试吧!
获取到 API
的 Token
后,我们故意传错误 URL
参数给接口,如:http://127.0.0.1:8000/api/v1/articles?tag_id=0&state=9999999&token=eyJhbG..
然后我们到$GOPATH/gin-blog/runtime/logs
查看日志:
$ tail -f log20180216.log
[INFO][article.go:79]2018/02/16 18:33:12 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:33:42 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:33:42 [tag_id 标签ID必须大于0]
[INFO][article.go:79]2018/02/16 18:38:39 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:38:39 [tag_id 标签ID必须大于0]
日志结构一切正常,我们的记录模式都为Info
,因此前缀是对的,并且我们是入参有问题,也把错误记录下来了,这样排错就很方便了!
至此,本节就完成了,这只是一个简单的扩展,实际上我们线上项目要使用的文件日志,是更复杂一些,开动你的大脑 举一反三吧!
相关文章:
Go-gin-example 第三部分 编写一个简单的文件日志系统
文章目录 本文目标新建logging包file.go编写log文件当前目录结构 接入自定义的log功能验证功能 本文目标 在上一节中,我们解决了 API’s 可以任意访问的问题,那么我们现在还有一个问题,就是我们的日志,都是输出到控制台上的&…...

SQL中如何添加数据
SQL中如何添加数据 一、SQL中如何添加数据(方法汇总)二、SQL中如何添加数据(方法详细解说)1. 使用SQL脚本(推荐)1.1 在表中插入1.1.1 **第一种形式**1.1.2 **第二种形式**SQL INSERT INTO 语法示例SQL INSE…...
如何更好的理解设计模式之桥接模式
桥接模式 点奶茶的时候, 我们一般选择原味奶茶/牛奶奶茶/咸味奶茶 现在假设我们也有这些已经实现好的奶茶类, 且这个奶茶类仅仅就是一个类, 什么也没有, 不可改动 类似 class 奶茶{ }class 原味奶茶 extends 奶茶{ }但是奶茶也分大杯奶茶, 中杯奶茶, 小杯奶茶, 如果我们要实现…...
归并排序
参考链接 排序算法:归并排序【图解代码】_哔哩哔哩_bilibili #include <stdio.h> #include <stdlib.h>// 合并 void merge(int arr[], int tempArr[], int left, int mid, int right) {// 标记左半区第一个未排序的元素int l_pos left;// 标记右半区…...

ELF 1技术贴|在NXP源码基础上适配开发板的按键功能
本次源代码适配是在NXP i.MX6ULL EVK评估板的Linux内核源代码(特定版本号为Linux-imx_4.1.15)的基础中展开的。 首要任务集中在对功能接口引脚配置的精细调整,确保其能无缝匹配至ELF 1开发板。接下来,我们将详细阐述适配过程中关…...

Linux:kubernetes(k8s)pod的基础操作(6)
Linux:kubernetes(k8s)允许在任意节点使用kubectl命令(5)-CSDN博客https://blog.csdn.net/w14768855/article/details/136460090?spm1001.2014.3001.5501 我在前两张进行了基础环境的一系列搭建,现在就正…...

【Docker】掌握 Docker 镜像操作:从基础到进阶
🍎个人博客:个人主页 🏆个人专栏:Linux ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在现代软件开发和部署中,容器化技术已经成为不可或缺的一部分。而 Docker 作为最流行的容器化…...

flink重温笔记(十一):Flink 高级 API 开发——flink 四大基石之 Checkpoint(详解存储后端)
Flink学习笔记 前言:今天是学习 flink 的第 11 天啦!学习了 flink 四大基石之 Checkpoint (检查点),主要是解决大数据领域持久化中间结果数据,以及取消任务,下次启动人可以恢复累加数据问题&…...
React富文本编辑器开发(九)位置
位置是指在使用Slate编辑器中进行插入、删除或执行其它操作时引用文档中特定位置的方式。有几种不同类型的位置接口,每种接口都用于不同的用例。 Path路径 路径是引用位置的最低级别的方法。每个路径都是一个简单的数字数组,它通过文档树下每个祖先节点…...

一次直播和图像识别技术应用的探索之旅
背景 Think Better 很多行业都在“卷”,作为金融科技行业的信也,也不例外。除了卷云计算、大数据和人工智能这些非常有深度的技术以外,信也向着技术融合创新的方向逐步探索,做得更好一些,为用户提供更好的价值…...

Ubuntu 下使用 Pybind11 实现 C++ 调用 Python 接口的示例
Pybind11 是一个轻量级的库,它提供了在 C 中无缝集成 Python 代码的能力。使用 Pybind11,你可以很容易地从 C 调用 Python 代码,反之亦然。下面我将通过一个简单的例子来展示如何在 Ubuntu 系统上使用 Pybind11 从 C 调用 Python 接口。 安装…...

docker安装和使用kafka
1. 启动zookeeper Kafka依赖zookeeper, 首先安装zookeeper -p:设置映射端口(默认2181) docker run --name zookeeper \--network app-tier \-e ALLOW_ANONYMOUS_LOGINyes \--restartalways \-d bitnami/zookeeper:latest2. 启动kafka docker…...

CTP-API开发系列之接口对接准备
CTP-API开发系列之接口对接准备 CTP-API开发系列之接口对接准备CTP-API文件清单CTP-API通用规则命名规则Spi与Api CTP-API通讯模式开发语言选择 CTP-API开发系列之接口对接准备 CTP-API文件清单 文件名说明ThostFtdcTraderApi.h交易接口,C头文件,包括 …...

C++小记 -链表
链表 文章目录 链表链表基础理论链表的类型单链表双链表循环链表 链表的存储方式链表的定义链表的操作添加节点删除节点 性能分析构建链表删除节点(内存泄漏的坑)1.直接移除2.使用虚拟头结点3.delete指针后,要将指针置为NULL!&…...
网络协议学习DAY1
1.网络协议模型: OSI协议模型 应用层 实际发送的数据 表示层 发送的数据是否加密 会话层 是否建立会话连接 传输层 数据传输的方式(数据报、流式) 网…...
vue3中全局变量的定义和获取
在vue项目中,我们知道vue2定义全局变量是在main.js文件将变量挂载到vue.prototype.name"lisi",在页面通过this.name去调用。但是在vue3中,这个定义全局变量有所改变: const app createApp(App) app.config.globalProp…...

1.2 数据模型 数据库系统概论
目录 1.2.1 两类数据模型 1.2.2 概念模型 1.信息世界中的基本概念 (1)实体 (2)属性 (3)码 (4)实体型 (5)实体集 (6)联系 2.…...
C#中openFileDialog 对话框不在最顶层,TopMost的异常情况
重点!!!若 当前窗体this的TopMost是false,可以设置为true,这样打开的对话框就是最顶层 /// <summary> /// 设置窗体TopMost,缺点和其他程序ide有冲突。例如VS有断点的调试会卡死 /// </summary&g…...

信息安全与阿里云等保三级方案实践总结
信息安全在当今数字化时代变得至关重要,企业和组织需要采取有效措施来保护其数据和信息资产。阿里云作为中国领先的云服务提供商,提供了等保三级方案,帮助用户满足国家信息安全等级保护的要求。本文将探讨信息安全和阿里云等保三级方案的重要…...
嵌入式学习记录——线程
线程基本概念: 线程:线程是一个轻量级的进程,位于进程空间内部,一个进程中可以创建多个线程 1.线程创建: 线程独占栈空间,文本段、数据段和堆区与进程共享 2.线程调度: 与进程调度是一样的 宏观并行,微观串行 3.线程消亡: 与进程消亡是一样的 4.进程和线程…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...