示例说明
printf 风格日志是人类可读、机器难解析的两难。slog 把属性键值化:JSONHandler 输出可直接索引的行,With 派生的子记录器自动携带绑定字段——支付处理器绑定订单号与金额后,内部所有日志天然带上业务上下文。Group 属性把相关字段组织成嵌套对象,风险评分这类复合信息不再平铺。
技术要点
- With 派生携带绑定上下文的记录器
- Group 把相关字段组织成嵌套对象
- Context 系列方法继承追踪上下文
代码解读
根记录器绑定服务与区域两个维度;PaymentHandler 的 With 在每次结算时追加订单与金额。超额走 ErrorContext 附带拒绝原因与 risk 分组;正常路径两条 Info。SetDefault 之后包级函数也共享绑定上下文。handler2 演示按级别分流到标准错误流的形态。
使用建议
级别阈值在生产调 info 以上控制量。上下文字段防高基数,订单号可以用户 ID 不行。自定义 Handler 可实现敏感字段脱敏。
代码示例
package main
import (
"context"
"errors"
"log/slog"
"os"
)
type PaymentHandler struct {
logger *slog.Logger
}
func (h *PaymentHandler) Settle(ctx context.Context, orderID string, amount int) error {
logger := h.logger.With(
slog.String("order_id", orderID),
slog.Int("amount_cents", amount),
)
logger.InfoContext(ctx, "settlement started")
if amount > 1_000_000 {
logger.ErrorContext(ctx, "settlement rejected",
slog.String("reason", "single-charge limit"),
slog.Group("risk",
slog.Int("score", 91),
slog.String("band", "high"),
),
)
return errAmountTooHigh
}
logger.InfoContext(ctx, "settlement completed")
return nil
}
var errAmountTooHigh = errors.New("amount exceeds single-charge limit")
func main() {
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})
logger := slog.New(handler).With(
slog.String("service", "billing"),
slog.String("region", "cn-north"),
)
payments := &PaymentHandler{logger: logger}
_ = payments.Settle(context.Background(), "ord-42", 250_000)
_ = payments.Settle(context.Background(), "ord-43", 2_500_000)
slog.SetDefault(logger)
slog.Info("default logger now carries bound context")
}