您的位置:首页 > 文旅 > 美景 > Go日志系统

Go日志系统

2024/10/5 19:20:53 来源:https://blog.csdn.net/littleschemer/article/details/141828665  浏览:    关键词:Go日志系统

1.日志系统

服务器日志是服务器运行过程中记录的各种信息的集合,它们对于系统管理员和开发人员来说具有重要的意义。例如, 调试,监控,行为分析等等。 

go自带一个log库,但与java生态存在同样的窘境,就是被第三方工具盖住了锋芒。例如java日志系统一般使用的是slfj坐门面,log4j或log4j2或logback做实现。

Go的log包提供了简单的日志记录功能,但它的输出格式和功能相对固定,不支持日志级别和结构化日志。如果要使用一些高级日志功能,可以采用一些第三方日志库,例如logrus,zerolog等。本文使用logrus进行演示。

2.logrus 介绍

logrus 是一个开源的 Go 语言日志库,它提供了一个简单、灵活且功能丰富的日志记录系统,被广泛用于 Go 应用程序中。以下是 logrus 的一些主要特性:

  1. 结构化日志logrus 支持结构化日志记录,这意味着你可以以键值对的形式记录日志,使得日志更易于解析和处理。

  2. 多种日志级别:它支持多种日志级别,包括调试(debug)、信息(info)、警告(warn)、错误(error)和致命(fatal)级别。这有助于在不同的环境中控制日志的详细程度。

  3. 自定义日志格式logrus 允许你自定义日志的输出格式,包括文本格式和 JSON 格式。你可以通过实现 Formatter 接口来创建自定义格式。

  4. 钩子(Hooks)logrus 支持钩子,这些钩子可以在日志记录事件发生时执行额外的动作,比如发送警报、记录到外部系统等。

  5. 日志轮转:虽然 logrus 本身不直接支持日志轮转,但可以通过集成第三方库(如 lumberjack)来实现日志文件的自动轮转。

  6. 并发安全logrus 是并发安全的,可以在多个 goroutine 中使用而不需要额外的同步措施。

下面是一个简单的例子:

package mainimport ("github.com/sirupsen/logrus"
)func main() {// 创建一个新的Loggerlogger := logrus.New()// 设置日志输出格式为JSONlogger.Formatter = &logrus.JSONFormatter{}// 设置日志级别logger.Level = logrus.InfoLevel// 记录一条信息级别的日志logger.Info("这是一条信息级别的日志")// 记录一条错误级别的日志logger.Error("这是一条错误级别的日志")// 记录带有字段的结构化日志logger.WithFields(logrus.Fields{"animal":

如果要实现日志翻页功能(每隔一天,或者超过XX大小自动翻页),需要结合lestrrat-go/file-rotatelogs工具。

3.合理的日志分类

在生产环境,主要有三大类日志,一种是系统日志,主要用于记录程序的行为,用于排查bug,行为监控等;一种则是运营日志,主要用于数据分析(如果是游戏服务器,当程序出现bug,可用于补偿或者回收)。最后一种是异常日志,用于修复bug。

对于系统日志,一般无需结构化输出,只有肉眼可分析即可。例如可以用下面的格式:

2024-09-08 19:46:54 [info] ----test1---
2024-09-08 19:46:54 [info] game server is starting ...
2024-09-08 19:48:21 [info] ----test2---
2024-09-08 19:48:21 [info] game server is starting ...
2024-09-08 19:50:14 [info] ----test3---
2024-09-08 19:50:14 [info] game server is starting ...

对于运营日志,如果服务器是分布式部署,需要将不同进程产生的运营日志统一采集到指定的目录,例如通过 ELK(Elasticsearch、Logstash、Kibana)或者hadoop。因此,运营日志一定是结构化日志(类似于mysql的表,有统一的格式),例如可以用下面的格式:

time|1725276165776|model|request|url|/var/queryUserGameVars|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276166035|model|request|url|/var/queryUserGameVars|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276166288|model|request|url|/array/queryUserGameVars|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276166541|model|request|url|/array/queryUserGameVars|remoteIp|103.167.134.39, 172.71.214.147|localIp|127.0.0.1
time|1725276188600|model|request|url|/player/getProgress|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276188852|model|request|url|/player/getProgress|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276195164|model|request|url|/player/getArchives|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276195421|model|request|url|/player/getArchives|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276197467|model|request|url|/player/getArchives|remoteIp|103.167.134.39, 172.71.214.147|localIp|127.0.0.1
time|1725276199553|model|request|url|/player/getArchives|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276206665|model|request|url|/template/create|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1
time|1725276206926|model|request|url|/template/create|remoteIp|103.167.134.39, 172.71.214.146|localIp|127.0.0.1

3.1.系统日志代码

func createConsoleLog() *logrus.Logger {logger := logrus.New()logger.Formatter = &consoleLogFormatter{}// 设置Logger的输出writer, _ := rotatelogs.New("logs/app/"+"app.%Y%m%d",rotatelogs.WithMaxAge(time.Duration(24)*time.Hour),)logger.Out = writer// 设置Logger的日志级别logger.Level = logrus.InfoLevelreturn logger
}

logrus 默认提供了JSONFormatter和TextFormatter两种格式化工具,但日志格式跟笔者的习惯不是很吻合,所以使用了自定义的格式工具,只需实现下面的方法即可。

type Formatter interface {Format(*Entry) ([]byte, error)
}

代码如下:

type consoleLogFormatter struct{}// Format 实现 logrus.Formatter 接口的 Format 方法。
func (f *consoleLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {var b *bytes.Bufferif entry.Buffer != nil {b = entry.Buffer} else {b = &bytes.Buffer{}}// 按照自定义格式写入日志信息_, _ = fmt.Fprintf(b, "%s [%s] %s\n", entry.Time.Format(time.DateTime), entry.Level, entry.Message)return b.Bytes(), nil
}

程序调用的时候只需如下:

// Printf 记录一条日志
func Printf(v string) {consoleLog.Info(v)
}

需要注意的是,该方法只有一个字符串参数,外部调用需要将完整的字符串内容拼完再传进来。

3.2.运营日志代码

运营日志一般需要分模块,例如游戏里的商店、任务、抽奖等等,每个模块可以使用独立文件,或者全部模块放在同一个文件,通过类型进行区分,均可。

变量定义,申明日志类型,名称,以及用一个map保存已经创建好的日志对象

type LogType int// 日志类型枚举,每一个类型对应独立的文件
const (Admin LogType = iotaAPPLICATION
)var logName = map[LogType]string{Admin:       "admin",APPLICATION: "application",
}var (logs       map[string]*logrus.Logger
)

使用自定义的格式化工具,创建logger对象

type businessLogFormatter struct{}// Format 实现 logrus.Formatter 接口的 Format 方法。
func (f *businessLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {var b *bytes.Bufferif entry.Buffer != nil {b = entry.Buffer} else {b = &bytes.Buffer{}}_, _ = fmt.Fprintf(b, "%s", entry.Message)return b.Bytes(), nil
}func createBusinessLog(name string) *logrus.Logger {logger := logrus.New()logger.Formatter = &businessLogFormatter{}writer, _ := rotatelogs.New("logs/"+name+"/"+name+".%Y%m%d",rotatelogs.WithMaxAge(time.Duration(24)*time.Hour),)logger.Out = writer// 设置Logger的日志级别logger.Level = logrus.InfoLevelreturn logger
}

在初始化时,使用map缓存名称与对应的日志对象

func init() {logs = make(map[string]*logrus.Logger)for _, logType := range logName {logger := createBusinessLog(logType)logs[logType] = logger}
}

日志打印接口

func Info(name LogType, args ...interface{}) {if len(args)%2 != 0 {panic("log arguments must be odd number")}logger := logs[logName[name]]sb := &strings.Builder{}sb.WriteString("time|")sb.WriteString(fmt.Sprintf("%d", time.Now().UnixNano()/1000000))sb.WriteString("|")for i := 0; i < len(args); i += 2 {key, ok := args[i].(string)if !ok {panic(fmt.Sprintf("key is not a string: %v", args[i]))}value := args[i+1]sb.WriteString(key)sb.WriteString("|")sb.WriteString(fmt.Sprintf("%v", value))sb.WriteString("|")}sb.WriteString("\n")logger.Info(sb.String())
}

该函数比较复杂,函数的第一个参数代表日志的类型,第二个参数是一个变长参数,因为要拼接key,value的格式,需要变长参数的数量必须是偶数。将数组的奇数项作为key,偶数项作为value。调用代码示例:

log.Info(log.APPLICATION, "key1", "value1", "key2", "value2", "key3", "value3")

3.3.系统异常日志

异常日志没什么好讲的,最重要的是保留完整的堆栈日志,能够通过日志分析程序异常


func createErrorLog() *logrus.Logger {logger := logrus.New()logger.Formatter = &logrus.TextFormatter{ForceColors: true}writer, _ := rotatelogs.New("logs/err/"+"error.%Y%m%d",rotatelogs.WithMaxAge(time.Duration(24)*time.Hour),)logger.Out = writerlogger.Level = logrus.InfoLevelreturn logger
}

调用时只需传递异常参数即可

func Error(err error) {if err != nil {stack := make([]byte, 1024)n := runtime.Stack(stack, false)errorLog.WithFields(logrus.Fields{"error": err,}).Error(string(stack[:n]))}
}

完整代码请移步:

--> go游戏服务器

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com