当前位置:首页 > 文章列表 > Golang > Go教程 > Go log/slog.NewTextHandler 怎样输出纯文本结构化日志

Go log/slog.NewTextHandler 怎样输出纯文本结构化日志

来源:17golang原创 2026-11-01 14:49:03 0浏览 收藏

如果 Go 服务已经使用 log/slog,但默认日志格式不够稳定、终端排障时字段不够直观,可以把 Handler 换成 slog.NewTextHandler。它会把一条结构化记录整理为便于阅读和解析的 key=value 文本,例如 level=INFO msg=started count=3。

官方资料:https://pkg.go.dev/log/slog#NewTextHandler

本文只解决一个问题:怎样用 NewTextHandler 选好输出目标、最低日志级别和字段策略。需要机器直接消费 JSON 时,应改用 slog.NewJSONHandler,不要把文本格式硬塞进日志采集链路。

Go slog TextHandler 从 Logger 经过 HandlerOptions 写入标准错误或文件的结构关系图
图1:NewTextHandler 将结构化记录整理为 key=value 文本并写入目标 Writer 的静态结构图。

先把结构化记录写成文本

NewTextHandler 接收两个参数:一个是实现了 io.Writer 的输出目标,另一个是可选的 *slog.HandlerOptions。创建 Handler 后,再交给 slog.New 得到 Logger。

package main

import (
    "log/slog"
    "os"
)

func main() {
    // 将文本日志写到标准错误,适合容器日志和本地终端排障。
    handler := slog.NewTextHandler(os.Stderr, nil)
    logger := slog.New(handler)

    // 结构化字段会以 key=value 形式出现在同一条日志中。
    logger.Info("worker started", "queue", "orders", "count", 3)
}

这段配置的关键不是“打印一行字符串”,而是保留了消息、级别和属性字段的结构。文本 Handler 会生成类似下面的结果:

time=2026-11-01T14:00:00+08:00 level=INFO msg="worker started" queue=orders count=3

实际时间会由运行时记录决定;排查文章中的输出只用于展示字段形状,不代表某一次本机运行结果。

输出到文件时,先明确 Writer 的生命周期

想把文本日志写入文件时,打开文件、处理错误,并让文件在进程退出前关闭。NewTextHandler 不负责替你管理 Writer,也不会替你创建目录。

package main

import (
    "log/slog"
    "os"
)

func main() {
    // 创建或覆盖示例日志文件;生产环境通常要接入轮转策略。
    file, err := os.OpenFile("service.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
    if err != nil {
        panic(err)
    }
    // 关闭动作放在创建成功之后,避免对空文件句柄调用 Close。
    defer file.Close()

    // Writer 的写入失败会由 Handler 在处理记录时返回给内部调用链。
    logger := slog.New(slog.NewTextHandler(file, nil))
    logger.Info("file logging enabled", "component", "scheduler")
}

如果服务运行在容器中,通常直接写 os.Stderr 更容易交给平台采集;如果必须写文件,还要把权限、轮转、磁盘满和多进程竞争纳入运维设计。

用 HandlerOptions 控制最低级别和来源

默认最低级别是 INFO。当 HandlerOptions 的 Level 设为 slog.LevelDebug 时,Debug 记录才会进入输出;AddSource 可以附加调用位置,但它会增加每条记录的字段长度。

package main

import (
    "log/slog"
    "os"
)

func newLogger(debug bool) *slog.Logger {
    level := slog.LevelInfo
    if debug {
        // 只在排障开关打开时放行 Debug,避免生产日志量失控。
        level = slog.LevelDebug
    }

    handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
        Level:     level,
        AddSource: debug,
    })
    return slog.New(handler)
}

func main() {
    logger := newLogger(true)
    logger.Debug("cache inspection", "key", "orders:1")
    logger.Info("request accepted", "status", 202)
}

这里的判断顺序很重要:先决定当前环境的最低级别,再创建 Handler。若日志没有输出,先确认记录级别是否低于 HandlerOptions.Level,不要先怀疑文本格式化失败。

Go slog HandlerOptions 过滤日志级别并整理 source 与属性字段的关系图
图2:HandlerOptions 对日志级别、source 字段和属性改写的静态关系图。

需要全局生效时设置默认 Logger

只创建一个局部 Logger,并不会改变 slog.Info 等顶层函数使用的默认 Handler。需要让应用中现有的顶层调用也输出为文本时,可以显式调用 slog.SetDefault。

package main

import (
    "log"
    "log/slog"
    "os"
)

func main() {
    // 统一默认输出,让 slog 顶层函数使用同一个文本 Handler。
    logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
    slog.SetDefault(logger)

    // 旧 log 包的标准输出也会接入默认 slog Handler。
    slog.Info("new logger ready", "mode", "text")
    log.Print("legacy call")
}

迁移旧代码时,这个入口很有用,但仍要留意旧日志的参数语义与字段命名。不要在多个初始化路径反复调用 SetDefault,否则排障时很难判断最终生效的是哪个 Writer。

用 With、WithGroup 和 ReplaceAttr 整理字段

服务日志通常有一批公共字段,例如服务名、环境和请求 ID。用 With 绑定公共字段,再用 WithGroup 给子系统字段加前缀,能减少重复和同名覆盖。

package main

import (
    "log/slog"
    "os"
)

func main() {
    // 公共字段只绑定一次,后续每条日志都会带上 service 和 env。
    base := slog.New(slog.NewTextHandler(os.Stderr, nil)).With(
        "service", "billing",
        "env", "prod",
    )
    worker := base.WithGroup("worker")

    // 分组后的字段会显示为 worker.id、worker.queue,降低重名歧义。
    worker.Info("job accepted", "id", "job-17", "queue", "invoice")
}

若需要删除时间、缩短源码路径或做敏感字段改写,可以在 HandlerOptions.ReplaceAttr 中返回空属性或替换后的属性。字段清理应该集中在 Handler 层完成,避免每个业务调用点各写一套脱敏逻辑。

package main

import (
    "log/slog"
    "os"
)

func main() {
    replace := func(groups []string, attr slog.Attr) slog.Attr {
        // 去掉顶层时间字段,让采集平台统一补时间。
        if attr.Key == slog.TimeKey && len(groups) == 0 {
            return slog.Attr{}
        }
        // 不把 token 原文写入日志;真实项目应覆盖更多敏感键。
        if attr.Key == "token" {
            return slog.String("token", "[redacted]")
        }
        return attr
    }

    logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
        ReplaceAttr: replace,
    }))
    logger.Warn("credential refreshed", "token", "secret-value")
}

按症状定位配置问题

现象优先检查处理方向
完全没有输出Writer 是否有效、初始化是否执行先换成 os.Stderr,再检查打开文件的错误
Info 有、Debug 没有HandlerOptions.Level需要调试时使用 slog.LevelDebug 或 LevelVar
字段同名难以区分业务 Logger 是否缺少分组用 WithGroup 给子系统字段加前缀
日志里出现敏感值是否集中配置 ReplaceAttr在 Handler 层做脱敏,不依赖调用者自觉
采集器要求 JSON输出协议而非可读性改用 NewJSONHandler,不要正则解析文本日志

结论

slog.NewTextHandler(writer, opts) 的最小可用组合是:先选定 Writer,再用 HandlerOptions 决定最低级别、source 字段和属性改写,最后交给 slog.New。本地排障适合输出到 os.Stderr 并放开 Debug;生产环境更应该固定字段策略、限制日志级别,并根据采集协议决定使用 TextHandler 还是 JSONHandler。

常见追问

NewTextHandler 和 NewJSONHandler 怎样选?

给人直接看、需要保留 key=value 可读性时选 TextHandler;下游是日志平台或程序解析时选 JSONHandler。

为什么设置了 TextHandler 但 slog.Info 还是旧格式?

通常只创建了局部 Logger,没有调用 slog.SetDefault。检查调用点使用的是自定义 Logger 还是顶层函数。

HandlerOptions.Level 能动态调整吗?

可以把 Level 设置为 *slog.LevelVar,再通过 Set 改变最低级别;修改时仍应限制权限和变更范围。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go hex.Dumper 怎样流式打印二进制调试信息Go hex.Dumper 怎样流式打印二进制调试信息
上一篇
Go hex.Dumper 怎样流式打印二进制调试信息
Go csv.Reader.ReadAll 怎样一次读取完整 CSV 并处理解析错误
下一篇
Go csv.Reader.ReadAll 怎样一次读取完整 CSV 并处理解析错误
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    824次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    819次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    906次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    879次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    701次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码