Go语言错误处理与日志系统设计:打造健壮的应用程序
引言
错误处理和日志记录是构建健壮应用程序的关键。Go语言提供了简洁而强大的错误处理机制,结合优秀的日志库可以实现高效的日志管理。本文将深入探讨Go语言的错误处理最佳实践和日志系统设计。
一、错误处理基础
1.1 error接口
type error interface { Error() string }1.2 创建错误
// 使用errors.New err := errors.New("something went wrong") // 使用fmt.Errorf err := fmt.Errorf("error processing %s: %w", name, err) // 自定义错误类型 type MyError struct { Code int Message string } func (e *MyError) Error() string { return fmt.Sprintf("[%d] %s", e.Code, e.Message) }1.3 错误包装与解包
// 包装错误 err := fmt.Errorf("failed to read file: %w", ioErr) // 解包错误 if errors.Is(err, os.ErrNotExist) { // 文件不存在 } // 获取原始错误 var target *MyError if errors.As(err, &target) { // 处理特定类型错误 }二、错误处理模式
2.1 错误返回模式
func processFile(path string) error { file, err := os.Open(path) if err != nil { return fmt.Errorf("open file failed: %w", err) } defer file.Close() // 处理文件 return nil }2.2 哨兵错误
var ErrNotFound = errors.New("not found") var ErrTimeout = errors.New("timeout") func findUser(id int) (*User, error) { user, exists := users[id] if !exists { return nil, ErrNotFound } return user, nil }2.3 自定义错误类型
type APIError struct { StatusCode int Message string Err error } func (e *APIError) Error() string { return fmt.Sprintf("API error [%d]: %s", e.StatusCode, e.Message) } func (e *APIError) Unwrap() error { return e.Err }三、日志系统设计
3.1 选择日志库
# 使用zap go get go.uber.org/zap3.2 zap配置
func NewLogger() *zap.Logger { logger, err := zap.NewProduction() if err != nil { log.Fatal(err) } return logger } // 自定义配置 func NewCustomLogger() *zap.Logger { config := zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), Development: true, Sampling: &zap.SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: "json", EncoderConfig: zap.NewProductionEncoderConfig(), OutputPaths: []string{"stdout", "/var/log/app.log"}, ErrorOutputPaths: []string{"stderr"}, } return config.Build() }3.3 日志使用
logger := zap.L() logger.Info("User login", zap.String("user", "john"), zap.Int("user_id", 123), zap.Duration("duration", time.Second), ) logger.Error("Database connection failed", zap.Error(err), zap.String("host", "localhost"), ) // 结构化日志 logger.With( zap.String("service", "api"), zap.String("version", "1.0.0"), ).Info("Service started")3.4 日志轮换
import ( "gopkg.in/natefinch/lumberjack.v2" "go.uber.org/zap" ) func NewRotatingLogger() *zap.Logger { writer := &lumberjack.Logger{ Filename: "/var/log/app.log", MaxSize: 100, // MB MaxBackups: 3, MaxAge: 28, // days Compress: true, } logger := zap.New(zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(writer), zap.InfoLevel, )) return logger }四、日志最佳实践
4.1 结构化日志
// bad logger.Info(fmt.Sprintf("User %s logged in with id %d", user, id)) // good logger.Info("User logged in", zap.String("user", user), zap.Int("id", id), )4.2 日志级别
logger.Debug("Entering function") // 调试信息 logger.Info("User logged in") // 一般信息 logger.Warn("High memory usage") // 警告 logger.Error("Failed to process request") // 错误 logger.DPanic("Configuration error") // 开发环境panic logger.Panic("Critical error") // panic logger.Fatal("Application shutting down") // 致命错误4.3 上下文日志
func withRequestID(ctx context.Context, logger *zap.Logger) *zap.Logger { if requestID, ok := ctx.Value("requestID").(string); ok { return logger.With(zap.String("request_id", requestID)) } return logger }4.4 敏感信息脱敏
// 自定义编码器隐藏敏感字段 func sanitizeEncoder() zapcore.Encoder { config := zap.NewProductionEncoderConfig() config.EncodeLevel = zapcore.CapitalLevelEncoder config.EncodeTime = zapcore.ISO8601TimeEncoder return zapcore.NewJSONEncoder(config) }五、实战:统一错误处理中间件
func errorMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { logger.Error("Panic recovered", zap.Any("panic", err), zap.Stack("stack"), ) w.WriteHeader(http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } func handleError(w http.ResponseWriter, err error) { var apiErr *APIError if errors.As(err, &apiErr) { w.WriteHeader(apiErr.StatusCode) json.NewEncoder(w).Encode(map[string]string{ "error": apiErr.Message, }) return } logger.Error("Unexpected error", zap.Error(err)) w.WriteHeader(http.StatusInternalServerError) }六、监控与告警
6.1 日志指标收集
type LogMetrics struct { errorCount prometheus.Counter warningCount prometheus.Counter requestLatency prometheus.Histogram } func NewLogMetrics(reg *prometheus.Registry) *LogMetrics { lm := &LogMetrics{ errorCount: prometheus.NewCounter(prometheus.CounterOpts{ Name: "app_errors_total", Help: "Total number of errors", }), warningCount: prometheus.NewCounter(prometheus.CounterOpts{ Name: "app_warnings_total", Help: "Total number of warnings", }), } reg.MustRegister(lm.errorCount, lm.warningCount) return lm }6.2 日志过滤与告警
func monitorLogs(logs <-chan *LogEntry) { for log := range logs { if log.Level == zap.ErrorLevel { alertManager.SendAlert(log.Message) } if strings.Contains(log.Message, "timeout") { metrics.timeoutCount.Inc() } } }结论
良好的错误处理和日志系统是构建健壮应用程序的基石。Go语言的error接口提供了灵活的错误处理机制,结合zap等优秀的日志库可以实现高效的日志管理。在实际项目中,需要建立统一的错误处理规范和日志标准,以便快速定位和解决问题。