# v1.1.0 - 错误处理与日志规范

## 错误处理最佳实践

### 错误码设计

```go
package code

// 错误码规则: 服务号(2位) + 模块号(2位) + 错误号(4位)
const (
    // 通用错误 00xxxx
    ErrSuccess          = 0
    ErrUnknown          = 100001
    ErrInvalidParam     = 100002
    ErrUnauthorized     = 100003
    ErrForbidden        = 100004
    ErrNotFound         = 100005
    ErrInternal         = 100006
    ErrTimeout          = 100007
    ErrRateLimit        = 100008
    
    // 业务错误 01xxxx (Biz服务)
    ErrUserNotFound     = 101001
    ErrChatNotFound     = 101002
    ErrMessageNotFound  = 101003
    ErrNotMember        = 101004
    ErrBanned           = 101005
    ErrFloodWait        = 101006
    
    // 支付错误 02xxxx (Premium)
    ErrPaymentFailed    = 102001
    ErrInsufficientFund = 102002
    ErrInvalidPlan      = 102003
    ErrAlreadyPremium   = 102004
)

type BizError struct {
    Code    int
    Message string
    Detail  string
}

func (e *BizError) Error() string {
    return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}

func NewError(code int, detail string) error {
    msg := GetErrorMsg(code)
    return &BizError{Code: code, Message: msg, Detail: detail}
}
```

### 错误包装与追踪

```go
// 错误包装，添加上下文
func (c *Core) ProcessMessage(ctx context.Context, req *Request) (*Response, error) {
    // 数据库操作
    msg, err := c.dao.GetMessage(ctx, req.MessageID)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, NewError(ErrMessageNotFound, 
                fmt.Sprintf("message_id=%d", req.MessageID))
        }
        // 包装错误，添加上下文
        return nil, fmt.Errorf("failed to get message: %w", err)
    }
    
    // 权限检查
    if err := c.checkPermission(ctx, req.UserID, msg); err != nil {
        return nil, fmt.Errorf("permission check failed: %w", err)
    }
    
    return msg, nil
}
```

### gRPC错误转换

```go
// 业务错误转换为gRPC错误
func ToGRPCError(err error) error {
    if err == nil {
        return nil
    }
    
    var bizErr *BizError
    if errors.As(err, &bizErr) {
        switch bizErr.Code {
        case ErrInvalidParam:
            return status.Error(codes.InvalidArgument, bizErr.Error())
        case ErrUnauthorized:
            return status.Error(codes.Unauthenticated, bizErr.Error())
        case ErrForbidden:
            return status.Error(codes.PermissionDenied, bizErr.Error())
        case ErrNotFound:
            return status.Error(codes.NotFound, bizErr.Error())
        case ErrRateLimit:
            return status.Error(codes.ResourceExhausted, bizErr.Error())
        default:
            return status.Error(codes.Internal, bizErr.Error())
        }
    }
    
    return status.Error(codes.Unknown, err.Error())
}
```

## 结构化日志规范

### 日志级别使用规范

```go
// ERROR - 系统错误，需要立即处理
// 使用场景: 数据库连接失败、关键依赖不可用
log.Error().
    Err(err).
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Msg("database connection failed")

// WARN - 警告，需要关注但系统可用
// 使用场景: 降级处理、非关键依赖失败、接近阈值
log.Warn().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Int("queue_depth", queueDepth).
    Msg("message queue depth approaching limit")

// INFO - 关键业务流程
// 使用场景: 请求处理完成、状态变更、重要决策点
log.Info().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Int64("msg_id", msgID).
    Dur("latency", duration).
    Msg("message sent successfully")

// DEBUG - 调试信息
// 使用场景: 详细的执行路径、变量值、函数入口/出口
log.Debug().
    Str("service", "biz").
    Str("method", "sendMessage").
    Int64("user_id", userID).
    Str("request", req.String()).
    Msg("processing request")
```

### 统一日志格式

```go
package logger

import (
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

// InitLogger 初始化结构化日志
func InitLogger(service string, env string) {
    // 设置时间格式
    zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
    
    // 根据环境设置日志级别
    if env == "production" {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
    } else {
        zerolog.SetGlobalLevel(zerolog.DebugLevel)
    }
    
    // 添加全局字段
    log.Logger = log.With().
        Str("service", service).
        Str("env", env).
        Timestamp().
        Logger()
}

// ContextLogger 从context获取带trace_id的logger
func ContextLogger(ctx context.Context) zerolog.Logger {
    if traceID, ok := ctx.Value("trace_id").(string); ok {
        return log.With().Str("trace_id", traceID).Logger()
    }
    return log.Logger
}
```

### 分布式追踪日志

```go
// TraceMiddleware 添加追踪ID
func TraceMiddleware(next HandlerFunc) HandlerFunc {
    return func(ctx context.Context, req Request) (Response, error) {
        traceID := generateTraceID()
        ctx = context.WithValue(ctx, "trace_id", traceID)
        
        logger := logger.ContextLogger(ctx)
        logger.Info().
            Str("method", req.Method).
            Str("client_ip", req.ClientIP).
            Msg("request started")
        
        start := time.Now()
        resp, err := next(ctx, req)
        
        logger.Info().
            Dur("duration", time.Since(start)).
            Bool("success", err == nil).
            Msg("request completed")
        
        return resp, err
    }
}
```

## 错误重试策略

### 指数退避重试

```go
package retry

import (
    "context"
    "time"
)

type Config struct {
    MaxRetries  int
    InitialDelay time.Duration
    MaxDelay     time.Duration
    Multiplier   float64
}

func DefaultConfig() Config {
    return Config{
        MaxRetries:   3,
        InitialDelay: 100 * time.Millisecond,
        MaxDelay:     5 * time.Second,
        Multiplier:   2.0,
    }
}

// Do 执行带重试的函数
func Do(ctx context.Context, cfg Config, fn func() error) error {
    var err error
    delay := cfg.InitialDelay
    
    for i := 0; i <= cfg.MaxRetries; i++ {
        err = fn()
        if err == nil {
            return nil
        }
        
        // 不可重试的错误直接返回
        if !IsRetryable(err) {
            return err
        }
        
        // 最后一次不重试
        if i == cfg.MaxRetries {
            break
        }
        
        // 等待后重试
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(delay):
            delay = time.Duration(float64(delay) * cfg.Multiplier)
            if delay > cfg.MaxDelay {
                delay = cfg.MaxDelay
            }
        }
    }
    
    return fmt.Errorf("max retries exceeded: %w", err)
}

// IsRetryable 判断错误是否可重试
func IsRetryable(err error) bool {
    // 超时、连接错误、5xx错误可重试
    if errors.Is(err, context.DeadlineExceeded) {
        return true
    }
    
    var bizErr *code.BizError
    if errors.As(err, &bizErr) {
        switch bizErr.Code {
        case code.ErrTimeout, code.ErrInternal:
            return true
        }
    }
    
    return false
}
```

## 使用示例

```go
// 带重试的数据库操作
err := retry.Do(ctx, retry.DefaultConfig(), func() error {
    return c.dao.UpdateMessage(ctx, msg)
})

// 带日志的业务操作
func (c *Core) SendMessage(ctx context.Context, req *SendMessageReq) (*Message, error) {
    logger := logger.ContextLogger(ctx)
    
    logger.Debug().
        Int64("to_user", req.Peer.UserID).
        Str("message_preview", preview(req.Message)).
        Msg("sending message")
    
    // 检查配额
    allowed, err := c.checkQuota(ctx, req.FromUserID)
    if err != nil {
        logger.Error().
            Err(err).
            Msg("failed to check quota")
        return nil, err
    }
    
    if !allowed {
        logger.Warn().
            Int64("user_id", req.FromUserID).
            Msg("quota exceeded")
        return nil, code.NewError(code.ErrRateLimit, "daily quota exceeded")
    }
    
    // 发送消息
    msg, err := c.doSend(ctx, req)
    if err != nil {
        logger.Error().
            Err(err).
            Msg("failed to send message")
        return nil, err
    }
    
    logger.Info().
        Int64("msg_id", msg.ID).
        Int64("to_user", req.Peer.UserID).
        Msg("message sent")
    
    return msg, nil
}
```
