当前位置: 首页 > news >正文

Go-gin-example 第三部分 编写一个简单的文件日志系统

文章目录

  • 本文目标
  • 新建`logging`包
    • file.go
    • 编写log文件
    • 当前目录结构
  • 接入自定义的log功能
  • 验证功能

本文目标

在上一节中,我们解决了 API’s 可以任意访问的问题,那么我们现在还有一个问题,就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!

新建logging

我们在pkg下新建logging目录,用来包含自定义logger相关的文件,新建file.golog.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:能够接受ErrNotExistsyscall的一些错误,它会返回一个布尔值,能够得知文件不存在或目录不存在
  • os.IsPermission:能够接受ErrPermissionsyscall的一些错误,它会返回一个布尔值,能够得知权限是否满足
  • 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,否则返回error
  • os.ModePermconst定义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包的代码,如下:

  1. 打开routers目录下的article.gotag.goauth.go
    2。 将log包的引用删除,修改引用我们自己的日志包为github.com/kingsill/gin-example/pkg/logging
  2. 将原本的log.Println(...)改为logging.Info(...)

验证功能

修改文件后,重启服务,我们来试试吧!

获取到 APIToken 后,我们故意传错误 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功能验证功能 本文目标 在上一节中&#xff0c;我们解决了 API’s 可以任意访问的问题&#xff0c;那么我们现在还有一个问题&#xff0c;就是我们的日志&#xff0c;都是输出到控制台上的&…...

SQL中如何添加数据

SQL中如何添加数据 一、SQL中如何添加数据&#xff08;方法汇总&#xff09;二、SQL中如何添加数据&#xff08;方法详细解说&#xff09;1. 使用SQL脚本&#xff08;推荐&#xff09;1.1 在表中插入1.1.1 **第一种形式**1.1.2 **第二种形式**SQL INSERT INTO 语法示例SQL INSE…...

如何更好的理解设计模式之桥接模式

桥接模式 点奶茶的时候, 我们一般选择原味奶茶/牛奶奶茶/咸味奶茶 现在假设我们也有这些已经实现好的奶茶类, 且这个奶茶类仅仅就是一个类, 什么也没有, 不可改动 类似 class 奶茶{ }class 原味奶茶 extends 奶茶{ }但是奶茶也分大杯奶茶, 中杯奶茶, 小杯奶茶, 如果我们要实现…...

归并排序

参考链接 排序算法&#xff1a;归并排序【图解代码】_哔哩哔哩_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内核源代码&#xff08;特定版本号为Linux-imx_4.1.15&#xff09;的基础中展开的。 首要任务集中在对功能接口引脚配置的精细调整&#xff0c;确保其能无缝匹配至ELF 1开发板。接下来&#xff0c;我们将详细阐述适配过程中关…...

Linux:kubernetes(k8s)pod的基础操作(6)

Linux&#xff1a;kubernetes&#xff08;k8s&#xff09;允许在任意节点使用kubectl命令&#xff08;5&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/136460090?spm1001.2014.3001.5501 我在前两张进行了基础环境的一系列搭建&#xff0c;现在就正…...

【Docker】掌握 Docker 镜像操作:从基础到进阶

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 结语 我的其他博客 前言 在现代软件开发和部署中&#xff0c;容器化技术已经成为不可或缺的一部分。而 Docker 作为最流行的容器化…...

flink重温笔记(十一):Flink 高级 API 开发——flink 四大基石之 Checkpoint(详解存储后端)

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 11 天啦&#xff01;学习了 flink 四大基石之 Checkpoint &#xff08;检查点&#xff09;&#xff0c;主要是解决大数据领域持久化中间结果数据&#xff0c;以及取消任务&#xff0c;下次启动人可以恢复累加数据问题&…...

React富文本编辑器开发(九)位置

位置是指在使用Slate编辑器中进行插入、删除或执行其它操作时引用文档中特定位置的方式。有几种不同类型的位置接口&#xff0c;每种接口都用于不同的用例。 Path路径 路径是引用位置的最低级别的方法。每个路径都是一个简单的数字数组&#xff0c;它通过文档树下每个祖先节点…...

一次直播和图像识别技术应用的探索之旅

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

Ubuntu 下使用 Pybind11 实现 C++ 调用 Python 接口的示例

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

docker安装和使用kafka

1. 启动zookeeper Kafka依赖zookeeper, 首先安装zookeeper -p&#xff1a;设置映射端口&#xff08;默认2181&#xff09; 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交易接口&#xff0c;C头文件&#xff0c;包括 …...

C++小记 -链表

链表 文章目录 链表链表基础理论链表的类型单链表双链表循环链表 链表的存储方式链表的定义链表的操作添加节点删除节点 性能分析构建链表删除节点&#xff08;内存泄漏的坑&#xff09;1.直接移除2.使用虚拟头结点3.delete指针后&#xff0c;要将指针置为NULL&#xff01;&…...

网络协议学习DAY1

1.网络协议模型: OSI协议模型 应用层 实际发送的数据 表示层 发送的数据是否加密 会话层 是否建立会话连接 传输层 数据传输的方式&#xff08;数据报、流式&#xff09; 网…...

vue3中全局变量的定义和获取

在vue项目中&#xff0c;我们知道vue2定义全局变量是在main.js文件将变量挂载到vue.prototype.name"lisi"&#xff0c;在页面通过this.name去调用。但是在vue3中&#xff0c;这个定义全局变量有所改变&#xff1a; const app createApp(App) app.config.globalProp…...

1.2 数据模型 数据库系统概论

目录 1.2.1 两类数据模型 1.2.2 概念模型 1.信息世界中的基本概念 &#xff08;1&#xff09;实体 &#xff08;2&#xff09;属性 &#xff08;3&#xff09;码 &#xff08;4&#xff09;实体型 &#xff08;5&#xff09;实体集 &#xff08;6&#xff09;联系 2.…...

C#中openFileDialog 对话框不在最顶层,TopMost的异常情况

重点&#xff01;&#xff01;&#xff01;若 当前窗体this的TopMost是false&#xff0c;可以设置为true&#xff0c;这样打开的对话框就是最顶层 /// <summary> /// 设置窗体TopMost&#xff0c;缺点和其他程序ide有冲突。例如VS有断点的调试会卡死 /// </summary&g…...

信息安全与阿里云等保三级方案实践总结

信息安全在当今数字化时代变得至关重要&#xff0c;企业和组织需要采取有效措施来保护其数据和信息资产。阿里云作为中国领先的云服务提供商&#xff0c;提供了等保三级方案&#xff0c;帮助用户满足国家信息安全等级保护的要求。本文将探讨信息安全和阿里云等保三级方案的重要…...

嵌入式学习记录——线程

线程基本概念: 线程:线程是一个轻量级的进程,位于进程空间内部,一个进程中可以创建多个线程 1.线程创建: 线程独占栈空间,文本段、数据段和堆区与进程共享 2.线程调度: 与进程调度是一样的 宏观并行,微观串行 3.线程消亡: 与进程消亡是一样的 4.进程和线程…...

终极Gmail桌面体验:告别浏览器标签混乱,拥抱高效邮件管理

终极Gmail桌面体验&#xff1a;告别浏览器标签混乱&#xff0c;拥抱高效邮件管理 【免费下载链接】gmail-desktop :postbox: Gmail desktop app for macOS, Windows & Linux (formerly Gmail Desktop) 项目地址: https://gitcode.com/gh_mirrors/gm/gmail-desktop 厌…...

手把手教你用Docker Compose部署Jitsi Meet视频会议,并解决“断开链接”的坑

从零构建高可用Jitsi Meet视频会议系统&#xff1a;Docker Compose实战与深度排错指南 在远程协作成为常态的今天&#xff0c;搭建自主可控的视频会议系统已成为许多技术团队的基础需求。Jitsi Meet作为开源的WebRTC视频会议解决方案&#xff0c;凭借其出色的音视频质量和灵活的…...

告别文档踩坑:手把手教你用OkHttp和Gson解析OneNET API返回的复杂JSON数据

告别文档踩坑&#xff1a;手把手教你用OkHttp和Gson解析OneNET API返回的复杂JSON数据 在Android开发中&#xff0c;处理网络请求和JSON数据解析是每个开发者都必须掌握的基本技能。然而&#xff0c;当面对像OneNET这样的物联网平台返回的复杂嵌套JSON结构时&#xff0c;即使是…...

瑞萨电子2019年中国市场战略与MCU/SoC产品深度解析

1. 项目概述&#xff1a;一次对特定年份半导体巨头市场策略的深度复盘在半导体这个日新月异的行业里&#xff0c;每年各大厂商的产品发布和市场策略&#xff0c;都像是一张张精心绘制的航海图&#xff0c;指引着下游应用市场的技术风向。今天&#xff0c;我想和大家深入聊聊一个…...

OpenStack 12大组件说明-blog

OpenStack 12大组件说明 OpenStack 是开源Iaas云计算平台&#xff0c;由12大核心组件构成&#xff0c;各组件独立部署、协同工作&#xff0c;覆盖计算、存储、网络、认证等全场景&#xff0c;以下是各组件核心说明&#xff08;精简版&#xff09;。 1. Nova&#xff08;计算服务…...

从LVGL官方例程到自定义界面:在Windows上用CodeBlocks模拟器快速玩转GUI设计

从LVGL官方例程到自定义界面&#xff1a;在Windows上用CodeBlocks模拟器快速玩转GUI设计 对于嵌入式开发者而言&#xff0c;图形用户界面(GUI)设计往往需要在硬件平台上反复烧录测试&#xff0c;效率低下。而LVGL模拟器配合CodeBlocks的组合&#xff0c;为开发者提供了一个在PC…...

别再死记硬背了!用这个商品库存表案例,5分钟搞懂HTML表格的rowspan属性

别再死记硬背了&#xff01;用商品库存表案例5分钟掌握HTML表格的rowspan属性 每次看到HTML表格代码里那些rowspan和colspan属性就头疼&#xff1f;别担心&#xff0c;今天我们不谈枯燥的语法定义&#xff0c;而是通过一个真实的商品库存管理案例&#xff0c;带你理解rowspan的…...

Hi3861点灯程序背后的构建系统:手把手教你修改BUILD.gn文件,定制你的第一个鸿蒙应用

Hi3861开发实战&#xff1a;深入鸿蒙构建系统与GN脚本定制指南 当LED灯在Hi3861开发板上第一次亮起时&#xff0c;很多开发者会认为这只是一个简单的GPIO控制实验。但鲜为人知的是&#xff0c;这个看似简单的"点灯"动作背后&#xff0c;隐藏着鸿蒙轻量设备开发中最核…...

如何实现微信聊天记录永久保存?开源工具WeChatMsg完整解决方案

如何实现微信聊天记录永久保存&#xff1f;开源工具WeChatMsg完整解决方案 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/…...

Inkscape实战:用蒙版给你的Logo或文字快速添加酷炫的渐变效果

Inkscape蒙版进阶&#xff1a;打造专业级渐变Logo的5种创意技法 在矢量设计领域&#xff0c;一个普通的Logo与令人眼前一亮的作品之间&#xff0c;往往只差一层巧妙的渐变蒙版。作为开源矢量图形编辑器的标杆&#xff0c;Inkscape的蒙版功能远不止于基础遮罩——当它与渐变工具…...