在Golang中,日志记录是软件开发的一个重要方面,因为它能帮助开发人员监控、排查故障并分析应用程序的行为。
核心要点
- Go 日志选型可以从标准库
log起步,但生产环境通常更需要结构化日志、日志级别、格式控制和上下文关联。 slog把结构化日志带入标准库,适合作为新项目的默认选择之一。- Logrus、Zap、Zerolog 都能做结构化日志,但在性能、API 风格、生态兼容性和易用性上侧重点不同。
- Web 框架集成日志时,要重点记录请求 ID、用户 ID、路由、状态码、延迟、错误和 trace 关联信息。
- 好日志不是越多越好,而是能在排障、审计、告警和分析时快速回答“发生了什么、影响谁、为什么发生、下一步看哪里”。
Go 日志库怎么选
| 日志方案 | 适合场景 | 主要特点 | 需要补齐的能力 |
|---|---|---|---|
标准库 log |
小工具、示例代码、简单命令行程序 | 依赖少、使用简单 | 结构化、级别、格式、轮转 |
标准库 slog |
新项目、希望减少第三方依赖的服务 | 标准库结构化日志、键值对输出 | 结合 handler、采集器和告警体系 |
| Logrus | 已有项目、生态兼容优先 | API 成熟、插件多 | 高吞吐场景需关注性能 |
| Zap | 高性能服务、低分配日志 | 结构化、高性能、生产配置友好 | API 相对更工程化 |
| Zerolog | 追求极简和低分配的结构化日志 | 链式 API、JSON 输出简洁 | 团队需统一字段规范 |
标准库 log
Go 的标准库包含一个名为 log 的基本日志包。它提供了一种向控制台记录消息的简单方法:
package main
import (
"log"
)
func main() {
log.Println("This is a log message")
}
然而,标准的log包比较简约,可能不适用于更复杂的日志记录需求,例如日志轮转、日志级别或日志格式化。
标准库 slog
最近,Go 标准库引入了一个名为 slog 的新日志包,为标准库带来了结构化日志功能。结构化日志采用键值对形式,因此可以快速且可靠地进行解析、过滤、搜索和分析。
与 log 包相比,slog 包提供了更高级的日志功能,建议在生产环境中使用。
以下是在Go中使用slog包进行日志记录的示例:
package main
import (
"os"
"log/slog"
)
func main() {
textHandler := slog.NewTextHandler(os.Stdout)
logger := slog.New(textHandler)
logger.Info("Usage Statistics",
slog.Int("current-memory", 50),
slog.Int("min-memory", 20),
slog.Int("max-memory", 80),
slog.Int("cpu", 10),
slog.String("app-version", "v0.0.1-beta"),
)
}
要在Go中使用slog包将日志记录到文件,可以使用slog.NewJSONHandler函数创建一个新的处理器,以JSON格式将日志写入文件。以下是一个示例:
package main
import (
"os"
"log/slog"
)
func main() {
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer file.Close()
jsonHandler := slog.NewJSONHandler(file)
logger := slog.New(jsonHandler)
logger.Info("Application started",
slog.String("app-version", "v0.0.1-beta"),
)
}
Logrus
为了满足更高级的日志记录需求,许多开发者会转而使用第三方日志记录库。一个受欢迎的选择是“logrus”,它提供了功能更丰富的日志记录体验。你可以通过以下方式安装它:
go get github.com/sirupsen/logrus
以下是如何使用Logrus库的示例:
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
// Create a new logger instance
log := logrus.New()
// Set the log level (optional)
log.SetLevel(logrus.InfoLevel)
// Log messages
log.WithFields(logrus.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
Logrus 提供了诸如结构化日志、日志级别、自定义格式化器等功能。
Zap
Go 生态系统中另一个流行的日志库是 Zap,它以高性能和结构化日志功能而闻名。你可以使用以下命令安装它:
go get go.uber.org/zap
以下是如何使用 Zap 库的示例:
package main
import (
"go.uber.org/zap"
)
func main() {
// Create a logger
logger, _ := zap.NewProduction()
// Log messages
logger.Info("This is an info message", zap.String("key", "value"))
}
Zap支持高效日志记录,内存分配少,且具有丰富的功能。
Zerolog
Zerolog 是一款快速且简洁的结构化日志工具。它专注于减少内存分配,并为结构化日志记录提供简洁的应用程序接口(API)。无论是基础还是高级的日志记录需求,它都能满足。
你可以通过以下方式安装 Zerolog:
go get github.com/rs/zerolog
以下是如何使用 Zerolog 库的示例:
package main
import (
"os"
"github.com/rs/zerolog"
)
func main() {
// Create a new logger instance
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
// Log messages
logger.Info().Str("key", "value").Msg("This is an info message")
logger.Error().Err(fmt.Errorf("An error occurred")).Msg("This is an error message")
}
与 Go 框架集成
使用Go框架构建Web应用程序时,妥善集成日志功能以捕获HTTP请求详情、错误和性能指标至关重要。主流的Go Web框架会提供中间件或内置支持来实现日志集成。
对于Gin应用程序,你可以集成结构化日志记录,以自动记录传入的请求、响应时间和错误。OpenTelemetry Gin中间件会捕获HTTP跨度,并允许你将日志与跟踪相关联。
同样,Echo框架提供了用于日志记录的中间件,该中间件可与Logrus和Zap等库无缝协作,使你能够记录请求元数据和跟踪信息。
Beego 还通过过滤器支持日志集成,使您能够捕获请求详情并与 OpenTelemetry 集成,以实现全面的可观测性。
这些框架集成确保您的日志包含来自HTTP请求的有价值上下文,从而更轻松地排查生产环境中的问题。
最佳实践
有效的日志记录需要在提供足够的信息用于排查问题和避免信息过载之间取得平衡。请根据应用程序和组织的特定要求及限制条件,调整你的日志记录做法。
结构化日志记录。 采用结构化日志记录,它允许您将键值对或结构化数据附加到日志条目。这使得以结构化方式过滤和分析日志变得更加容易。
日志级别。 使用日志级别(例如INFO、DEBUG、WARN、ERROR)对日志消息进行分类。这使你能够控制日志的详细程度,并在调试或故障排除时专注于相关信息。
上下文信息。 在日志消息中包含相关的上下文信息。例如,包含请求ID、用户ID和时间戳。这有助于跨组件追踪问题。
错误包装。 记录错误时,请使用错误包装来保留原始错误上下文。为此,您可以使用“github.com/pkg/errors”之类的包或内置的“fmt.Errorf”。
字段规范。 统一服务名、环境、版本、trace ID、request ID、user ID、错误类型等字段名称。字段名混乱会直接降低日志检索和告警质量。
敏感信息处理。 不要把密码、Token、身份证号、银行卡号等敏感数据直接写入日志。必要时做脱敏、哈希或采样。
监控和告警。 为应用程序的日志设置监控和告警。这有助于您在出现错误或异常模式时及时获悉。日志告警可参考:
常见问题
Q1:新 Go 项目应该优先选哪个日志库?
A:如果没有特殊性能或生态要求,可以优先评估标准库 slog。它提供结构化日志能力,也减少了第三方依赖。
Q2:Zap 和 Zerolog 的核心差异是什么? A:两者都关注性能和结构化日志。Zap 更偏生产工程化配置,Zerolog API 更简洁、输出 JSON 更直接,具体选择取决于团队习惯和框架集成。
Q3:日志和 Trace 如何配合排障? A:日志里应带上 trace ID 或 request ID,这样从告警、链路追踪或某条错误日志出发时,可以快速跳转到同一次请求的上下文。
原文:https://uptrace.dev/blog/golang-logging