Go Web 编程实战Go Web 编程实战
首页
课程导读
首页
课程导读
  • 课程导读
  • 课程目录

    • Go基础语法精要
    • Web基础与HTTP处理
    • Gin框架入门
    • 路由与中间件
    • 数据库操作
    • 模板渲染
    • 错误处理与日志
    • 测试与部署
    • 高级中间件模式
    • 微服务架构
    • 性能优化
    • 安全最佳实践
    • 项目结构与代码组织

错误处理与日志

1. Go错误处理基础

Go使用简单的error接口处理错误:

// 基本错误处理
file, err := os.Open("config.json")
if err != nil {
    // 处理错误
    log.Printf("无法打开配置文件: %v", err)
    return err
}
defer file.Close()

// 错误比较
if err == io.EOF {
    // 处理EOF
}

// 自定义错误
var ErrNotFound = errors.New("未找到")

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, ErrNotFound
    }
    // ...
}

2. 错误包装与上下文

Go 1.13+引入了错误包装:

import "errors"

func processFile(path string) error {
    data, err := readFile(path)
    if err != nil {
        return fmt.Errorf("处理文件失败: %w", err)
    }
    // ...
}

func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("读取文件 %s 失败: %w", path, err)
    }
    return data, nil
}

// 使用errors.Is检查特定错误
err := processFile("config.json")
if errors.Is(err, os.ErrNotExist) {
    // 处理文件不存在的特殊情况
}

3. 自定义错误类型

对于更复杂的错误场景,可以定义自定义错误类型:

type HTTPError struct {
    Code    int
    Message string
    Err     error
}

func (e *HTTPError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("%d: %s (%v)", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("%d: %s", e.Code, e.Message)
}

func NewHTTPError(code int, message string, err error) *HTTPError {
    return &HTTPError{
        Code:    code,
        Message: message,
        Err:     err,
    }
}

// 使用示例
func getUserHandler(w http.ResponseWriter, r *http.Request) {
    user, err := getUser(r.URL.Query().Get("id"))
    if err != nil {
        httpErr := NewHTTPError(http.StatusNotFound, "用户未找到", err)
        log.Println(httpErr)
        http.Error(w, httpErr.Message, httpErr.Code)
        return
    }
    json.NewEncoder(w).Encode(user)
}

4. 日志记录基础

Go标准库log提供基本日志功能:

// 基本日志配置
log.SetPrefix("WEB: ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

// 不同级别日志
log.Println("普通日志")      // 标准输出
log.Printf("%s日志", "格式化") 
log.Fatalln("致命错误")     // 输出后调用os.Exit(1)
log.Panicln("恐慌日志")     // 输出后调用panic()

5. 结构化日志(使用zap)

企业应用推荐使用结构化日志库如zap:

import "go.uber.org/zap"

func main() {
    // 生产环境配置(高性能JSON格式)
    logger, _ := zap.NewProduction()
    defer logger.Sync() // 刷新缓冲
    
    // 开发环境配置(易读控制台格式)
    // logger, _ := zap.NewDevelopment()
    
    // 记录结构化日志
    logger.Info("用户登录",
        zap.String("username", "alice"),
        zap.Int("attempt", 3),
        zap.Duration("duration", time.Second),
    )
    
    // 错误日志示例
    if err := doSomething(); err != nil {
        logger.Error("操作失败",
            zap.String("module", "payment"),
            zap.Error(err),
        )
    }
}

6. Gin框架中的错误处理

func main() {
    r := gin.New()
    
    // 全局恢复中间件
    r.Use(gin.Recovery())
    
    // 自定义错误处理
    r.Use(func(c *gin.Context) {
        c.Next() // 处理请求
        
        // 检查是否有错误
        if len(c.Errors) > 0 {
            for _, err := range c.Errors {
                log.Printf("请求错误: %v", err)
            }
            
            c.JSON(http.StatusInternalServerError, gin.H{
                "errors": c.Errors.Errors(),
            })
        }
    })
    
    r.GET("/", func(c *gin.Context) {
        if err := doSomething(); err != nil {
            c.Error(err) // 收集错误
            return
        }
        c.String(200, "OK")
    })
    
    r.Run(":8080")
}

提示:良好的错误处理和日志记录是系统可维护性的关键。建议在项目早期就建立统一的错误处理规范。

Prev
模板渲染
Next
测试与部署