日志分析是程序开发和运维过程中非常重要的环节,通过合理的日志采集和分析,我们可以快速定位程序异常、统计业务数据、监控服务运行状态。对于Golang新手来说,掌握基础的日志分析实现方法,能够有效提升问题排查效率。

日志分析的基础准备
在做日志分析之前,首先需要规范日志的输出格式,方便后续解析。Go语言标准库自带的log包功能比较简单,实际开发中更推荐使用第三方日志库logrus,它支持结构化日志输出,更适合做分析。
首先安装logrus依赖:
go get github.com/sirupsen/logrus
规范日志输出格式
我们可以配置logrus输出JSON格式的日志,包含时间、日志级别、消息内容、自定义字段等信息,示例代码如下:
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
// 设置日志格式为JSON
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置日志级别为Info及以上
logrus.SetLevel(logrus.InfoLevel)
// 输出带自定义字段的日志
logrus.WithFields(logrus.Fields{
"user_id": 1001,
"action": "login",
"ip": "192.168.0.1",
}).Info("用户登录成功")
logrus.WithFields(logrus.Fields{
"user_id": 1002,
"action": "order",
"order_id": "ORD20240501",
}).Error("订单创建失败")
}
运行上述代码后,会输出类似下面的JSON日志,每个字段都清晰可辨,方便后续解析:
{"action":"login","ip":"192.168.0.1","level":"info","msg":"用户登录成功","time":"2024-05-01 10:00:00","user_id":1001}
{"action":"order","level":"error","msg":"订单创建失败","order_id":"ORD20240501","time":"2024-05-01 10:00:05","user_id":1002}
日志采集与存储
日志输出后需要采集存储,常见的方案有两种:一是输出到本地文件,后续读取文件做分析;二是直接输出到消息队列或者日志收集系统,不过新手阶段先掌握本地文件的处理即可。
我们可以修改logrus的配置,将日志输出到本地文件:
package main
import (
"github.com/sirupsen/logrus"
"os"
)
func initLogger() {
// 打开日志文件,如果不存在则创建,追加写入
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logrus.Fatal("打开日志文件失败:", err)
}
// 同时输出到文件和标准输出
logrus.SetOutput(io.MultiWriter(os.Stdout, file))
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
logrus.SetLevel(logrus.InfoLevel)
}
func main() {
initLogger()
// 模拟业务日志输出
for i := 0; i < 5; i++ {
logrus.WithFields(logrus.Fields{
"request_id": i,
"path": "/api/user/list",
"status": 200,
}).Info("接口请求完成")
}
}
日志内容解析与分析
日志存储到文件后,我们就可以编写代码读取文件内容,解析每一条日志做分析。下面以统计不同日志级别的数量为例,讲解具体的实现逻辑。
读取并解析日志文件
首先读取app.log文件的所有内容,按行分割,每一行就是一个JSON格式的日志,然后解析JSON提取日志级别,统计各级别的数量:
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
)
// 定义日志结构体,匹配JSON日志的字段
type LogEntry struct {
Level string `json:"level"`
Msg string `json:"msg"`
Time string `json:"time"`
// 其他字段按需定义即可
}
func analyzeLog() {
// 打开日志文件
file, err := os.Open("app.log")
if err != nil {
fmt.Println("打开日志文件失败:", err)
return
}
defer file.Close()
// 统计各级别日志数量
levelCount := make(map[string]int)
scanner := bufio.NewScanner(file)
// 逐行读取文件
for scanner.Scan() {
line := scanner.Text()
var entry LogEntry
// 解析JSON日志
err := json.Unmarshal([]byte(line), &entry)
if err != nil {
// 跳过解析失败的日志行
continue
}
levelCount[entry.Level]++
}
// 输出统计结果
fmt.Println("日志级别统计结果:")
for level, count := range levelCount {
fmt.Printf("级别: %s, 数量: %dn", level, count)
}
}
func main() {
analyzeLog()
}
扩展分析场景
除了统计日志级别,我们还可以做更多分析,比如统计某个接口的请求次数、统计错误日志的出现时间分布、提取特定用户的操作记录等。下面以统计接口请求路径的访问次数为例,给出核心代码片段:
// 在LogEntry结构体中增加Path字段
type LogEntry struct {
Level string `json:"level"`
Msg string `json:"msg"`
Time string `json:"time"`
Path string `json:"path"`
}
// 统计接口访问次数
func countApiRequest() {
file, _ := os.Open("app.log")
defer file.Close()
pathCount := make(map[string]int)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
var entry LogEntry
if err := json.Unmarshal([]byte(scanner.Text()), &entry); err == nil {
if entry.Path != "" {
pathCount[entry.Path]++
}
}
}
fmt.Println("接口访问统计:")
for path, count := range pathCount {
fmt.Printf("路径: %s, 访问次数: %dn", path, count)
}
}
注意事项
- 日志输出时要避免打印敏感信息,比如用户密码、身份证号等内容,防止信息泄露。
- 日志文件要定期切割,避免单个文件过大导致读取解析效率下降,可以使用
lumberjack等库做日志轮转。 - 解析日志时要注意处理异常格式的行,避免程序因为某一行日志格式错误就崩溃。
- 如果日志量非常大,单机分析效率会很低,这时候可以考虑使用ELK等专业的日志分析系统,不过新手阶段先掌握基础的文件分析逻辑即可。