基于go的日志库开发详解(简单易懂带源码)

基于go的日志库开发详解(简单易懂带源码),第1张

基于go的日志库开发

学了一周啦,今天来肝个大点的东东,日志库咯,
以后学java的时候呀,用的人家现成的,也没有了解就直接用了。还记得叫什么 log4j 啥的。
不过这不是重点,重点是,今天我们用go来完成一个日志库的开发哈

先看放需求,有需求书,咱才好下手。

日志库开发源码连接
1.支持往不同的方式输出日志 那就是我们搞两个程序满,一个往终端进行输出日志,一个往文件里面输出日志再来一个公共用的文件,一共三个程序:建个mylogger包,公共程序:mylogger.go 、文件输出 file.go 、终端输出 console.go


2.日志要分级别 这个也很好理解吧,想java里面或者其他语言里面都有像 DebugTraceInfoWarningErrorFatal 这些。文件相关程序和终端相关程序都应该有这个,所以我们写在公共的程序mylogger.go里面
// LogLever 日志等级类型
type LogLever uint16

// 日志类型 等级变量
const (
	UNKNOWN LogLever = iota
	DEBUG
	TRACE
	INFO
	WARNING
	ERROR
	FATAL
)
整个基于uint16的类型,给不同的日志级别划分等级所用的常量,方便后续功能的实现iota 自动增长的常量大家可别忘了哦(每行加一)

公共程序里做好方便转换的方法。因为我们输入的时字符串呀。嘿嘿

在正式开发之前,我们还得说说,runtime包下面的 Caller函数FuncForPC函数

我们先看看开发文档哈!

你问我有什么用?emmm
[2021-10-24 03:32:21] [WARNING] [main.go:main:17] 这是一条Warning级别的日志!!!
我们一般打印的日志格式是这样的满,然后里面就要有文件名、方法名字、还有调用这个方法的行号runtime包里面可以让我们得到我们想要的信息哦


终端程序开发 现在我们就可以对终端输出日志的程序进行开发啦 设置好数据结构和构造器

然后再搞一个打印到终端的方法了
func (c ConsoleLogger) log(lv LogLever, format string, a ...interface{}) {
	if c.enable(lv) {
		msg := fmt.Sprintf(format, a...)
		nowTime := time.Now().Format("2006-01-02 03:04:05")
		funcName, fileName, lineNo := getInfo(3)
		fmt.Printf("[%s] [%s] [%s:%s:%d] %s\n", nowTime, getLogString(lv), fileName, funcName, lineNo, msg)
	}
}
因为我们日志等级,有很多种,所以针对不同的同等,我们都要调用一次该方法

这里要说一下哈,后面我们设置了a …interface{} 的形式去接收参数,这样可以保证日志打印的多样性,举个例子
		log.Info("这是一条不带参数的Info级别的日志!!")
		age := 22
		name := "张三"
		log.Info("这是一条带参数的Info级别的日志!! name:%s id:%d", name, age)
当我们需要打印的日志携带参数时,有的时候我们需要打印error啥的,所以这很有必要哦
3.日志要支持开关控制 这个就是你需要打印的日志级别呀。比如说,你在开发的时候,需要看全部级别的日志但是在项目上线后,这些日志就不需要打印了,只保留error及其以上的日志。 日志开关控制 前面有贴出来过,就是打印方法里面的那个enable方法,啊哈哈哈哈哈
// 打印日志的条件控制
// 打印日志等级为设置日志之下的
func (c ConsoleLogger) enable(loglever LogLever) bool {
	return loglever >= c.Lever
}
在设置mylogger.go程序的时候,我们就设置了不同的日志有不同的值。从而有了等级区别。我们在构造器里面投入的就是控制打印日志的最小级别。高于或等于当前级别才会被打印哦!

到这里我们就终端开发完啦,来测试测试吧

可以看出来哈,我们实现了终端输出的效果哈,信息,时间啥的也不差。级别控制也很完美舒服哈,啊哈哈哈哈哈哈,来进行文件开发吧
4.完整的日志记录含有时间、行号、文件名、日志级别、日志信息 这个,我们通过Runtime包里面的各种方法,都已经实现了哈
5.日志文件要进行分割 这个也能理解呀,不可以一个文件往死里装吧,所以还是要分割的但是分割我们可以按照时间进行分割,也可以按照大小进行分割呀,我个人还是认为按照大小分好一点,啊哈哈哈哈哈哈各有利弊,各有利弊,大家看自己需求吧,这里我就使用 按照大小进行分割 了哈 文件程序的数据结构
// 往文件里面写日志相关代码
type FileLogger struct {
	Level       LogLever
	filePath    string // 日志文件保存的路径
	fileName    string // 日志文件保存的文件名
	fileObj     *os.File
	errFileObj  *os.File
	maxFileSize int64 // 日志文件保存的最大内存
}
我们要有文件保存的路径、保存的名字、还有文件限定的大小以及必要的日志等级 文件程序的构造器
// NewFileLogger 生成日志文件构造器
func NewFileLogger(leverStr, fp, fn string, maxSize int64) *FileLogger {
	logLever, err := parseLogLever(leverStr)
	if err != nil {
		panic(err)
	}
	f1 := &FileLogger{
		Level:       logLever,
		filePath:    fp,
		fileName:    fn,
		maxFileSize: maxSize,
	}
	err1 := f1.initFile()
	if err != nil {
		panic(err1)
	}
	return f1
}
输入文件路径和文件名,我们就可以利用初始化函数,来打开两个文件了,一个用于平常的日志,一个用于error及其以上错误级别的日志
// 初始化保存的日志文件和错误日志文件
func (f *FileLogger) initFile() error {
	fullFileName := path.Join(f.filePath, f.fileName)
	fileObj, err1 := os.OpenFile(fullFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err1 != nil {
		fmt.Printf("open log file failed! err:%v", err1)
		return err1
	}
	errFileObj, err2 := os.OpenFile(fullFileName+".err", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err2 != nil {
		fmt.Printf("open err log file failed! err:%v", err2)
		return err2
	}
	f.fileObj = fileObj
	f.errFileObj = errFileObj
	return nil
}
文件 *** 作忘记了可以去回顾一下哦,超级详细友好的gogo哦 go的文件 *** 作详解
文件分割 *** 作 重点来啦,详细看我们可以通过重命名再打开的形式来不断获取新的文件重命名我们可以加上时间戳来表示 1. 建立一个时间戳,用来拼接到需要分割的文件名后面
nowStr := time.Now().Format("20060102150405.000")
这个是格式哈,忘记了可以去看下go的 time包的应用 go基础之time包详解 2. 通过file.stat()方法来获取老的文件名字
3. 将老文件名和新文件名称都拼接起来
	fileInfo, err := file.Stat()
	if err != nil {   // 特殊判断
		fmt.Printf("get file info failed! err:%v\n", err)
		return nil, err
	}
	logName := path.Join(f.filePath, fileInfo.Name()) // 获取老的文件名
	newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr)
4. 关闭当前文件(只有关闭当前文件才能进行重命名的 *** 作哦)
5. 进行重命名
	os.Rename(logName, newLogName)
直接用就好,啊哈哈哈哈哈哈,舒服 6. 打开一个新的日志文件
7. 将新的文件对象赋值给构造器对象里面fileObj方便下次的打印,这样就完成了对文件的切割啦 当然啦,在进行切割的时候,我们需要判断一下文件的大小是否超过我们在构造器里面设置的maxSize 文件的大小可以通过file.Stat()来获取
	fileInfo, err := file.Stat()
	判断 fileInfo.Size() >= f.maxFileSize 即可
最后我们来运行一下吧

我们设置了10 * 1024 * 1024 也就是10M哈,可以看出,运行完以后在左边也是被切割出来一个文件哈,然后我们文件里看看

啊哈哈和,正好10M哈,完美实现,结尾散花,啊哈哈哈哈哈哈 等等,等等 因为客户端和服务器都是实现了差不多相同的日志等级的打印方法,所以我们还可以做一个接口来装,后面使用的就会方便啦。
// Logger 接口
type Logger interface {
	Debug(format string, a ...interface{})
	Trace(format string, a ...interface{})
	Info(format string, a ...interface{})
	Warning(format string, a ...interface{})
	Error(format string, a ...interface{})
	Fatal(format string, a ...interface{})
}

号拉号拉,肝一天了,今天放个假啦!!!

对了,接口忘记了欢迎来看 (笑哭😀)

go基础之 接口详解 拜拜咯,一定要坚持下去。 日志库开发源码

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/995884.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存