40分钟学 Go 语言高并发:错误处理最佳实践

发布于:2024-11-29 ⋅ 阅读:(31) ⋅ 点赞:(0)

错误处理最佳实践

一、课程概述

学习要点 重要程度 掌握目标
error设计 ★★★★★ 掌握合理的错误类型设计和错误码管理
错误包装 ★★★★☆ 理解和运用errors包提供的错误包装功能
panic处理 ★★★★★ 掌握panic/recover的使用和最佳实践
日志记录 ★★★★☆ 实现规范的错误日志记录系统

二、错误处理框架实现

让我们实现一个完整的错误处理框架:

package errorhandling

import (
    "fmt"
    "runtime"
    "strings"
    "time"
)

// ErrorCode 错误码类型
type ErrorCode int

// ErrorLevel 错误级别
type ErrorLevel int

const (
    // 错误级别定义
    LevelDebug ErrorLevel = iota
    LevelInfo
    LevelWarn
    LevelError
    LevelFatal
)

// 基础错误码定义
const (
    ErrCodeSuccess ErrorCode = iota
    ErrCodeInvalidParam
    ErrCodeInternalError
    ErrCodeTimeout
    ErrCodeNotFound
    ErrCodeUnauthorized
)

// AppError 应用错误结构
type AppError struct {
    Code      ErrorCode  // 错误码
    Message   string    // 错误信息
    Level     ErrorLevel // 错误级别
    Stack     string    // 错误堆栈
    Cause     error     // 原始错误
    Timestamp time.Time // 错误发生时间
    Context   map[string]interface{} // 错误上下文
}

// New 创建新的应用错误
func New(code ErrorCode, message string, level ErrorLevel) *AppError {
    return &AppError{
        Code:      code,
        Message:   message,
        Level:     level,
        Stack:     captureStack(2),
        Timestamp: time.Now(),
        Context:   make(map[string]interface{}),
    }
}

// Wrap 包装已有错误
func Wrap(err error, code ErrorCode, message string) *AppError {
    if err == nil {
        return nil
    }

    // 如果已经是AppError,则更新信息
    if appErr, ok := err.(*AppError); ok {
        return &AppError{
            Code:      code,
            Message:   message,
            Level:     appErr.Level,
            Stack:     appErr.Stack,
            Cause:     appErr.Cause,
            Timestamp: appErr.Timestamp,
            Context:   appErr.Context,
        }
    }

    // 创建新的AppError
    return &AppError{
        Code:      code,
        Message:   message,
        Level:     LevelError,
        Stack:     captureStack(2),
        Cause:     err,
        Timestamp: time.Now(),
        Context:   make(map[string]interface{}),
    }
}

// Error 实现error接口
func (e *AppError) Error() string {
    if e.Cause != nil {
        return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Cause)
    }
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// WithContext 添加错误上下文
func (e *AppError) WithContext(key string, value interface{}) *AppError {
    e.Context[key] = value
    return e
}

// GetContext 获取错误上下文
func (e *AppError) GetContext(key string) interface{} {
    return e.Context[key]
}

// Is 实现错误比较
func (e *AppError) Is(target error) bool {
    t, ok := target.(*AppError)
    if !ok {
        return false
    }
    return e.Code == t.Code
}

// captureStack 捕获错误堆栈
func captureStack(skip int) string {
    var buf strings.Builder
    
    // 收集堆栈信息
    for i := skip; ; i++ {
        pc, file, line, ok := runtime.Caller(i)
        if !ok {
            break
        }
        fn := runtime.FuncForPC(pc)
        fmt.Fprintf(&buf, "\n%s:%d - %s", file, line, fn.Name())
    }
    
    return buf.String()
}

// ErrorHandler 错误处理器接口
type ErrorHandler interface {
    Handle(err error) error
}

// DefaultErrorHandler 默认错误处理器
type DefaultErrorHandler struct {
    handlers map[ErrorCode]func(error) error
}

// NewDefaultErrorHandler 创建默认错误处理器
func NewDefaultErrorHandler() *DefaultErrorHandler {
    return &DefaultErrorHandler{
        handlers: make(map[ErrorCode]func(error) error),
    }
}

// Register 注册错误处理函数
func (h *DefaultErrorHandler) Register(code ErrorCode, handler func(error) error) {
    h.handlers[code] = handler
}

// Handle 处理错误
func (h *DefaultErrorHandler) Handle(err error) error {
    if err == nil {
        return nil
    }

    // 转换为AppError
    appErr, ok := err.(*AppError)
    if !ok {
        appErr = Wrap(err, ErrCodeInternalError, "internal error")
    }

    // 查找并执行对应的处理函数
    if handler, exists := h.handlers[appErr.Code]; exists {
        return handler(appErr)
    }

    // 默认处理
    return appErr
}

三、错误日志系统实现

让我们实现一个完整的错误日志记录系统:

package errorhandling

import (
    "encoding/json"
    "fmt"
    "os"
    "sync"
    "time"
)

// ErrorLogger 错误日志记录器
type ErrorLogger struct {
    mu        sync.Mutex
    file      *os.File
    formatter ErrorFormatter
    buffer    []LogEntry
    bufferSize int
    flushInterval time.Duration
}

// LogEntry 日志条目
type LogEntry struct {
    Timestamp time.Time              `json:"timestamp"`
    Level     ErrorLevel            `json:"level"`
    Code      ErrorCode             `json:"code"`
    Message   string                `json:"message"`
    Stack     string                `json:"stack"`
    Context   map[string]interface{} `json:"context"`
}

// ErrorFormatter 错误格式化接口
type ErrorFormatter interface {
    Format(LogEntry) string
}

// JSONFormatter JSON格式化器
type JSONFormatter struct{}

// Format 实现JSON格式化
func (f *JSONFormatter) Format(entry LogEntry) string {
    data, _ := json.Marshal(entry)
    return string(data) + "\n"
}

// NewErrorLogger 创建错误日志记录器
func NewErrorLogger(filename string, bufferSize int, flushInterval time.Duration) (*ErrorLogger, error) {
    file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, fmt.Errorf("failed to open log file: %v", err)
    }

    logger := &ErrorLogger{
        file:         file,
        formatter:    &JSONFormatter{},
        buffer:      make([]LogEntry, 0, bufferSize),
        bufferSize:  bufferSize,
        flushInterval: flushInterval,
    }

    // 启动定时刷新
    go logger.startFlushTimer()

    return logger, nil
}

// Log 记录错误
func (l *ErrorLogger) Log(err error) {
    if err == nil {
        return
    }

    appErr, ok := err.(*AppError)
    if !ok {
        appErr = Wrap(err, ErrCodeInternalError, "internal error")
    }

    entry := LogEntry{
        Timestamp: appErr.Timestamp,
        Level:     appErr.Level,
        Code:      appErr.Code,
        Message:   appErr.Message,
        Stack:     appErr.Stack,
        Context:   appErr.Context,
    }

    l.mu.Lock()
    l.buffer = append(l.buffer, entry)

    // 如果缓冲区满,则刷新
    if len(l.buffer) >= l.bufferSize {
        l.flush()
    }
    l.mu.Unlock()
}

// flush 刷新缓冲区到文件
func (l *ErrorLogger) flush() {
    if len(l.buffer) == 0 {
        return
    }

    for _, entry := range l.buffer {
        formatted := l.formatter.Format(entry)
        l.file.WriteString(formatted)
    }

    l.buffer = l.buffer[:0]
}

// startFlushTimer 启动定时刷新
func (l *ErrorLogger) startFlushTimer() {
    ticker := time.NewTicker(l.flushInterval)
    for range ticker.C {
        l.mu.Lock()
        l.flush()
        l.mu.Unlock()
    }
}

// Close 关闭日志记录器
func (l *ErrorLogger) Close() error {
    l.mu.Lock()
    defer l.mu.Unlock()

    l.flush()
    return l.file.Close()
}

// SetFormatter 设置格式化器
func (l *ErrorLogger) SetFormatter(formatter ErrorFormatter) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.formatter = formatter
}

// GetBuffer 获取当前缓冲区内容
func (l *ErrorLogger) GetBuffer() []LogEntry {
    l.mu.Lock()
    defer l.mu.Unlock()

    buffer := make([]LogEntry, len(l.buffer))
    copy(buffer, l.buffer)
    return buffer
}

四、Panic处理实现

让我们实现一个完整的panic处理系统:

package errorhandling

import (
    "context"
    "fmt"
    "runtime/debug"
    "sync"
)

// PanicHandler panic处理器
type PanicHandler struct {
    handlers []func(interface{}, []byte)
    logger   *ErrorLogger
    mu       sync.RWMutex
}

// NewPanicHandler 创建panic处理器
func NewPanicHandler(logger *ErrorLogger) *PanicHandler {
    return &PanicHandler{
        logger: logger,
    }
}

// AddHandler 添加处理函数
func (h *PanicHandler) AddHandler(handler func(interface{}, []byte)) {
    h.mu.Lock()
    defer h.mu.Unlock()
    h.handlers = append(h.handlers, handler)
}

// Wrap 包装函数添加panic处理
func (h *PanicHandler) Wrap(f func()) func() {
    return func() {
        defer h.Recover()
        f()
    }
}

// WrapWithContext 包装带context的函数
func (h *PanicHandler) WrapWithContext(ctx context.Context, f func(context.Context)) func(context.Context) {
    return func(ctx context.Context) {
        defer h.RecoverWithContext(ctx)
        f(ctx)
    }
}

// Recover 恢复panic
func (h *PanicHandler) Recover() {
    if r := recover(); r != nil {
        stack := debug.Stack()
        h.handlePanic(r, stack)
    }
}

// RecoverWithContext 带context的panic恢复
func (h *PanicHandler) RecoverWithContext(ctx context.Context) {
    if r := recover(); r != nil {
        stack := debug.Stack()
        h.handlePanicWithContext(ctx, r, stack)
    }
}

// handlePanic 处理panic
func (h *PanicHandler) handlePanic(r interface{}, stack []byte) {
    // 创建错误记录
    err := New(
        ErrCodeInternalError,
        fmt.Sprintf("panic recovered: %v", r),
        LevelFatal,
    ).WithContext("stack", string(stack))

    // 记录日志
    if h.logger != nil {
        h.logger.Log(err)
    }

    // 执行所有处理函数
    h.mu.RLock()
    defer h.mu.RUnlock()
    for _, handler := range h.handlers {
        handler(r, stack)
    }
}

// handlePanicWithContext 处理带context的panic
func (h *PanicHandler) handlePanicWithContext(ctx context.Context, r interface{}, stack []byte) {
    // 从context获取额外信息
    ctxInfo := make(map[string]interface{})
    if requestID, ok := ctx.Value("request_id").(string); ok {
        ctxInfo["request_id"] = requestID
    }
    if userID, ok := ctx.Value("user_id").(string); ok {
        ctxInfo["user_id"] = userID
    }

    // 创建错误记录
    err := New(
        ErrCodeInternalError,
        fmt.Sprintf("panic recovered: %v", r),
        LevelFatal,
    ).WithContext("stack", string(stack))

    // 添加context信息
    for k, v := range ctxInfo {
        err.WithContext(k, v)
    }

    // 记录日志
    if h.logger != nil {
        h.logger.Log(err)
    }

    // 执行所有处理函数
    h.mu.RLock()
    defer h.mu.RUnlock()
    for _, handler := range h.handlers {
        handler(r, stack)
    }
}

// SafeGo 安全地启动goroutine
func (h *PanicHandler) SafeGo(f func()) {
    go func() {
        defer h.Recover()
        f()
    }()
}

// SafeGoWithContext 安全地启动带context的goroutine
func (h *PanicHandler) SafeGoWithContext(ctx context.Context, f func(context.Context)) {
    go func() {
        defer h.RecoverWithContext(ctx)
        f(ctx)
    }()
}

// PanicMiddleware HTTP中间件处理panic
func (h *PanicHandler) PanicMiddleware(next func()) func() {
    return func() {
        defer func() {
            if r := recover(); r != nil {
                stack := debug.Stack()
                h.handlePanic(r, stack)
                // 可以在这里返回500错误响应
            }
        }()
        next()
    }
}

// MonitorGoroutine 监控goroutine panic
type GoroutineMonitor struct {
    handler *PanicHandler
    wg      sync.WaitGroup
}

func NewGoroutineMonitor(handler *PanicHandler) *GoroutineMonitor {
    return &GoroutineMonitor{
        handler: handler,
    }
}

// Go 安全地启动并监控goroutine
func (m *GoroutineMonitor) Go(f func()) {
    m.wg.Add(1)
    go func() {
        defer m.wg.Done()
        defer m.handler.Recover()
        f()
    }()
}

// Wait 等待所有goroutine完成
func (m *GoroutineMonitor) Wait() {
    m.wg.Wait()
}

五、使用示例

让我们看一个完整的使用示例:

package main

import (
    "context"
    "fmt"
    "log"
    "time"
)

func main() {
    // 创建错误日志记录器
    logger, err := NewErrorLogger("errors.log", 100, 5*time.Second)
    if err != nil {
        log.Fatalf("Failed to create error logger: %v", err)
    }
    defer logger.Close()

    // 创建错误处理器
    errorHandler := NewDefaultErrorHandler()
    
    // 注册错误处理函数
    errorHandler.Register(ErrCodeInvalidParam, func(err error) error {
        if appErr, ok := err.(*AppError); ok {
            logger.Log(appErr)
            return fmt.Errorf("invalid parameter: %s", appErr.Message)
        }
        return err
    })

    // 创建panic处理器
    panicHandler := NewPanicHandler(logger)
    
    // 添加panic处理函数
    panicHandler.AddHandler(func(r interface{}, stack []byte) {
        log.Printf("Panic occurred: %v\nStack trace:\n%s", r, stack)
    })

    // 创建goroutine监控器
    monitor := NewGoroutineMonitor(panicHandler)

    // 示例1:基本错误处理
    err = processRequest("invalid data")
    if err != nil {
        errorHandler.Handle(err)
    }

    // 示例2:带context的错误处理
    ctx := context.WithValue(context.Background(), "request_id", "12345")
    monitor.Go(func() {
        if err := processRequestWithContext(ctx); err != nil {
            errorHandler.Handle(err)
        }
    })

    // 示例3:panic处理
    monitor.Go(func() {
        dangerousOperation()
    })

    // 等待所有goroutine完成
    monitor.Wait()
}

// 示例函数:处理请求
func processRequest(data string) error {
    if data == "invalid data" {
        return New(ErrCodeInvalidParam, "invalid data format", LevelError)
    }
    return nil
}

// 示例函数:带context的请求处理
func processRequestWithContext(ctx context.Context) error {
    requestID := ctx.Value("request_id").(string)
    return New(ErrCodeInternalError, "processing failed").
        WithContext("request_id", requestID)
}

// 示例函数:可能发生panic的操作
func dangerousOperation() {
    // 模拟panic
    panic("something went wrong")
}

// 自定义错误处理示例
type DatabaseError struct {
    *AppError
    Query string
}

func NewDatabaseError(message string, query string) *DatabaseError {
    return &DatabaseError{
        AppError: New(ErrCodeInternalError, message, LevelError),
        Query:    query,
    }
}

// 中间件示例
func errorMiddleware(handler func() error) func() error {
    return func() error {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("Panic in handler: %v", r)
            }
        }()
        return handler()
    }
}

// 事务示例
func withTransaction(ctx context.Context, fn func(context.Context) error) error {
    // 开始事务
    tx := beginTransaction()
    
    // 确保事务结束
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
            panic(r) // 重新抛出panic
        }
    }()

    // 执行操作
    err := fn(ctx)
    if err != nil {
        tx.Rollback()
        return err
    }

    // 提交事务
    return tx.Commit()
}

// 模拟事务对象
type Transaction struct{}

func beginTransaction() *Transaction {
    return &Transaction{}
}

func (t *Transaction) Commit() error {
    return nil
}

func (t *Transaction) Rollback() {
    // 回滚操作
}

六、错误处理流程图

让我们用流程图来描述错误处理的过程:
在这里插入图片描述

七、最佳实践总结

7.1 错误设计原则

  1. 错误分类

    • 业务错误
    • 系统错误
    • 第三方错误
  2. 错误信息

    • 明确的错误码
    • 详细的错误描述
    • 必要的上下文信息
  3. 错误追踪

    • 错误堆栈
    • 错误链路
    • 错误上下文

7.2 错误处理策略

  1. 错误恢复

    • 及时发现
    • 优雅降级
    • 自动重试
  2. 错误隔离

    • 错误边界
    • 错误域分离
    • 故障隔离
  3. 错误监控

    • 错误统计
    • 性能影响
    • 告警机制

7.3 实现建议

  1. 错误封装

    // 推荐
    return &AppError{
        Code: ErrCodeNotFound,
        Message: "user not found",
        Context: map[string]interface{}{
            "user_id": id,
        },
    }
    
    // 不推荐
    return fmt.Errorf("user not found")
    
  2. 错误判断

    // 推荐
    if errors.Is(err, ErrNotFound) {
        // 处理特定错误
    }
    
    // 不推荐
    if err.Error() == "not found" {
        // 不要用字符串比较
    }
    
  3. panic处理

    // 推荐
    defer func() {
        if r := recover(); r != nil {
            // 具体的恢复逻辑
        }
    }()
    
    // 不推荐
    if err != nil {
        panic(err) // 避免随意使用panic
    }
    

7.4 日志记录准则

  1. 日志级别
  • Debug:调试信息
  • Info:常规信息
  • Warn:警告信息
  • Error:错误信息
  • Fatal:致命错误
  1. 日志内容
  • 时间戳
  • 错误级别
  • 错误描述
  • 错误堆栈
  • 上下文信息
  1. 日志格式
  • 结构化日志
  • JSON格式
  • 统一格式化

7.5 测试建议

  1. 错误测试
  • 测试所有错误分支
  • 验证错误恢复机制
  • 检查错误信息准确性
  1. 异常测试
  • 模拟panic场景
  • 测试恢复机制
  • 验证资源清理
  1. 性能测试
  • 错误处理性能
  • 日志写入性能
  • 内存使用情况

通过以上内容,我们实现了一个完整的错误处理系统,它具备:

  • 统一的错误类型
  • 完善的错误处理机制
  • 可靠的panic恢复
  • 高效的日志记录
  • 完整的监控指标

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!