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

embedded/2024/12/3 2:32:54/

错误处理最佳实践

一、课程概述

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

二、错误处理框架实现

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

package errorhandlingimport ("fmt""runtime""strings""time"
)// ErrorCode 错误码类型
type ErrorCode int// ErrorLevel 错误级别
type ErrorLevel intconst (// 错误级别定义LevelDebug ErrorLevel = iotaLevelInfoLevelWarnLevelErrorLevelFatal
)// 基础错误码定义
const (ErrCodeSuccess ErrorCode = iotaErrCodeInvalidParamErrCodeInternalErrorErrCodeTimeoutErrCodeNotFoundErrCodeUnauthorized
)// 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,}}// 创建新的AppErrorreturn &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] = valuereturn 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}// 转换为AppErrorappErr, 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 errorhandlingimport ("encoding/json""fmt""os""sync""time"
)// ErrorLogger 错误日志记录器
type ErrorLogger struct {mu        sync.Mutexfile      *os.Fileformatter ErrorFormatterbuffer    []LogEntrybufferSize intflushInterval 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 errorhandlingimport ("context""fmt""runtime/debug""sync"
)// PanicHandler panic处理器
type PanicHandler struct {handlers []func(interface{}, []byte)logger   *ErrorLoggermu       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 *PanicHandlerwg      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 mainimport ("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() {// 模拟panicpanic("something went wrong")
}// 自定义错误处理示例
type DatabaseError struct {*AppErrorQuery 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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!


http://www.ppmy.cn/embedded/142478.html

相关文章

webrtc ios h264 硬编解码

webrtc ios h264 硬编解码 一 ios 系统支持 从ios8开始,苹果公司开放了硬解码和硬编码API(即 VideoToolbox.framework API) 二 主要api 1 主要解码函数 VTDecompressionSessionCreate // 创建解码 session VTDecompressionSession…

【摸鱼】Docker配置主从mysql数据库环境

docker pull mysql拉取docker镜像,国内现在访问不了docker hub,可以去阿里云上镜像加速器地址https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors启动主库docker run -p 3306:3306 --name master-mysql --privilegedtrue -v /app/docker/data…

鸿蒙启航日志:探索华为科技之旅的第一天

鸿蒙学习之旅启航:首日深度探索华为的科技世界 今天,我正式踏上了学习鸿蒙系统的征途,心中充满了对未知的好奇与期待。作为一名具备一定计算机基础的学习者,我深知这次学习之旅不仅是一次技术的挑战,更是一次深入了解…

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环,网络上也有很多 UI 自动化相关的知识或资料,具体到 windows 端的 UI 自动化,我们需要从以下几个方面考虑: 开发语言 毋庸置疑,在 UI 自动化测试领域&am…

vi/vim文件管理命令练习

一.练习要求 文件管理命令练习: (1)在/opt目录下创建一个临时目录tmp; (2)在临时目录下创建一个文件,文件名为a.txt;vi/vim练习: (1) 应用vi命令在/tmp文件夹下创建文…

jmeter学习(7)命令行控制

jmeter -n -t E:\IOT\test2.jmx -l E:\IOT\output\output.jtl -j E:\IOT\output\jmeter.log -e -o E:\IOT\output\report IOT下创建output 文件夹,jmx文件名避免中文,再次执行output.jtl不能有数据要删除

【攻防世界】WEB-inget

首先找到该关卡 启动靶场环境 访问靶场 构造一个id参数,尝试访问,无内容回显 使用sqlmap工具,先获取数据库,输入命令sqlmap -u http://61.147.171.105:58893/?id1 --dbs 发现第一个即为所需数据库,接下来进行获取…

【微服务】Nacos配置管理

一、统一配置管理 1、配置统一管理 2、微服务获取配置 ①引入Nacos的配置管理客户端依赖(usersevice下) <!--nacos的配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-confi…