跳转到内容

Log Primer

当你已经通过 稳定根 确认改动仍属于默认服务路径,而且问题进一步收窄到服务如何输出结构化日志时,就打开这一页:组件接受什么接口、基础 logger 如何构造,以及哪些字段助手适用。

log 拥有 StructuredLogger 接口、用 NewLogger 进行规范的基础 logger 构造,以及日志字段助手。只允许导入 stdliblog 不了解 app bootstrap、routing 或特定功能的行为。

logger := log.NewLogger()
logger.Info("server starting", log.Field("port", 8080))
logger.Error("db ping failed", log.Field("err", err))
  • 你正在跨模块边界接受或传递 StructuredLogger
  • 你正在调用 NewLogger() 为应用构造基础 logger
  • 你正在用字段助手 API 向日志事件添加结构化字段
  • 你正在实现满足 StructuredLogger 的新日志后端
  • 改动关于导出管道、聚合或将日志发送到外部系统 — 那属于 x/observability
  • 改动添加了 CLI bootstrap 助手、默认单例构造或包级全局状态
  • 工作关于请求级访问日志、字段脱敏策略或日志采样 — 那些是 middleware 关注点
  • 改动是特定功能的日志语义或仓库级测试助手
  1. log/module.yaml
  2. log/logger.go
  3. log/fields.go
  4. log/glog.go
  5. reference/standard-service/internal/app/app.go
这些工作适合留在 log一旦变成这些问题就应移出
StructuredLogger 接口 — 所有 logger 满足的契约特定功能的 logger 适配器或应用领域的日志语义
NewLogger() — 返回 StructuredLogger 的规范基础构造CLI 助手、包级全局单例或默认 logger 注册
字段助手:任何 logger 都可接受的通用结构化字段仓库级测试助手或特定功能的字段词汇表
实现满足接口的新后端(如 JSON、noop)导出管道 wiring、聚合或外部传输适配器
import "github.com/spcent/plumego/log"
// 文本输出到 stderr — 开发环境默认
logger := log.NewLogger()
// JSON 输出 — 生产环境
logger := log.NewLogger(log.LoggerConfig{
Format: log.LoggerFormatJSON,
Level: log.INFO,
})
// 丢弃所有输出 — 在测试中很有用
logger := log.NewLogger(log.LoggerConfig{Format: log.LoggerFormatDiscard})

LoggerFormat 选项:LoggerFormatTextLoggerFormatJSONLoggerFormatDiscard

logger.Info("server started", log.Fields{"addr": ":8080"})
logger.Warn("config missing, using default", log.Fields{"key": "TIMEOUT"})
logger.Error("database unavailable", log.Fields{"err": err.Error()})

当没有结构化字段时可以省略 Fields:

logger.Info("shutdown complete")
logger.InfoCtx(ctx, "request processed", log.Fields{
"user_id": userID,
"duration_ms": duration.Milliseconds(),
})

WithFields 创建子 logger,使其在后续所有调用中都携带这些字段:

reqLogger := logger.WithFields(log.Fields{
"request_id": contract.RequestIDFromContext(ctx),
"method": r.Method,
"path": r.URL.Path,
})
reqLogger.Info("handler started")
reqLogger.Error("handler failed", log.Fields{"err": err.Error()})

With 是单字段的简写:

svcLogger := logger.With("service", "user-api")

在模块边界处接受 log.StructuredLogger,而非具体类型:

type UserRepository interface {
Find(ctx context.Context, id string) (*User, error)
}
type UserService struct {
repo UserRepository
logger log.StructuredLogger
}
func NewUserService(repo UserRepository, logger log.StructuredLogger) *UserService {
return &UserService{repo: repo, logger: logger}
}

app.New 中从 core app 实例获取 logger:

app := core.New(cfg.Core, core.AppDependencies{Logger: log.NewLogger()})
svc := NewUserService(db, app.Logger())
常量说明
log.DEBUG详细 — 用于开发
log.INFO正常运营消息
log.WARNING非致命问题
log.ERROR需要关注的错误
log.FATAL记录后调用 os.Exit(1)

日志是几乎每个组件都会接触的唯一可观测性工具。如果接口泄漏了 app-bootstrap 假设或引入了功能包,耦合就会扩散到处。log 将导入列表保持为 stdlib,专门是为了让任何稳定根或扩展都能接受 StructuredLogger 而不产生循环依赖。永远不要记录密钥 — 这是事后最难审计的唯一硬性规则。