Go错误与日志处理—推荐实践

news2024/11/29 3:20:05

错误的分类

在 Go 语言中,错误是通过实现 error 接口的类型表示的,但不同场景下的错误可以按性质和用途进行分类。以下是 Go 语言错误的常见分类,以及每类错误的解释和示例:


标准错误类型

标准库中定义了许多常见的错误类型,用于表示各种常见的错误场景。以下是一些 Go 标准库中常见的错误类型和相关包:

errors.Newfmt.Errorf

  • 用于创建自定义的错误。

  • 标准库提供的最基础的错误类型。

示例

import ( 
    "errors" 
    "fmt"
) 

err1 := errors.New("this is an error") 
err2 := fmt.Errorf("formatted error: %d", 42)

IO 相关错误

io

  • 包含基础 I/O 操作的错误类型。

常见错误

  • io.EOF:表示流结束(End Of File)。

  • io.ErrUnexpectedEOF:在读取流时遇到意外的 EOF。

  • io.ErrClosedPipe:操作已关闭的管道。

示例

import "io" 
if err == io.EOF { 
    fmt.Println("Reached end of file") 
}

文件操作相关错误

os

  • 处理文件系统相关的错误。

常见错误

  • os.ErrNotExist:文件或目录不存在。

  • os.ErrExist:文件或目录已经存在。

  • os.ErrPermission:权限不足。

  • os.ErrInvalid:无效操作。

示例: 

import "os" 

if errors.Is(err, os.ErrNotExist) {
    fmt.Println("File does not exist") 
}

网络相关错误

net

  • 网络操作相关的错误。

常见错误

  • net.InvalidAddrError:无效地址错误。

  • net.UnknownNetworkError:未知网络类型错误。

  • net.AddrError:地址解析错误。

  • net.DNSError:域名解析错误。

示例

import "net"

_, err := net.LookupHost("invalid_domain")
if dnsErr, ok := err.(*net.DNSError); ok {
     fmt.Println("DNS error:", dnsErr) 
}

 JSON 相关错误

encoding/json

  • JSON 编码和解码的错误。

常见错误

  • json.InvalidUnmarshalError:解码到无效的目标。

  • json.UnmarshalTypeError:JSON 与目标类型不匹配。

示例

import "encoding/json" 

var data interface{}
err := json.Unmarshal([]byte("invalid json"), &data) 

if syntaxErr, ok := err.(*json.SyntaxError); ok { 
    fmt.Println("JSON Syntax Error at offset:", syntaxErr.Offset) 
}

HTTP 相关错误

net/http

  • HTTP 请求与响应相关的错误。

常见错误

  • http.ErrHandlerTimeout:HTTP 处理程序超时。

  • http.ErrBodyNotAllowed:HTTP 请求体不被允许。

示例

import "net/http" 

if errors.Is(err, http.ErrHandlerTimeout) { 
    fmt.Println("HTTP handler timeout") 
}

时间解析相关错误

time

  • 处理时间解析或格式化错误。

常见错误

  • time.ErrBad:时间字符串格式错误。

示例

import "time"

_, err := time.Parse("2006-01-02", "invalid-date")
if err != nil { 
    fmt.Println("Time parsing error:", err) 
}

数据库相关错误

database/sql

  • 数据库操作相关的错误。

常见错误

  • sql.ErrNoRows:查询未返回结果。

  • sql.ErrTxDone:事务已完成,不能再执行操作。

示例

import "database/sql" 

if errors.Is(err, sql.ErrNoRows) { 
    fmt.Println("No rows found") 
}

压缩解压相关错误

compress/gzip

  • 用于处理 gzip 格式的错误。

常见错误

  • gzip.ErrHeader:gzip 文件头错误。


加密解密相关错误

cryptocrypto/x509

  • 加密或证书解析相关错误。

常见错误

  • x509.IncorrectPasswordError:密码错误。

  • x509.UnknownAuthorityError:未知的证书颁发机构。


按错误来源分类

应用级错误

应用程序逻辑中定义的错误,如输入验证失败、业务规则不满足等。这些错误通常由程序员明确定义。

示例

type ValidationError struct { 
    Field string 
    Msg string 
} 

func (e ValidationError) Error() string { 
    return fmt.Sprintf("validation failed on field %s: %s", e.Field, e.Msg) 
}

系统级错误

系统资源相关的错误,包括文件访问、网络问题等。 示例

func readConfig(filename string) error { 
    _, err := os.ReadFile(filename) 
    if err != nil {
        return fmt.Errorf("failed to read config: %w", err) 
    } 
    return nil 
}

第三方库错误

使用第三方库时返回的错误,需要通过文档或代码了解这些错误的含义,并采取适当措施。 示例

func sendMessageToKafka() error { 
    err := producer.SendMessage(message) 
    if err != nil { 
        return fmt.Errorf("kafka producer error: %w", err) 
    } 
    return nil 
}

按错误处理方式分类

可恢复错误

可以通过重新尝试或特定逻辑处理恢复的错误。 示例

func retryOperation(attempts int) error { 
    for i := 0; i < attempts; i++ {
        err := doSomething() 
        if err == nil { 
            return nil 
        } 
        time.Sleep(1 * time.Second) // 等待后重试 
    } 
    return fmt.Errorf("operation failed after %d attempts", attempts) 
}

不可恢复错误

表示程序的逻辑或系统的严重错误,无法通过重新尝试解决,如非法状态、编程错误等。

示例

func mustDivide(a, b int) int { 
    if b == 0 { 
        panic("division by zero") 
    } 
    return a / b 
}


按错误语义分类

用户输入错误

用户提供的输入不满足预期导致的错误。

示例

func validateInput(input string) error { 
    if input == "" {
        return fmt.Errorf("input cannot be empty") 
    } 
    return nil 
}

数据处理错误

数据格式、解析、转换等问题。

示例

func parseInt(value string) (int, error) { 
    num, err := strconv.Atoi(value) 
    if err != nil { 
        return 0, fmt.Errorf("failed to parse integer: %w", err) 
    } 
    return num, nil 
}

网络/IO 错误

网络连接失败、超时、文件系统操作失败等问题。

示例

func fetchData(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("failed to fetch data: %w", err) } defer resp.Body.Close() return io.ReadAll(resp.Body) }

业务逻辑错误

业务逻辑不满足需求导致的错误。

示例

func checkAccountBalance(balance, withdrawAmount float64) error { 
    if withdrawAmount > balance { 
        return fmt.Errorf("insufficient balance") 
    } 
    return nil 
}

按错误表现分类

明确错误

明确的错误通过 error 接口表示,并具有清晰的语义。

示例

return fmt.Errorf("unable to connect to database: %w", err)

模糊错误

返回的错误缺乏上下文信息,不利于调试。

示例

return errors.New("something went wrong") // 不清楚具体问题是什么

总结

Go 中的错误分类可以帮助开发者更清晰地理解错误的来源和性质,从而制定合理的处理策略。推荐:

  1. 使用明确的错误上下文。

  2. 尽量细化错误类型,尤其是应用级错误。

  3. 使用 errors.Iserrors.As 对错误进行分类处理。

  4. 在必要的场景下记录日志,但不要重复记录错误信息。


错误处理规范

错误检查与优先处理

  • 及时检查错误:不要忽略返回的错误值。

  • 优先处理错误:如果发生错误,尽快中止当前流程或采取修复措施。

好的示例:

func readFile(filename string) ([]byte, error) { 
    data, err := os.ReadFile(filename) 
    if err != nil { 
        return nil, fmt.Errorf("failed to read file %s: %w", filename, err) 
    } 
    return data, nil 
}

坏的示例:

func readFile(filename string) ([]byte, error) { 
    data, _ := os.ReadFile(filename) // 忽略错误,可能导致不可预见的问题 
    return data, nil 
}

使用错误包装提供上下文信息

  • 使用 fmt.Errorf%w 包装错误,保留错误链路。

  • 错误信息应清晰表明发生错误的上下文。

好的示例:

func processFile(filename string) error { 
    file, err := os.Open(filename) 
    if err != nil { 
        return fmt.Errorf("failed to open file %s: %w", filename, err) 
    } 
    defer file.Close() // 文件处理逻辑... return nil 
}

坏的示例:

func processFile(filename string) error { 
    _, err := os.Open(filename) 
    if err != nil { 
        return err // 丢失了错误上下文,难以追踪来源 
    } 
    return nil 
}

自定义错误类型

  • 针对特定业务场景,创建自定义错误类型以提供丰富的上下文。

好的示例:

type ValidationError struct { 
    Field string Message string 
} 

func (e ValidationError) Error() string { 
    return fmt.Sprintf("validation failed on field %s: %s", e.Field, e.Message) 
} 

func validateInput(input string) error { 
    if input == "" { 
        return ValidationError{"input", "cannot be empty"} 
    } 
    return nil 
}

坏的示例:

func validateInput(input string) error { 
    if input == "" { 
        return fmt.Errorf("invalid input") // 错误信息缺乏上下文 
    } 
    return nil 
}

使用 errors.Iserrors.As 检查错误

  • 使用 errors.Is 检查错误是否是某种特定类型。

  • 使用 errors.As 提取并处理特定的错误类型。

对比

特性errors.Iserrors.As
用途检查错误值是否相等或包装目标错误检查错误是否为特定类型
参数错误和目标错误值错误和目标错误类型的指针
返回值布尔值布尔值,目标指针可能会被赋值
支持链式错误
适用场景判断是否是某个特定错误判断是否属于某个特定类型的错误

好的示例:

func handleError(err error) { 
    if errors.Is(err, os.ErrNotExist) { 
        fmt.Println("文件不存在") 
    } 
    var pathErr *os.PathError 
    
    if errors.As(err, &pathErr) { 
        fmt.Printf("路径错误: %s\n", pathErr.Path) 
    } 
}

避免滥用 panic,使用显式错误返回

  • panic 仅用于不可恢复的错误,普通错误应返回 error

  • 提供有意义的错误信息。

好的示例:

func divide(a, b int) (int, error) { 
    if b == 0 { 
        return 0, fmt.Errorf("division by zero") 
    } 
    return a / b, nil 
}

坏的示例:

func divide(a, b int) int { 
    if b == 0 { 
        panic("division by zero") // 滥用 panic,不建议用于常规错误处理 
    } 
    return a / b 
}

日志与错误分离

  • 错误和日志分层:日志应由调用方处理,库函数仅返回错误。

  • 日志通常在服务层或调用者处理,库函数不应记录日志。

好的示例:

func fetchData(url string) ([]byte, error) { 
    resp, err := http.Get(url) 
    if err != nil { 
        return nil, fmt.Errorf("failed to fetch data from %s: %w", url, err) 
    } 
    defer resp.Body.Close() 
    
    if resp.StatusCode != http.StatusOK { 
        return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) 
    } 
    return io.ReadAll(resp.Body) 
}

坏的示例:

func fetchData(url string) ([]byte, error) { 
    resp, err := http.Get(url) 
    if err != nil { 
        log.Printf("error: %v", err) // 不必要的日志记录 
        return nil, err 
    } 
    defer resp.Body.Close() 
    
    return io.ReadAll(resp.Body) 
}

使用 defer 简化资源清理

  • 使用 defer 保证资源在函数退出时被正确释放。

好的示例:

func processLargeFile(filename string) error { 
    file, err := os.Open(filename) 
    if err != nil { 
        return fmt.Errorf("failed to open file: %w", err) 
    } 
    defer file.Close() // 确保资源释放 

    // 文件处理逻辑... 
    return nil 
}

坏的示例:

func processLargeFile(filename string) error { 
    file, err := os.Open(filename) 
    if err != nil { 
        return err 
    } // 如果忘记关闭文件,会导致资源泄露 
    file.Close() 
    return nil 
}

分层处理错误

  • 在业务逻辑层返回错误,允许调用方决定是否记录日志。

  • 在顶层捕获错误并进行统一处理。

好的实践:

// 库函数 
func queryDatabase(query string) ([]Record, error) { 
    rows, err := db.Query(query) 
    if err != nil { 
        return nil, fmt.Errorf("database query failed: %w", err) 
    } 
    defer rows.Close() // 解析数据... 

    return records, nil 
} 

// 应用层 
func handleRequest(query string) { 
    records, err := queryDatabase(query) 
    if err != nil { 
        log.Printf("query error: %v", err) 
        return 
    } 
    fmt.Println(records) 
}

错误与日志处理的推荐实践

不同层级对错误和日志的处理

数据访问层DAL/Repository)

职责:直接与数据库或其他持久化存储交互。

  • 错误处理

    • 返回具体的、易于处理的错误。例如:SQL 执行失败、数据未找到等。

    • 尽量使用错误包装 (fmt.Errorf),为上层提供上下文。

    • 不要记录日志,交由上层决定是否需要记录。

  • 示例

func GetUserByID(id int) (*User, error) { 
    user := &User{} 
    err := db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name) 
    if errors.Is(err, sql.ErrNoRows) { 
        return nil, fmt.Errorf("user with ID %d not found: %w", id, err) 
    } 
    if err != nil { 
        return nil, fmt.Errorf("failed to fetch user: %w", err) 
    } 
    return user, nil 
}

服务层(Service/Use Case)

职责:实现业务逻辑。

  • 错误处理

    • 捕获底层错误并添加业务语义上下文。

    • 根据需要返回特定业务错误或通用错误。

    • 可对一些关键错误进行日志记录(如影响业务流程的错误)。

  • 日志记录

    • 记录错误可能对调试或审计有价值的信息。

    • 日志应包含业务上下文(如用户 ID、请求参数等)。

  • 示例

func ProcessOrder(orderID int) error { 
    order, err := repo.GetOrderByID(orderID) 
    if err != nil { 
        return fmt.Errorf("failed to process order %d: %w", orderID, err) 
    } 
    if order.Status != "pending" { 
        return fmt.Errorf("order %d is not in a pending state", orderID) 
    } 
    // 业务逻辑... 
    return nil 
}

控制器层(Controller/Handler)

职责:处理用户请求并返回响应。

  • 错误处理

    • 将服务层的错误转换为用户友好的消息(HTTP 状态码或自定义响应)。

    • 不应暴露底层实现细节。

  • 日志记录

    • 在请求入口处记录重要的请求信息。

    • 在错误返回时记录错误上下文和请求相关信息。

  • 示例

func OrderHandler(w http.ResponseWriter, r *http.Request) { 
    orderID, err := strconv.Atoi(r.URL.Query().Get("id")) 
    if err != nil { 
        http.Error(w, "invalid order ID", http.StatusBadRequest) 
        return 
    } 
    err = service.ProcessOrder(orderID) 
    if err != nil { 
        log.Printf("failed to process order: %v", err) 
        http.Error(w, "failed to process order", http.StatusInternalServerError) 
        return 
    } 
    w.WriteHeader(http.StatusOK) 
}

错误与日志的推荐实践

错误返回层级

  1. 底层(如 DAL

    1. 返回详细的上下文错误,方便上层理解问题。

    2. 不记录日志,避免重复记录。

  2. 中间层(如 Service)

    1. 包装底层错误,提供业务相关的上下文。

    2. 根据需要选择是否记录关键日志。

  3. 顶层(如 Controller

    1. 转换错误为用户友好的消息。

    2. 记录完整的请求上下文和错误信息。


日志记录层级

  1. 入口点(Controller/Handler)

    1. 记录请求相关的信息(URL、参数、用户身份等)。

    2. 记录最终的响应状态。

  2. 服务层

    1. 记录对业务有重要影响的错误或状态变化。

  3. 数据层

    1. 尽量不记录日志,避免暴露内部实现细节。


常见反模式与改进

  1. 重复记录日志

    1. 底层记录错误,上层再次记录相同错误,导致日志冗余。

    2. 改进:仅在一个明确的层级记录日志。

  2. 暴露内部错误细节

    1. 直接将数据库错误返回到用户端。

    2. 改进:在顶层捕获并转换为用户友好的消息。

  3. 忽略日志上下文

    1. 日志中缺乏关键信息(如用户 ID、操作参数等)。

    2. 改进:在日志中包含足够的上下文信息。


总结

错误处理应遵循逐层封装的原则,每一层专注于自身职责,避免信息泄漏或日志冗余。日志记录应关注调试和审计价值,并在错误信息中添加业务或操作上下文,从而提高系统的可维护性和可观测性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2249478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s调度器扩展(scheduler)

1.K8S调度器 筛选插件扩展 为了熟悉 K8S调度器扩展步骤&#xff0c;目前只修改 筛选 插件 准备环境&#xff08;到GitHub直接下载压缩包&#xff0c;然后解压&#xff0c;解压要在Linux系统下完成&#xff09; 2. 编写调度器插件代码 在 Kubernetes 源代码目录下编写调度插件…

Qt桌面应用开发 第七天(绘图事件 绘图设备)

目录 1.绘图事件paintEvent 2.高级绘图 3.图片绘制 4.绘图设备 4.1QPixmap 4.2QBitmap 4.3QImage 4.4QPicture 1.绘图事件paintEvent paintEvent——绘图事件 需求&#xff1a;利用QPainter绘制点、线、圆、矩形、文字&#xff1b;设置画笔改为红色&#xff0c;宽度为…

Spring Boot 3 集成 Spring Security(3)数据管理

文章目录 准备工作新建项目引入MyBatis-Plus依赖创建表结构生成基础代码 逻辑实现application.yml配置SecurityConfig 配置自定义 UserDetailsService创建测试 启动测试 在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security&#xff08;1&#xff09;认证》和 《…

ChatGPT的应用场景:开启无限可能的大门

ChatGPT的应用场景:开启无限可能的大门 随着人工智能技术的快速发展,自然语言处理领域迎来了前所未有的突破。其中,ChatGPT作为一款基于Transformer架构的语言模型,凭借其强大的语言理解和生成能力,在多个行业和场景中展现出了广泛的应用潜力。以下是ChatGPT八个最具代表…

13 —— 开发环境调错-source map

问题&#xff1a;代码被压缩后&#xff0c;无法正确定位源代码的位置&#xff08;行数和列数&#xff09; source map&#xff1a;准确追踪error和warning在原始代码的位置 —— webpack.config.js配置devtool选项 module.exports { devtool: inline-source-map }; inline-s…

水库大坝安全监测之量水堰计应用

量水堰计是水库大坝安全监测系统中的一种关键设备&#xff0c;主要用于测量水库水位、流量等水力参数。以下是量水堰计在水库大坝安全监测中的应用及注意事项&#xff1a; 一、量水堰计的工作原理 量水堰计是一种专门用于测量水流流量的仪器&#xff0c;其工作原理主要基于水流…

Scrapy图解工作流程-cnblog

1.1 介绍部分&#xff1a; 文字提到常用的Web框架有Django和Flask&#xff0c;接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分&#xff1a; Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…

复合查询和内外连接

文章目录 1. 简单查询2. 多表查询2.1 显示雇员名、雇员工资以及所在部门的名字2.2 显示部门号为10的部门名&#xff0c;员工名和工资2.3 显示各个员工的姓名&#xff0c;工资&#xff0c;及工资级别 3. 自连接4. 子查询4.1 where后的子查询4.1.1 单行子查询4.1.2 多行子查询 (i…

UniApp开发实战:常见报错解析与解决方案

UniApp开发实战&#xff1a;常见报错解析与解决方案 病例1、TypeError: undefined is not an object (evaluating ‘this. s c o p e . scope. scope.getAppWebview’) 需求&#xff1a;获取页面示例&#xff0c;动态修改头部搜索框内容&#xff0c;获取页面实例时候报错unde…

Docker 容器网络创建网桥链接

一、网络&#xff1a;默认情况下&#xff0c;所有的容器都以bridge方式链接到docker的一个虚拟网桥上&#xff1b; 注意&#xff1a;“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位&#xff0c;它表示子网掩码中有16个连续的1&#xff0c;后面跟着16个连续的0。用于区分…

一个开源轻量级的服务器资源监控平台,支持告警推送

大家好&#xff0c;今天给大家分享一款开源的轻量级服务器资源监控工具Beszel&#xff0c;提供历史数据记录、Docker容器统计信息监控以及多种警报功能&#xff0c;用于监控服务器资源。 项目介绍 Beszel由hub&#xff08;中心服务器端应用&#xff0c;基于PocketBase构建&…

使用Compose Multiplatform开发跨平台的Android调试工具

背景 最近对CMP跨平台很感兴趣&#xff0c;为了练手&#xff0c;在移动端做了一个Android和IOS共享UI和逻辑代码的天气软件&#xff0c;简单适配了一下双端的深浅主题切换&#xff0c;网络状态监测&#xff0c;刷新调用振动器接口。 做了两年多车机Android开发&#xff0c;偶…

[MRCTF2020]Transform

查壳&#xff0c;拖入64位IDA LOBYTE8位就是一个字节&#xff0c;在此处无意义&#xff0c;因为我们输入的本来就是按字节输入的 设 a byte_414040,bdword_40F040,cbyte_40F0E0,输入的字符串为flag; 从题目里得到 加密代码 a[i] flag[b[i]]; a[i] ^ b[i]; c a 即c[i] a[i…

podman 源码 5.3.1编译

1. 构建环境 在麒麟V10服务器操作系统上构建&#xff1a;Kylin-Server-V10-GFB-Release-2204-Build03-ARM64.iso。由于只是编译 podman 源码&#xff0c;没必要特地在物理机或服务上安装一个这样的操作系统&#xff0c;故采用在虚拟机里验证。 2. 安装依赖 参考资料&#xf…

Llmcad: Fast and scalable on-device large language model inference

题目&#xff1a;Llmcad: Fast and scalable on-device large language model inference 发表于2023.09 链接&#xff1a;https://arxiv.org/pdf/2309.04255 声称是第一篇speculative decoding边缘设备的论文&#xff08;不一定是绝对的第一篇&#xff09;&#xff0c;不开源…

用Java爬虫“搜刮”工厂数据:一场数据的寻宝之旅

引言&#xff1a;数据的宝藏 在这个数字化的时代&#xff0c;数据就像是隐藏在数字丛林中的宝藏&#xff0c;等待着勇敢的探险家去发掘。而我们&#xff0c;就是那些手持Java魔杖的现代海盗&#xff0c;准备用我们的爬虫船去征服那些数据的海洋。今天&#xff0c;我们将一起踏…

14、保存与加载PyTorch训练的模型和超参数

文章目录 1. state_dict2. 模型保存3. check_point4. 详细保存5. Docker6. 机器学习常用库 1. state_dict nn.Module 类是所有神经网络构建的基类&#xff0c;即自己构建一个深度神经网络也是需要继承自nn.Module类才行&#xff0c;并且nn.Module中的state_dict包含神经网络中…

【计算机网络】多路转接之poll

poll也是一种linux中的多路转接方案(poll也是只负责IO过程中的"等") 解决&#xff1a;1.select的fd有上限的问题&#xff1b;2.每次调用都要重新设置关心的fd 一、poll的使用 int poll(struct pollfd *fds, nfds_t nfds, int timeout); ① struct pollfd *fds&…

矩阵重新排列——sort函数

s o r t sort sort函数表示排序&#xff0c;对向量和矩阵都成立 向量 s o r t ( a ) sort(a) sort(a)将向量 a a a中元素从小到大排序 s o r t ( a , ′ d e s c e n d ′ ) sort(a,descend) sort(a,′descend′)将向量 a a a中元素从大到小排序 [ s o r t a , i d ] s o r…

深入解密 K 均值聚类:从理论基础到 Python 实践

1. 引言 在机器学习领域&#xff0c;聚类是一种无监督学习的技术&#xff0c;用于将数据集分组成若干个类别&#xff0c;使得同组数据之间具有更高的相似性。这种技术在各个领域都有广泛的应用&#xff0c;比如客户细分、图像压缩和市场分析等。聚类的目标是使得同类样本之间的…