Golang中应该使用全局日志器还是传递实例?
Golang中应该使用全局日志器还是传递实例? 你好,
我是Go语言的新手,模块和包系统对我来说也比较陌生。
我想使用Zerolog(GitHub - rs/zerolog: Zero Allocation JSON Logger)进行日志记录。大多数时候,我看到人们直接调用它,甚至文档也使用全局实例。但这难道不被认为是糟糕的做法吗?我是否应该创建一个日志记录器实例并传递它?(尤其是在测试和模拟方面)
对我来说,在代码中的某个地方调用
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
并让它修改日志记录器,感觉很奇怪。
Go语言在这方面有所不同吗?我应该直接导入日志记录包并使用它吗?还是说这个例子GitHub - rs/zerolog: Zero Allocation JSON Logger是“干净”的做法?
提前感谢
更多关于Golang中应该使用全局日志器还是传递实例?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
啊,对了,忘记提 slog 了,我已经在所有项目中使用了它好几个月了。
更多关于Golang中应该使用全局日志器还是传递实例?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好 @falco467
当你在不同的包中需要不同的配置时,该怎么办呢?
是的,我也这么想 🙂 我觉得这些例子有点误导人(或者只是过于简化了)。
我会创建一个包级别的单一日志记录器 mypackage.log,它派生自全局实例,并在包内的所有地方使用它。
纯属个人观点,我会尽量避免使用全局变量。
通常,我会在 main() 函数中设置所有需要的内容,并将信息传递给其他组件。
这也会让测试变得更容易。
func main() {
fmt.Println("hello world")
}
我看不出传递一个实例相比使用全局字段有什么真正的好处。如果你只是定义一个全局变量“logger”并在各处使用它,你也可以像传递引用一样轻松地将其替换为另一个记录器。我认为这会让很多事情变得更简单。
并查看新的结构化日志包 slog 包 - golang.org/x/exp/slog - Go 包,该包将在 1.21 版本(下个月)移入标准库。
在你的 main.go 中创建一个单独的日志记录器实例,并将其传递到各处。这样做还有一个额外的好处:如果你想要更改日志记录器或日志记录选项,只需修改一个地方即可。
// 示例代码:创建并传递日志记录器实例
package main
import (
"log"
"os"
)
var logger *log.Logger
func init() {
logger = log.New(os.Stdout, "APP: ", log.Ldate|log.Ltime|log.Lshortfile)
}
func main() {
logger.Println("应用程序启动")
// 将 logger 传递给其他函数或模块
doSomething(logger)
}
func doSomething(l *log.Logger) {
l.Println("正在执行某些操作")
}
在Go语言中,关于全局日志器与传递实例的选择,主要取决于项目的具体需求。Zerolog同时支持两种方式,各有适用场景。
全局日志器的使用
Zerolog的全局日志器适合简单的应用场景:
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
)
func init() {
// 配置全局日志器
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.SetGlobalLevel(zerolog.InfoLevel)
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
func processData() {
// 直接使用全局日志器
log.Info().Msg("Processing data started")
log.Debug().Str("detail", "some debug info").Msg("Debug detail")
log.Error().Err(someError).Msg("Processing failed")
}
传递日志器实例
对于需要更好控制或测试的场景,传递实例更合适:
package main
import (
"github.com/rs/zerolog"
"os"
)
type Service struct {
logger zerolog.Logger
}
func NewService(logger zerolog.Logger) *Service {
return &Service{logger: logger}
}
func (s *Service) Process() {
s.logger.Info().Msg("Service processing started")
// 业务逻辑
}
// 在生产环境中使用
func main() {
logger := zerolog.New(os.Stderr).
With().
Timestamp().
Str("service", "myapp").
Logger()
svc := NewService(logger)
svc.Process()
}
// 在测试中模拟
func TestService(t *testing.T) {
// 使用测试输出
testLogger := zerolog.New(io.Discard).Level(zerolog.Disabled)
svc := NewService(testLogger)
// 测试逻辑
}
混合使用策略
实际项目中常采用混合策略:
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
)
// 包级别的默认日志器
var defaultLogger = zerolog.New(os.Stderr).With().Timestamp().Logger()
// 可配置的组件
type Component struct {
logger zerolog.Logger
}
func NewComponent(logger ...zerolog.Logger) *Component {
if len(logger) > 0 {
return &Component{logger: logger[0]}
}
return &Component{logger: defaultLogger}
}
func (c *Component) DoWork() {
c.logger.Info().Msg("Component working")
}
// 使用全局日志器作为后备
func helperFunction() {
log.Info().Msg("Using global logger")
}
配置管理示例
package config
import (
"github.com/rs/zerolog"
"io"
"os"
)
type LoggerConfig struct {
Level zerolog.Level
Output io.Writer
AddCaller bool
}
func NewLogger(cfg LoggerConfig) zerolog.Logger {
logger := zerolog.New(cfg.Output).
Level(cfg.Level).
With().
Timestamp()
if cfg.AddCaller {
logger = logger.Caller()
}
return logger.Logger()
}
// 应用配置
func SetupAppLogger() zerolog.Logger {
cfg := LoggerConfig{
Level: zerolog.InfoLevel,
Output: os.Stderr,
AddCaller: true,
}
return NewLogger(cfg)
}
测试友好设计
package service
import (
"github.com/rs/zerolog"
"testing"
)
type UserService struct {
logger zerolog.Logger
repo UserRepository
}
// 生产环境构造器
func NewUserService(logger zerolog.Logger, repo UserRepository) *UserService {
return &UserService{
logger: logger,
repo: repo,
}
}
// 测试辅助函数
func NewTestUserService(t *testing.T, repo UserRepository) *UserService {
testLogger := zerolog.New(io.Discard).Level(zerolog.Disabled)
return NewUserService(testLogger, repo)
}
func TestUserService(t *testing.T) {
mockRepo := &MockUserRepository{}
svc := NewTestUserService(t, mockRepo)
// 测试逻辑
}
选择依据:
- 小型工具或简单应用:全局日志器
- 大型项目或微服务:传递实例
- 需要测试和模拟:传递实例
- 库或框架:提供两种方式
Zerolog文档中的示例展示了两种模式,传递实例的方式在需要控制输出、级别或进行测试时确实是更清晰的做法。

