log.setflags仅控制日志前缀中的元信息(如时间、文件名、行号),不影响消息体;默认为log.lstdflags(含日期和时间但不含文件行号),需显式添加log.lshortfile等才显示位置信息。

log.SetFlags 会影响哪些输出内容
调用 log.SetFlags 不会改变日志消息体本身,只控制前缀是否包含时间、文件名、行号等元信息。默认值是 log.LstdFlags(即 log.Ldate | log.Ltime),但**不包含文件和行号**——这点常被误认为“没生效”。
- 要显示
file:line,必须显式添加log.Lshortfile或log.Llongfile -
log.Lshortfile输出main.go:23,log.Llongfile输出完整路径,如/home/user/project/main.go:23 - 如果同时设了
log.Ldate和log.Ltime,但没设log.Lmicroseconds,时间精度只到秒;加了它才会显示微秒(如10:23:45.123456) - 注意:一旦调用
log.SetFlags,它会**完全覆盖默认值**,不是追加。所以想保留时间又加行号,得写全:log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
用 log.SetPrefix 实现模块/层级标识
log.SetPrefix 是最轻量的自定义方式,适合在启动时为全局 logger 打上统一前缀,比如服务名或环境标识。但它不能动态变化,也不支持格式化占位符。
- 前缀字符串会**原样拼在每条日志开头**,后面紧跟冒号和空格(这是硬编码行为,无法关闭)
- 例如
log.SetPrefix("[API]")+log.Println("request received")→[API]: request received - 若需不同模块用不同前缀(如 "[DB]"、"[Cache]"),不能靠多次调用
SetPrefix切换——它只影响后续日志,且无并发安全保证 - 真正需要多前缀场景,应直接用
log.New创建多个独立 logger 实例,每个带自己的 prefix 和 flags
log.New 创建自定义 logger 的关键参数
用 log.New 是可控性最强的方式,它返回一个独立的 *log.Logger 实例,完全隔离于标准 logger(log.Printf 等使用的那个)。
- 第一个参数是
io.Writer,常见选择:os.Stdout、os.Stderr,或带缓冲的bufio.NewWriter(os.Stderr);写入文件时务必用os.OpenFile并设os.O_APPEND - 第二个参数是 prefix 字符串,同
SetPrefix行为,末尾自动加": " - 第三个参数是 flags,和
SetFlags的取值完全一致,比如log.Ldate | log.Ltime | log.Lshortfile - 注意:自定义 logger 的
Print/Printf方法**不会触发 panic recovery**,也不会受log.SetOutput影响——它是完全独立的
为什么不要在生产环境直接用 log.Printf
标准 logger(log.Printf 等)默认输出到 stderr,且无法单独关闭或重定向而不影响其他部分;更关键的是,它缺乏结构化能力,所有字段都挤在 message 字符串里,后期做日志采集、过滤、告警时非常吃力。
- 比如
log.Printf("user=%s action=login status=%s", user, status),看似清晰,但实际解析依赖正则,容易因空格、引号、特殊字符出错 - 标准库 log 不支持 level(Info/Warn/Error),所有日志等级一律平权,无法按需开关某类日志
- 如果项目中混用
log.Printf和多个log.New实例,log.SetFlags会意外修改所有标准 logger 的行为,但对自定义实例无效——这种隐式耦合极易引发线上日志格式不一致 - 真要轻量结构化,至少用
json.Marshal包一层再输出;但更推荐直接上zap或zerolog,它们在性能和字段表达力上远超标准 log
log 的定制边界很明确:能控制前缀、时间戳、位置信息,但无法做字段提取、级别过滤、异步写入或 JSON 序列化。一旦需求超出这些,就该果断切换 logger 库,而不是在 fmt.Sprintf 里套嵌套再套嵌套。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











