错误处理最佳实践
一、课程概述
学习要点 | 重要程度 | 掌握目标 |
---|---|---|
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 错误设计原则
错误分类
- 业务错误
- 系统错误
- 第三方错误
错误信息
- 明确的错误码
- 详细的错误描述
- 必要的上下文信息
错误追踪
- 错误堆栈
- 错误链路
- 错误上下文
7.2 错误处理策略
错误恢复
- 及时发现
- 优雅降级
- 自动重试
错误隔离
- 错误边界
- 错误域分离
- 故障隔离
错误监控
- 错误统计
- 性能影响
- 告警机制
7.3 实现建议
错误封装
// 推荐 return &AppError{ Code: ErrCodeNotFound, Message: "user not found", Context: map[string]interface{}{ "user_id": id, }, } // 不推荐 return fmt.Errorf("user not found")
错误判断
// 推荐 if errors.Is(err, ErrNotFound) { // 处理特定错误 } // 不推荐 if err.Error() == "not found" { // 不要用字符串比较 }
panic处理
// 推荐 defer func() { if r := recover(); r != nil { // 具体的恢复逻辑 } }() // 不推荐 if err != nil { panic(err) // 避免随意使用panic }
7.4 日志记录准则
- 日志级别
- Debug:调试信息
- Info:常规信息
- Warn:警告信息
- Error:错误信息
- Fatal:致命错误
- 日志内容
- 时间戳
- 错误级别
- 错误描述
- 错误堆栈
- 上下文信息
- 日志格式
- 结构化日志
- JSON格式
- 统一格式化
7.5 测试建议
- 错误测试
- 测试所有错误分支
- 验证错误恢复机制
- 检查错误信息准确性
- 异常测试
- 模拟panic场景
- 测试恢复机制
- 验证资源清理
- 性能测试
- 错误处理性能
- 日志写入性能
- 内存使用情况
通过以上内容,我们实现了一个完整的错误处理系统,它具备:
- 统一的错误类型
- 完善的错误处理机制
- 可靠的panic恢复
- 高效的日志记录
- 完整的监控指标
怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!