在Golang的标准库中,log包提供了基础的日志功能,其中log.Logger结构体是日志输出的核心载体,默认情况下输出的日志仅包含时间戳和日志内容,缺少日志级别、调用上下文等关键信息。通过自定义log.Logger的输出格式,可以按需添加各类辅助信息,让日志更便于排查问题。

log.Logger的核心结构
log.Logger的定义位于log包中,核心字段包括输出目标、前缀字符串和日志标志位三个部分,理解这些字段是自定义格式的基础。
type Logger struct {
mu sync.Mutex // 保证日志输出并发安全
prefix string // 日志前缀,每次输出都会先打印该内容
flag int // 日志标志位,控制输出哪些额外信息
out io.Writer // 日志输出目标,默认是os.Stderr
buf []byte // 日志输出的临时缓冲区
}
其中flag字段是控制默认格式的关键,log包预定义了多个标志位常量,可以通过位或运算组合使用:
log.Ldate:输出本地日期,格式为2009/01/23log.Ltime:输出本地时间,格式为01:23:23log.Lmicroseconds:输出微秒级时间,需要在Ltime基础上使用log.Llongfile:输出完整文件名和行号log.Lshortfile:输出文件名和行号,会覆盖Llongfilelog.LUTC:使用UTC时间而非本地时间log.Lmsgprefix:将前缀放在日志内容之后,而非开头log.LstdFlags:默认值,等价于Ldate | Ltime
自定义日志格式的基础实现
如果仅需要基于现有标志位调整格式,可以直接创建log.Logger实例并指定对应的flag和prefix参数。比如需要输出日期、时间、短文件名和日志级别前缀,可以这样实现:
package main
import (
"log"
"os"
)
func main() {
// 创建自定义Logger,输出到标准输出,前缀为[INFO],标志位包含日期、时间、短文件名
logger := log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime|log.Lshortfile)
logger.Println("这是一条自定义格式的日志")
}
上述代码运行后会输出类似2024/05/20 14:30:00 main.go:12: [INFO] 这是一条自定义格式的日志的内容,已经比默认格式多了文件名和行号信息。
完全自定义日志格式的实现
如果预定义的标志位无法满足需求,比如需要添加日志级别、进程ID、自定义时间戳格式等信息,就需要自定义log.Logger的输出逻辑。核心思路是自定义一个实现了io.Writer接口的结构体,在写入日志内容时先按照自定义格式拼接完整内容,再输出到目标位置。
步骤1:定义自定义Writer结构体
我们创建一个customLoggerWriter结构体,内部包含输出目标和日志级别字段,实现Write方法来自定义格式:
package main
import (
"fmt"
"io"
"log"
"os"
"time"
)
// 自定义Writer结构体
type customLoggerWriter struct {
output io.Writer // 实际输出目标
level string // 日志级别
}
// 实现Write方法,自定义日志格式
func (w *customLoggerWriter) Write(p []byte) (n int, err error) {
// 拼接自定义格式:时间[级别] 日志内容
// 时间格式设置为2006-01-02 15:04:05.000
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
// 拼接完整日志内容,注意p已经包含换行符,不需要额外添加
fullLog := fmt.Sprintf("%s [%s] %s", timestamp, w.level, p)
// 写入实际输出目标
return w.output.Write([]byte(fullLog))
}
步骤2:创建自定义Logger实例
使用自定义的Writer创建log.Logger实例,就可以输出完全自定义格式的日志:
func main() {
// 创建自定义Writer,输出到标准输出,日志级别为INFO
writer := &customLoggerWriter{
output: os.Stdout,
level: "INFO",
}
// 创建Logger,prefix设为空,flag设为0,因为格式已经在Writer中处理
logger := log.New(writer, "", 0)
logger.Println("这是一条完全自定义格式的日志")
logger.Println("这是另一条自定义格式的日志")
}
运行上述代码后,输出内容如下:
2024-05-20 14:35:00.123 [INFO] 这是一条完全自定义格式的日志 2024-05-20 14:35:00.124 [INFO] 这是另一条自定义格式的日志
步骤3:扩展支持多日志级别
实际项目中通常需要区分INFO、WARN、ERROR等不同日志级别,我们可以进一步封装,创建不同级别的日志输出方法:
// 封装多级别日志的Logger
type CustomLogger struct {
infoLogger *log.Logger
warnLogger *log.Logger
errorLogger *log.Logger
}
// 创建CustomLogger实例
func NewCustomLogger(output io.Writer) *CustomLogger {
return &CustomLogger{
infoLogger: log.New(&customLoggerWriter{output: output, level: "INFO"}, "", 0),
warnLogger: log.New(&customLoggerWriter{output: output, level: "WARN"}, "", 0),
errorLogger: log.New(&customLoggerWriter{output: output, level: "ERROR"}, "", 0),
}
}
// Info级别日志输出
func (c *CustomLogger) Info(format string, v ...interface{}) {
c.infoLogger.Printf(format, v...)
}
// Warn级别日志输出
func (c *CustomLogger) Warn(format string, v ...interface{}) {
c.warnLogger.Printf(format, v...)
}
// Error级别日志输出
func (c *CustomLogger) Error(format string, v ...interface{}) {
c.errorLogger.Printf(format, v...)
}
func main() {
logger := NewCustomLogger(os.Stdout)
logger.Info("这是Info级别的日志,用户ID:%d", 1001)
logger.Warn("这是Warn级别的日志,请求耗时:%dms", 300)
logger.Error("这是Error级别的日志,错误原因:%s", "数据库连接失败")
}
上述封装后,调用不同方法就可以输出对应级别的日志,格式统一且易于扩展。
注意事项
在自定义log.Logger格式时,需要注意以下几点:
- log.Logger的Write方法是并发安全的,内部通过mu字段加锁,自定义Writer如果是多协程使用,也需要保证自身的并发安全
- 自定义Writer的Write方法接收的字节切片已经包含log.Logger添加的前缀和标志位内容,如果设置了prefix或flag,需要和自定义格式做好区分,避免重复输出
- 如果日志需要输出到文件,只需要将output设置为对应的文件实例即可,不需要修改自定义格式的逻辑,扩展性较好
通过以上方法,就可以灵活实现Golang中log.Logger的自定义日志格式,满足不同项目的日志需求。
Golanglog.Logger自定义日志格式日志输出修改时间:2026-06-18 06:03:37