Go语言错误处理与日志记录最佳实践引言错误处理和日志记录是任何生产级应用的关键组成部分。Go语言提供了简洁的错误处理机制同时社区也有丰富的日志库可供选择。本文将深入探讨Go语言的错误处理模式、日志记录最佳实践以及如何构建健壮的错误处理体系。一、Go语言错误处理基础1.1 error接口type error interface { Error() string }1.2 创建错误// 使用errors.New创建简单错误 err : errors.New(connection failed) // 使用fmt.Errorf创建格式化错误 err : fmt.Errorf(failed to connect to %s: %w, addr, err) // 自定义错误类型 type MyError struct { Code int Message string } func (e *MyError) Error() string { return fmt.Sprintf(error %d: %s, e.Code, e.Message) }1.3 错误包装与解包import errors // 包装错误 err : fmt.Errorf(failed to read file: %w, ioErr) // 判断错误类型 if errors.Is(err, os.ErrNotExist) { fmt.Println(file not found) } // 解包错误 var targetErr *MyError if errors.As(err, targetErr) { fmt.Printf(error code: %d\n, targetErr.Code) }二、错误处理模式2.1 错误返回模式func OpenFile(path string) (*os.File, error) { f, err : os.Open(path) if err ! nil { return nil, fmt.Errorf(open file failed: %w, err) } return f, nil }2.2 哨兵错误模式var ErrNotFound errors.New(not found) func GetUser(id int) (*User, error) { user, ok : users[id] if !ok { return nil, ErrNotFound } return user, nil } func main() { user, err : GetUser(1) if err ! nil { if errors.Is(err, ErrNotFound) { fmt.Println(user not found) } else { fmt.Printf(unexpected error: %v\n, err) } } }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 } func HandleRequest(w http.ResponseWriter, r *http.Request) { err : processRequest(r) if err ! nil { var apiErr *APIError if errors.As(err, apiErr) { w.WriteHeader(apiErr.StatusCode) w.Write([]byte(apiErr.Message)) } else { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(internal server error)) } } }三、日志记录最佳实践3.1 使用zap日志库go get go.uber.org/zapfunc main() { logger, err : zap.NewProduction() if err ! nil { panic(err) } defer logger.Sync() // 基本日志 logger.Info(server started, zap.String(host, localhost), zap.Int(port, 8080), ) // 错误日志 logger.Error(failed to connect to database, zap.String(dsn, localhost:3306), zap.Error(err), ) }3.2 结构化日志type RequestLogger struct { logger *zap.Logger } func (rl *RequestLogger) LogRequest(r *http.Request, duration time.Duration, statusCode int) { rl.logger.Info(request completed, zap.String(method, r.Method), zap.String(path, r.URL.Path), zap.Int(status, statusCode), zap.Duration(duration, duration), zap.String(remote_addr, r.RemoteAddr), ) }3.3 日志级别配置config : zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), Development: false, DisableCaller: false, DisableStacktrace: false, Sampling: zap.SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: json, EncoderConfig: zap.NewProductionEncoderConfig(), OutputPaths: []string{stdout, /var/log/app.log}, ErrorOutputPaths: []string{stderr}, } logger, _ : config.Build()四、错误处理与日志集成4.1 中间件错误处理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(stacktrace), ) w.WriteHeader(http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }4.2 统一错误响应type ErrorResponse struct { Error string json:error Message string json:message,omitempty RequestID string json:request_id,omitempty Timestamp time.Time json:timestamp } func HandleError(w http.ResponseWriter, r *http.Request, err error, statusCode int) { requestID : r.Context().Value(requestIDKey).(string) logger.Error(request error, zap.String(request_id, requestID), zap.Int(status_code, statusCode), zap.Error(err), ) response : ErrorResponse{ Error: http.StatusText(statusCode), Message: err.Error(), RequestID: requestID, Timestamp: time.Now(), } w.Header().Set(Content-Type, application/json) w.WriteHeader(statusCode) json.NewEncoder(w).Encode(response) }五、错误处理最佳实践5.1 错误处理检查清单实践说明不要忽略错误始终检查并处理error返回值错误包装使用%w包装原始错误保留错误链错误类型判断使用errors.Is和errors.As判断错误类型错误日志在错误发生的地方记录详细日志错误传播不要在中间层吞掉错误5.2 避免的错误处理反模式// bad: 吞掉错误 func badExample() { f, _ : os.Open(file.txt) // 错误忽略了错误 defer f.Close() } // bad: 重复包装错误 func badWrap(err error) error { return fmt.Errorf(error: %w, fmt.Errorf(error: %w, err)) } // good: 单层包装保留原始错误 func goodWrap(err error) error { return fmt.Errorf(failed to process: %w, err) }六、日志记录最佳实践6.1 日志格式规范// 推荐格式级别、时间戳、请求ID、消息、键值对 logger.Info(user login, zap.String(request_id, abc123), zap.String(user_id, 12345), zap.String(ip, 192.168.1.1), )6.2 日志轮换import gopkg.in/natefinch/lumberjack.v2 logger, _ : zap.NewProductionConfig().Build( zap.WrapCore(func(core zapcore.Core) zapcore.Core { writer : zapcore.AddSync(lumberjack.Logger{ Filename: /var/log/app.log, MaxSize: 100, // MB MaxBackups: 3, MaxAge: 7, // days Compress: true, }) return zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writer, zap.InfoLevel, ) }), )6.3 敏感信息脱敏func sanitizePassword(s string) zap.Field { return zap.String(password, ***) } logger.Info(user created, zap.String(username, alice), sanitizePassword(secret123), )七、实战案例错误处理框架package errors import ( fmt net/http ) type ErrorCode int const ( ErrInternal ErrorCode iota 1 ErrBadRequest ErrNotFound ErrUnauthorized ) type AppError struct { Code ErrorCode Message string Err error } func (e *AppError) Error() string { return e.Message } func (e *AppError) Unwrap() error { return e.Err } func (e *AppError) StatusCode() int { switch e.Code { case ErrBadRequest: return http.StatusBadRequest case ErrNotFound: return http.StatusNotFound case ErrUnauthorized: return http.StatusUnauthorized default: return http.StatusInternalServerError } } func New(code ErrorCode, message string, err error) *AppError { return AppError{ Code: code, Message: message, Err: err, } }结论错误处理和日志记录是构建可靠应用的基础。Go语言的错误处理机制简洁而强大通过合理使用错误包装、自定义错误类型和结构化日志可以构建健壮的错误处理体系。良好的错误处理实践能够提高应用的可观测性和可维护性是生产级应用不可或缺的一部分。
Go语言错误处理与日志记录最佳实践
Go语言错误处理与日志记录最佳实践引言错误处理和日志记录是任何生产级应用的关键组成部分。Go语言提供了简洁的错误处理机制同时社区也有丰富的日志库可供选择。本文将深入探讨Go语言的错误处理模式、日志记录最佳实践以及如何构建健壮的错误处理体系。一、Go语言错误处理基础1.1 error接口type error interface { Error() string }1.2 创建错误// 使用errors.New创建简单错误 err : errors.New(connection failed) // 使用fmt.Errorf创建格式化错误 err : fmt.Errorf(failed to connect to %s: %w, addr, err) // 自定义错误类型 type MyError struct { Code int Message string } func (e *MyError) Error() string { return fmt.Sprintf(error %d: %s, e.Code, e.Message) }1.3 错误包装与解包import errors // 包装错误 err : fmt.Errorf(failed to read file: %w, ioErr) // 判断错误类型 if errors.Is(err, os.ErrNotExist) { fmt.Println(file not found) } // 解包错误 var targetErr *MyError if errors.As(err, targetErr) { fmt.Printf(error code: %d\n, targetErr.Code) }二、错误处理模式2.1 错误返回模式func OpenFile(path string) (*os.File, error) { f, err : os.Open(path) if err ! nil { return nil, fmt.Errorf(open file failed: %w, err) } return f, nil }2.2 哨兵错误模式var ErrNotFound errors.New(not found) func GetUser(id int) (*User, error) { user, ok : users[id] if !ok { return nil, ErrNotFound } return user, nil } func main() { user, err : GetUser(1) if err ! nil { if errors.Is(err, ErrNotFound) { fmt.Println(user not found) } else { fmt.Printf(unexpected error: %v\n, err) } } }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 } func HandleRequest(w http.ResponseWriter, r *http.Request) { err : processRequest(r) if err ! nil { var apiErr *APIError if errors.As(err, apiErr) { w.WriteHeader(apiErr.StatusCode) w.Write([]byte(apiErr.Message)) } else { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(internal server error)) } } }三、日志记录最佳实践3.1 使用zap日志库go get go.uber.org/zapfunc main() { logger, err : zap.NewProduction() if err ! nil { panic(err) } defer logger.Sync() // 基本日志 logger.Info(server started, zap.String(host, localhost), zap.Int(port, 8080), ) // 错误日志 logger.Error(failed to connect to database, zap.String(dsn, localhost:3306), zap.Error(err), ) }3.2 结构化日志type RequestLogger struct { logger *zap.Logger } func (rl *RequestLogger) LogRequest(r *http.Request, duration time.Duration, statusCode int) { rl.logger.Info(request completed, zap.String(method, r.Method), zap.String(path, r.URL.Path), zap.Int(status, statusCode), zap.Duration(duration, duration), zap.String(remote_addr, r.RemoteAddr), ) }3.3 日志级别配置config : zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), Development: false, DisableCaller: false, DisableStacktrace: false, Sampling: zap.SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: json, EncoderConfig: zap.NewProductionEncoderConfig(), OutputPaths: []string{stdout, /var/log/app.log}, ErrorOutputPaths: []string{stderr}, } logger, _ : config.Build()四、错误处理与日志集成4.1 中间件错误处理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(stacktrace), ) w.WriteHeader(http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }4.2 统一错误响应type ErrorResponse struct { Error string json:error Message string json:message,omitempty RequestID string json:request_id,omitempty Timestamp time.Time json:timestamp } func HandleError(w http.ResponseWriter, r *http.Request, err error, statusCode int) { requestID : r.Context().Value(requestIDKey).(string) logger.Error(request error, zap.String(request_id, requestID), zap.Int(status_code, statusCode), zap.Error(err), ) response : ErrorResponse{ Error: http.StatusText(statusCode), Message: err.Error(), RequestID: requestID, Timestamp: time.Now(), } w.Header().Set(Content-Type, application/json) w.WriteHeader(statusCode) json.NewEncoder(w).Encode(response) }五、错误处理最佳实践5.1 错误处理检查清单实践说明不要忽略错误始终检查并处理error返回值错误包装使用%w包装原始错误保留错误链错误类型判断使用errors.Is和errors.As判断错误类型错误日志在错误发生的地方记录详细日志错误传播不要在中间层吞掉错误5.2 避免的错误处理反模式// bad: 吞掉错误 func badExample() { f, _ : os.Open(file.txt) // 错误忽略了错误 defer f.Close() } // bad: 重复包装错误 func badWrap(err error) error { return fmt.Errorf(error: %w, fmt.Errorf(error: %w, err)) } // good: 单层包装保留原始错误 func goodWrap(err error) error { return fmt.Errorf(failed to process: %w, err) }六、日志记录最佳实践6.1 日志格式规范// 推荐格式级别、时间戳、请求ID、消息、键值对 logger.Info(user login, zap.String(request_id, abc123), zap.String(user_id, 12345), zap.String(ip, 192.168.1.1), )6.2 日志轮换import gopkg.in/natefinch/lumberjack.v2 logger, _ : zap.NewProductionConfig().Build( zap.WrapCore(func(core zapcore.Core) zapcore.Core { writer : zapcore.AddSync(lumberjack.Logger{ Filename: /var/log/app.log, MaxSize: 100, // MB MaxBackups: 3, MaxAge: 7, // days Compress: true, }) return zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writer, zap.InfoLevel, ) }), )6.3 敏感信息脱敏func sanitizePassword(s string) zap.Field { return zap.String(password, ***) } logger.Info(user created, zap.String(username, alice), sanitizePassword(secret123), )七、实战案例错误处理框架package errors import ( fmt net/http ) type ErrorCode int const ( ErrInternal ErrorCode iota 1 ErrBadRequest ErrNotFound ErrUnauthorized ) type AppError struct { Code ErrorCode Message string Err error } func (e *AppError) Error() string { return e.Message } func (e *AppError) Unwrap() error { return e.Err } func (e *AppError) StatusCode() int { switch e.Code { case ErrBadRequest: return http.StatusBadRequest case ErrNotFound: return http.StatusNotFound case ErrUnauthorized: return http.StatusUnauthorized default: return http.StatusInternalServerError } } func New(code ErrorCode, message string, err error) *AppError { return AppError{ Code: code, Message: message, Err: err, } }结论错误处理和日志记录是构建可靠应用的基础。Go语言的错误处理机制简洁而强大通过合理使用错误包装、自定义错误类型和结构化日志可以构建健壮的错误处理体系。良好的错误处理实践能够提高应用的可观测性和可维护性是生产级应用不可或缺的一部分。