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

错误的分类

在 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. 改进:在日志中包含足够的上下文信息。


总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

典型组合逻辑电路设计

目录 行为级描述方式基本运算电路 一、半加器&#xff08;Half Adder&#xff09; 二、全加器&#xff08;Full Adder&#xff09; 1、逻辑门构成加法器 2、集成全加器 3、串行加法器 4、超前进位加法器 三、全减器(Full Deductor) 数值比较电路 一、一位比较器 二、…

【论文阅读】三平面相关与变体

文章目录 1. 【CVPR2023】Tri-Perspective View for Vision-Based 3D Semantic Occupancy Prediction动机可视化方法Pipeline 2. 【2023/08/31】PointOcc: Cylindrical Tri-Perspective View for Point-based 3D Semantic Occupancy Prediction动机&#xff08;针对雷达点云、与…

修改bag的frame_id的工具srv_tools

在使用数据集导航或者建图时&#xff0c;bag中的点云或者其他话题的frame_id没有和需要的对应 1.创建工作空间 2.cd xxxx/src 3.git clone https://github.com/srv/srv_tools.git cd .. catkin_make source ./devel/setup.bash rosrun bag_tools change_frame_id.py -t /要改…

hue 4.11容器化部署,已结合Hive与Hadoop

配合《Hue 部署过程中的报错处理》食用更佳 官方配置说明页面&#xff1a; https://docs.gethue.com/administrator/configuration/connectors/ 官方配置hue.ini页面 https://github.com/cloudera/hue/blob/master/desktop/conf.dist/hue.ini docker部署 注意&#xff1a; …

如何用Excel做数据可视化自动化报表?

作为一个经常需要做数据报表的人&#xff0c;我最常用的工具是Excel&#xff0c;对于我来说用Excel处理繁琐冗杂的数据并不难&#xff0c;但是我发现身边很多人用Excel做的数据报表非常的耗时&#xff0c;而且最后的成品也是难以直视&#xff0c;逻辑和配色等都非常的“灾难”。…

layui table 纵向滚动条导致单元格表头表体错位问题

我用的时layui2.6.8版本 历史项目维护&#xff0c;bug给我让我做了&#xff0c;本来利用前端手段强解决&#xff0c;后来发现很多table 找了解决办法 打开layui-v2.6.8/lay/modules/table.js 如果打开后时压缩的代码 直接搜索 e.find(".layui-table-patch") …

C语言学习笔记:流程控制和数据输入输出

流程控制和数据的输入输出 算法 著名计算机科学家沃思提出了一个公式&#xff1a; 数据结构 算法 程序 数据结构&#xff1a;对数据的描述 算法&#xff1a;对操作步骤的描述 算法定义 广义的说&#xff0c;为解决一个问题而采取的方法和有限的步骤&#xff0c;就称为“…

旋转图像(java)

题目描述&#xff1a; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 代码思路&#xff1a; class Solution {public void ro…

windows 应用 UI 自动化实战

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

【R语言管理】Pycharm配置R语言及使用Anaconda管理R语言虚拟环境

目录 使用Anaconda创建R语言虚拟环境1. 安装Anaconda2. 创建R语言虚拟环境 Pycharm配置R语言1. 安装Pycharm2. R Language for IntelliJ插件 参考 使用Anaconda创建R语言虚拟环境 1. 安装Anaconda Anaconda的安装可参见另一博客-【Python环境管理工具】Anaconda安装及使用教程…

C语言进程编程

getpid函数&#xff1a; 原型&#xff1a;pid_t getpid(void) 特性&#xff1a;返回值是PID值 用途&#xff1a;获取当前进程PID 用法例 #include<stdio.h> #include <sys/types.h> #include<unistd.h> int main() {pid_t pid;pid getpid();printf(&qu…

SpringMVC |(一)SpringMVC概述

文章目录 &#x1f4da;SpringMVC概述&#x1f407;三层架构&#x1f407;异步调用 &#x1f4da;SpringMVC入门案例&#x1f407;入门案例&#x1f407;注意事项 &#x1f4da;小结 学习来源&#xff1a;黑马程序员SSM框架教程_SpringSpringMVCMaven高级SpringBootMyBatisPlus…

Android 桌面窗口新功能推进,聊一聊 Android 桌面化的未来

Android 桌面化支持可以说是 Android 15 里被多次提及的 new features&#xff0c;例如在 Android 15 QPR1 Beta 2 里就提到为 Pixel 平板引入了桌面窗口支持&#xff0c;桌面窗口允许用户在自由窗口同时运行多个应用&#xff0c;同时可以像在传统 PC 平台上一样调整这些窗口的…

Vue+Vite 组件开发的环境准备(零基础搭建)

一、什么是Vite Vue3作为一款现代化的JavaScript框架&#xff0c;配合Vite这样的构建工具&#xff0c;极大地简化了流程&#xff0c;提升了效率。Vite 是一个基于现代浏览器原生的 ES 模块系统&#xff0c;能够以原生模块导入的方式运行源代码的开发服务器。它被设计用来替代传…

linux高级系统编程之进程

进程 一个正在进行的程序 并行与并发 并行:执行的程序在不同CPU上同时执行 并发:一个CPU,多个进程交替执行,因为交替速度很快,所以从宏观上来看是同时执行的,但是从围观的角度是交替执行的 单道与多道 单道程序设计:所有进程一个一个排队执行,若A阻塞,B只能等待,,即使CPU处于空…

git 命令之只提交文件的部分更改

git 命令之只提交文件的部分更改 有时&#xff0c;我们在一个文件中进行了多个更改&#xff0c;但只想提交其中的一部分更改。这时可以使用 使用 git add -p 命令 Git add -p命令允许我们选择并添加文件中的特定更改。它将会显示一个交互式界面&#xff0c;显示出文件中的每个更…

Excel中根据某列内容拆分为工作簿

简介&#xff1a;根据A列的内容进行筛选&#xff0c;将筛选出来的数据生成一个新的工作簿(可以放到指定文件夹下)&#xff0c;且工作簿名为筛选内容。 举例&#xff1a; 将上面的内容使用VBA会在当前test1下生成5个工作簿&#xff0c;工作簿名分别为TEST1.xls TEST2.xls TEST3…

数据结构 (10)队列

前言 队列是一种特殊的数据结构&#xff0c;它遵循先进先出&#xff08;FIFO&#xff0c;First In First Out&#xff09;的原则。 一、定义与基本概念 定义&#xff1a;队列是一种只允许在一端&#xff08;队尾&#xff09;进行插入操作&#xff0c;而在另一端&#xff08;队头…

Tomcat10部署Servlet加载错误问题解决

Servlet加载错误&#xff1a;HelloServlet不是Servlet 环境信息&#xff1a;IDEA中的maven项目&#xff0c;tomcat10.1.33 问题信息&#xff1a;XXX.Servlet不是Servlet 问题原因&#xff1a;tomcat10将JavaEE也换成了Jakarta EE&#xff1b; Jakarta EE较以前的JavaEE有一个重…

2024年第十三届”认证杯“数学中国数学建模国际赛(小美赛)

↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓