Go 语言 log 包使用教程
Go 语言的 log 包提供了简单而高效的日志记录功能,是开发调试和生产监控的重要工具。
1. 基础概念
在 Go 的 log 模块中,核心概念包括:
log.Logger: 日志记录器,负责格式化和输出日志信息。- 日志级别: 包括普通日志、致命错误(Fatal)和恐慌(Panic)三种级别。
- 日志格式: 支持自定义前缀、时间戳格式和输出标志。
- 输出目标: 可以将日志输出到标准输出、标准错误、文件或任何实现了
io.Writer接口的对象。
为什么使用 log 包?
虽然可以直接使用 fmt.Println 输出调试信息,但 log 包提供了更多优势:
- 时间戳: 自动添加时间戳,便于追踪问题发生时间
- 格式统一: 提供一致的日志格式
- 输出控制: 可以轻松切换输出目标
- 错误处理: 提供 Fatal 和 Panic 级别的错误处理
- 线程安全: 多个 goroutine 可以安全地并发写入日志
2. 快速入门
基本日志输出
最简单的日志使用方式是直接调用 log 包的函数:
go
package main
import (
"log"
)
func main() {
// 输出普通日志信息
log.Print("这是一条日志")
log.Println("这是一条带换行的日志")
log.Printf("这是格式化日志:用户ID=%d, 用户名=%s", 1001, "Alice")
}运行结果:
shell
2025/11/05 14:30:15 这是一条日志2025/11/05 14:30:15 这是一条带换行的日志
2025/11/05 14:30:15 这是格式化日志:用户ID=1001, 用户名=Alice不同级别的日志
log 包提供了三个级别的日志函数:
go
package main
import (
"log"
)
func main() {
log.Println("这是一条很普通的日志。")
v := "很普通的"
log.Printf("这是一条%s日志。\n", v)
// Fatalln 会触发 fatal 错误,程序将退出
log.Fatalln("这是一条会触发fatal的日志。")
// Panicln 会触发 panic 错误,程序将退出
log.Panicln("这是一条会触发panic的日志。")
// 下面的代码不会执行
fmt.Println("下面的代码不会执行")
}运行结果:
shell
2025/11/05 14:04:17 这是一条很普通的日志。
2025/11/05 14:04:17 这是一条很普通的日志。
2025/11/05 14:04:17 这是一条会触发fatal的日志。
exit status 13. 自定义日志格式
日志标志说明
log标准库提供了如下的flag选项,它们是一系列定义好的常量。
go
const (
Ldate = 1 << iota // 日期:2009/01/23
Ltime // 时间:01:23:23
Lmicroseconds // 微秒级别时间:01:23:23.123123. 等价于:Ltime | Lmicroseconds
Llongfile // 文件全路径名+行号: /a/b/c/d.go:23
Lshortfile // 文件名+行号:d.go:23. 等价于:Lfile | Lline
LUTC // 使用UTC时间
LstdFlags = Ldate | Ltime // 标准日期时间格式:2009/01/23 01:23:23
)设置日志前缀和标志
可以通过 SetPrefix 和 SetFlags 自定义日志格式:
go
package main
import (
"log"
)
func main() {
// 设置日志前缀
log.SetPrefix("[MyApp] ")
// 设置日志标志
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)
log.Println("这是自定义格式的日志")
log.Printf("当前用户:%s", "Bob")
}运行结果:
shell
[MyApp] 2025/11/05 14:30:15.123456 main.go:12: 这是自定义格式的日志
[MyApp] 2025/11/05 14:30:15.123789 main.go:13: 当前用户:Bob4. 自定义日志器
创建新的日志器
使用 log.New 可以创建自定义的日志器:
go
package main
import (
"log"
"os"
)
func main() {
// 创建不同用途的日志器
// 信息日志器
infoLogger := log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
// 警告日志器
warnLogger := log.New(os.Stdout, "WARN: ", log.Ldate|log.Ltime|log.Lshortfile)
// 错误日志器 - 输出到标准错误
errorLogger := log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
// 使用不同的日志器
infoLogger.Println("应用程序启动")
warnLogger.Println("配置文件未找到,使用默认配置")
errorLogger.Println("数据库连接失败")
}运行结果:
shell
INFO: 2025/11/05 14:30:15 main.go:16: 应用程序启动
WARN: 2025/11/05 14:30:15 main.go:17: 配置文件未找到,使用默认配置
ERROR: 2025/11/05 14:30:15 main.go:18: 数据库连接失败日志器结构体封装
创建一个结构化的日志器来管理不同级别的日志:
go
package main
import (
"io"
"log"
"os"
)
// Logger 封装不同级别的日志器
type Logger struct {
info *log.Logger
warn *log.Logger
error *log.Logger
}
// NewLogger 创建新的日志器
func NewLogger(infoWriter, warnWriter, errorWriter io.Writer) *Logger {
return &Logger{
info: log.New(infoWriter, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile),
warn: log.New(warnWriter, "WARN: ", log.Ldate|log.Ltime|log.Lshortfile),
error: log.New(errorWriter, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile),
}
}
// Info 记录信息日志
func (l *Logger) Info(v ...interface{}) {
l.info.Println(v...)
}
// Warn 记录警告日志
func (l *Logger) Warn(v ...interface{}) {
l.warn.Println(v...)
}
// Error 记录错误日志
func (l *Logger) Error(v ...interface{}) {
l.error.Println(v...)
}
func main() {
// 创建自定义日志器
logger := NewLogger(os.Stdout, os.Stdout, os.Stderr)
// 使用日志器
logger.Info("用户登录成功", "userID=1001")
logger.Warn("API 调用频率过高")
logger.Error("数据库查询失败")
}5. 日志输出控制
输出到文件
将日志输出到文件是生产环境的常见需求:
go
package main
import (
"log"
"os"
)
func main() {
// 创建或打开日志文件
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
defer logFile.Close()
// 设置日志输出到文件
log.SetOutput(logFile)
log.SetPrefix("[App] ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// 记录日志
log.Println("应用程序启动")
log.Printf("当前工作目录: %s", getCurrentDir())
log.Println("初始化完成")
}
func getCurrentDir() string {
dir, _ := os.Getwd()
return dir
}同时输出到多个目标
使用 io.MultiWriter 可以同时输出到多个目标:
go
package main
import (
"io"
"log"
"os"
)
func main() {
// 创建日志文件
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("无法创建日志文件:", err)
}
defer logFile.Close()
// 创建多重写入器 - 同时写入文件和标准输出
multiWriter := io.MultiWriter(os.Stdout, logFile)
// 创建日志器
logger := log.New(multiWriter, "[App] ", log.Ldate|log.Ltime|log.Lshortfile)
// 记录日志 - 会同时输出到控制台和文件
logger.Println("应用程序启动")
logger.Println("配置加载完成")
logger.Println("服务器开始监听端口 8080")
}6. 实际应用示例
构建一个 Web 服务器日志系统
go
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
// WebLogger Web 服务器日志器
type WebLogger struct {
access *log.Logger
error *log.Logger
}
// NewWebLogger 创建 Web 日志器
func NewWebLogger() (*WebLogger, error) {
// 创建访问日志文件
accessFile, err := os.OpenFile("access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, fmt.Errorf("无法创建访问日志文件: %v", err)
}
// 创建错误日志文件
errorFile, err := os.OpenFile("error.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
accessFile.Close()
return nil, fmt.Errorf("无法创建错误日志文件: %v", err)
}
return &WebLogger{
access: log.New(io.MultiWriter(os.Stdout, accessFile), "[ACCESS] ", log.Ldate|log.Ltime),
error: log.New(io.MultiWriter(os.Stderr, errorFile), "[ERROR] ", log.Ldate|log.Ltime|log.Lshortfile),
}, nil
}
// LogAccess 记录访问日志
func (wl *WebLogger) LogAccess(r *http.Request, status int, duration time.Duration) {
wl.access.Printf("%s %s %s %d %v",
r.RemoteAddr, r.Method, r.URL.Path, status, duration)
}
// LogError 记录错误日志
func (wl *WebLogger) LogError(err error, context string) {
wl.error.Printf("%s: %v", context, err)
}
// LoggingMiddleware 日志中间件
func LoggingMiddleware(logger *WebLogger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// 调用下一个处理器
next.ServeHTTP(wrapped, r)
// 记录访问日志
duration := time.Since(start)
logger.LogAccess(r, wrapped.statusCode, duration)
})
}
// responseWriter 包装器,用于捕获状态码
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// 处理器函数
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "欢迎访问首页!")
}
func apiHandler(logger *WebLogger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 模拟可能的错误
if r.URL.Query().Get("error") == "true" {
logger.LogError(fmt.Errorf("模拟的API错误"), "API处理")
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, `{"message": "API 调用成功", "timestamp": "%s"}`, time.Now().Format(time.RFC3339))
}
}
func main() {
// 创建日志器
logger, err := NewWebLogger()
if err != nil {
log.Fatal("初始化日志器失败:", err)
}
// 设置路由
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/api", apiHandler(logger))
// 应用日志中间件
handler := LoggingMiddleware(logger, mux)
// 启动服务器
logger.access.Println("服务器启动在端口 :8080")
if err := http.ListenAndServe(":8080", handler); err != nil {
logger.LogError(err, "服务器启动")
}
}使用示例:
shell
# 启动服务器
$ go run main.go
# 在另一个终端测试
$ curl http://localhost:8080/
$ curl http://localhost:8080/api
$ curl http://localhost:8080/api?error=true7. 最佳实践
1. 日志级别管理
- 在开发环境使用 DEBUG 级别,生产环境使用 INFO 或更高级别
- 合理使用 Fatal 和 Panic,避免在库代码中使用
- 为不同模块创建独立的日志器
2. 日志格式规范
- 保持日志格式的一致性
- 包含必要的上下文信息(时间戳、文件位置等)
- 使用结构化的日志前缀
3. 性能考虑
- 避免在高频调用的代码中使用过多日志
- 考虑使用条件日志来减少不必要的输出
- 对于大量日志,考虑异步写入
8. 局限性与替代方案
log 包的局限性
- 功能简单: 只提供基础的日志功能,缺少日志级别控制
- 性能一般: 对于高并发场景,性能可能不够理想
- 格式固定: 日志格式相对固定,自定义能力有限
- 缺少结构化: 不支持结构化日志(JSON 格式等)
推荐的替代方案
对于复杂的日志需求,推荐使用第三方日志库:
9. 总结
Go 的 log 包是一个简单实用的日志记录工具,适合大多数基本场景。它的优势在于:
- 标准库: 无需额外依赖
- 简单易用: API 设计直观
- 线程安全: 支持并发使用
- 灵活输出: 可以输出到任何 io.Writer
虽然功能相对基础,但对于简单到中等复杂度的应用来说,log 包完全够用。当需要更高级功能(如日志级别、结构化日志、高性能等)时,可以考虑使用第三方库。