代码编织梦想

错误处理最佳实践

一、课程概述

学习要点重要程度掌握目标
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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40780178/article/details/144073296

完美的错误处理:go 语言最佳实践分享_学长阿毛的博客-爱代码爱编程

Go 语言是一门非常流行的编程语言,由于其高效的并发编程和出色的网络编程能力,越来越受到广大开发者的青睐。在任何编程语言中,错误处理都是非常重要的一环,它关系到程序的健壮性和可靠性。Go 语言作为一门现代化的编程语言,自然也

【go语言成长之路】如何编写go代码-爱代码爱编程

文章目录 如何编写Go代码一、介绍二、代码组织三、第一个程序四、从模块导入包五、从远程模块导入包六、测试 如何编写Go代码 一、介绍 ​ 本文档演示了模块内简单 Go 包的开发,并介绍了 g

go-爱代码爱编程

go-zero 拦截器 有时我们需要在处理请求的过程中添加一些额外的逻辑,比如身份验证、日志记录、请求限流、性能监控等,这些都可以通过拦截器实现。go zero可以设置多个拦截器 一、 服务端拦截器 服务端拦截器用于处

go语言链接redis数据库-爱代码爱编程

1.使用go get命令安装go-redis/v8库: 我这里使用的vscode工具安装: go get github.com/go-redis/redis/v8 2.创建Redis客户端实例 使用以下Go代码

第二章:编写第一个 go 程序 1.hello world 程序 -爱代码爱编程

1. 编写代码 1.设置 Go 环境变量 使用 go env -w 命令可以永久设置 Go 环境变量。GO111MODULE=on 是一个常用的设置,用于确保在所有项目中启用模块化支持。 $ go env -w GO11

40分钟学 go 语言高并发:context包与并发控制-爱代码爱编程

Context包与并发控制 学习目标 知识点掌握程度应用场景context原理深入理解实现机制并发控制和请求链路追踪超时控制掌握超时设置和处理API请求超时、任务限时控制取消信号传播理解取消机制和传播链优雅退出、资源释放

40分钟学 go 语言高并发:超时控制与取消机制-爱代码爱编程

超时控制与取消机制 一、知识要点概述 知识模块重要程度掌握要求超时策略⭐⭐⭐⭐⭐深入理解context包的超时机制,掌握不同场景下的超时控制方法取消传播⭐⭐⭐⭐⭐熟练运用context的取消传播机制,实现优雅的任务取消资

40分钟学 go 语言高并发:pipeline模式(二)-爱代码爱编程

Pipeline模式(二) 一、实战应用示例 1.1 日志处理Pipeline 让我们实现一个处理日志文件的Pipeline示例: package main import ( "bufio" "co

40分钟学 go 语言高并发:【实战】并发安全的配置管理器-爱代码爱编程

【实战】并发安全的配置管理器 一、课程概述 学习要点重要程度掌握目标配置热更新★★★★★理解配置热更新原理,实现动态加载配置并发读写控制★★★★★掌握并发安全的读写控制机制观察者模式★★★★☆理解并实现配置变更通知机制版

40分钟学 go 语言高并发:pipeline模式(一)-爱代码爱编程

Pipeline模式 一、课程概述 学习要点重要程度掌握目标流水线设计★★★★★掌握Pipeline基本结构和设计原则扇入扇出★★★★☆理解并实现多输入多输出的Pipeline错误传播★★★★★掌握Pipeline中的错

40分钟学 go 语言高并发:【实战】并发安全的配置管理器(功能扩展)-爱代码爱编程

【实战】并发安全的配置管理器(功能扩展) 一、扩展思考 分布式配置中心 实现配置的集中管理支持多节点配置同步实现配置的版本一致性 配置加密 敏感配置的加密存储配置的安全传输访问权限控制 配置格式支持 支持YAM