# 06-日志和错误处理规范 ## 概述 本规范基于项目现有代码实践,规定了日志记录和错误处理的统一标准。所有日志和错误处理必须遵循现有代码的实现方式,确保一致性和可维护性。 ## 日志规范 ### 日志框架 - 项目统一使用 SLF4J (Simple Logging Facade for Java) 作为日志门面。 - 底层实现使用 Logback。 - 在类中声明 Logger 实例: ```java private static final Logger logger = LoggerFactory.getLogger(ClassName.class); ``` ### 日志级别 - **DEBUG**: 用于调试信息,如方法调用参数、API 请求响应等。 - **INFO**: 用于重要业务逻辑信息,如令牌过期等。 - **WARN**: 用于警告信息,如 API 未返回预期数据。 - **ERROR**: 用于错误信息,如异常发生时。 ### 日志配置 - 日志配置文件位于 `src/main/resources/logback-spring.xml`。 - 输出到控制台和文件。 - 文件日志路径:`${LOG_PATH}/app.log`,默认 `./logs/app.log`。 - 日志轮转:按日期轮转,保留 30 天历史日志。 - 日志格式:`%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n`。 - 在 `application.yml` 中配置日志级别: ```yaml logging: level: root: info work.baiyun.chronicdiseaseapp: debug ``` ### 日志使用原则 - 在服务实现类中使用 Logger 记录关键操作和异常。 - 使用占位符记录变量值,如 `logger.debug("validateToken: token={}, userToken={}", token, ut)`。 - 异常记录时包含异常对象,如 `logger.error("Error while calling Weixin API", e)`。 ## 错误处理规范 ### 全局异常处理器 - 使用 `@RestControllerAdvice` 注解的 `CustomExceptionHandler` 类统一处理异常。 - 返回统一响应格式 `R`,包含错误码和消息。 ### 异常类型处理 - **BindException**: 参数绑定异常,提取所有错误消息返回。 - **ConstraintViolationException**: 参数校验异常,提取约束违反消息返回。 - **CustomException**: 自定义异常,返回异常消息。 - **Exception**: 未知异常,记录错误日志,返回通用错误信息。 ### 自定义异常 - 自定义业务异常 `CustomException` 在代码中继承自 `RuntimeException`(运行时异常),全局异常处理器会捕获并转换为统一 `R.fail()` 响应。 - 构造函数接受消息字符串: ```java public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } ``` ### 错误码体系 #### 错误码设计原则 1. **统一性**: 所有错误码在项目中保持统一格式和规范 2. **可读性**: 错误码具有明确的含义和业务指向 3. **可扩展性**: 错误码体系支持模块化扩展 4. **兼容性**: 与现有系统和API保持兼容 #### 错误码结构 错误码采用数字编码方式,每个错误码由5位数字组成: - 第1位:错误级别(1-通用错误,2-用户相关,3-微信相关,4-数据相关,5-地理位置相关) - 第2-5位:具体错误编号 #### 错误码分类 ##### 通用错误码 (1000-1999) | 错误码 | 错误信息 | 说明 | |--------|---------|------| | 1000 | 系统内部错误 | 未预期的系统错误 | | 1001 | 参数错误 | 请求参数不合法或缺失 | | 1002 | 未授权访问 | 缺少有效的身份认证信息 | | 1003 | 权限不足 | 当前用户无权执行该操作 | | 1004 | 资源不存在 | 请求的资源未找到 | | 1005 | 请求方法不被允许 | 使用了不支持的HTTP方法 | ##### 用户相关错误码 (2000-2999) | 错误码 | 错误信息 | 说明 | |--------|---------|------| | 2000 | 用户不存在 | 指定的用户ID找不到对应用户 | | 2001 | 用户密码错误 | 提供的用户密码不正确 | | 2002 | 用户未登录 | 用户会话已过期或未登录 | | 2003 | 用户已存在 | 尝试创建已存在的用户 | | 2004 | 用户账户被锁定 | 用户账户因安全原因被锁定 | ##### 微信相关错误码 (3000-3999) | 错误码 | 错误信息 | 说明 | |--------|---------|------| | 3000 | 微信授权码无效 | 提供的微信code参数无效或已过期 | | 3001 | 获取微信openid失败 | 调用微信接口获取openid时失败 | | 3002 | 用户角色不能为空 | 创建用户时未指定角色类型 | | 3003 | 用户角色无效 | 提供的用户角色代码不合法 | ##### 数据相关错误码 (4000-4999) | 错误码 | 错误信息 | 说明 | |--------|---------|------| | 4000 | 数据不存在 | 指定的数据记录未找到 | | 4001 | 无权访问该数据 | 当前用户无权访问指定的数据 | | 4002 | 数据删除失败 | 执行数据删除操作时发生错误 | | 4003 | 数据保存失败 | 执行数据保存操作时发生错误 | ##### 地理位置相关错误码 (5000-5999) | 错误码 | 错误信息 | 说明 | |--------|---------|------| | 5000 | 地理位置请求失败 | 调用地理位置服务时发生错误 | | 5001 | 地理位置服务不可用 | 地理位置服务暂时不可用 | #### 错误码使用规范 ##### 错误码枚举类 项目中使用`ErrorCode`枚举类来定义和管理所有错误码,位于`work.baiyun.chronicdiseaseapp.enums.ErrorCode`。 ##### 控制器中使用错误码 所有控制器在返回错误响应时,应使用统一的[R.fail()](file:///D:/慢病APP/other/EmulatedProject/src/main/java/work/baiyun/chronicdiseaseapp/common/R.java#L30-L35)方法,并传入相应的错误码和错误信息: ```java // 正确用法 return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), ErrorCode.PARAMETER_ERROR.getMessage() + ": Missing code parameter"); // 错误用法(不推荐) return R.fail(400, "Missing code parameter"); ``` ##### 异常处理中使用错误码 在全局异常处理器或控制器的try-catch块中,应根据具体的异常类型返回相应的错误码: ```java try { // 业务逻辑 } catch (IllegalArgumentException e) { return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), ErrorCode.PARAMETER_ERROR.getMessage() + ": " + e.getMessage()); } catch (Exception e) { return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage() + ": " + e.getMessage()); } ``` ### 异常处理原则 - 业务逻辑中的异常应抛出 `CustomException` 或记录日志后处理。 - 全局异常处理器确保所有未捕获异常被统一处理。 - 异常信息不应暴露敏感数据给客户端。 - 所有控制器应统一使用R类返回错误响应,避免直接抛出异常,确保API响应格式的一致性。 ### ID 在日志与错误信息中的格式(补充) 建议: - 在记录日志或返回错误信息时,建议以字符串形式记录业务 ID,确保与对外 API 返回格式一致,便于排查与跨端比对。 - 避免在日志格式化中对大整数进行科学计数或截断(例如使用占位符 `{}` 直接记录字符串值)。 - 示例: ```java logger.info("处理记录完成,recordId={}", String.valueOf(record.getId())); ``` 注意:日志中以字符串记录 ID 并不改变数据库或实体类型,仅是记录/展示层的一致性约定。 ### 响应体中新增链路追踪字段(timestamp / requestId / traceId) 为便于调用方与日志系统做关联,API 的统一返回结构 `R` 已新增 `timestamp`、`requestId`、`traceId` 三个字段: - timestamp:返回时的 Unix 毫秒时间戳,用于对照日志时间或做简单耗时统计。 - requestId:HTTP 请求级别的唯一 ID,建议客户端设置 `X-Request-Id`,如果客户端未设置,服务端会自动生成。该 ID 也会保存在日志的 MDC 中(日志格式中使用 `%X{requestId}`),便于将一条响应与该请求在日志中关联起来。 - traceId:分布式链路追踪 ID(用于跨服务链路分析),建议由客户端或上游服务传入 `X-Trace-Id`;若未传入,服务端会生成一个 UUID。该字段也写入 MDC(`%X{traceId}`),便于配合 Zipkin/Jaeger 等链路追踪系统排查问题。 示例响应: ```json { "code": 1000, "message": "ok", "timestamp": 1697028512345, "requestId": "123e4567-e89b-12d3-a456-426614174000", "traceId": "abcd1234efgh5678", "data": { ... } } ``` 日志例子(logback pattern 建议包含 `%X{requestId}` 与 `%X{traceId}`): ``` 2025-11-14 10:00:00.000 [http-nio-8080-exec-1] [123e4567-e89b-12d3-a456-426614174000] [abcd1234efgh5678] INFO work.baiyun.chronicdiseaseapp.service.UserService - user created id=100 ``` 注意: - 不要在 `requestId` 或 `traceId` 中携带敏感信息(如 token、身份证号等)。这些字段只用于追踪和关联日志,非鉴权或隐私字段。 - 如果系统已接入 Spring Cloud Sleuth / OpenTelemetry 等链路追踪框架,优先使用它们提供的 traceId;本约定的 `traceId` 与第三方追踪工具应尽量保持一致(例如使用 `X-B3-TraceId` 或 `traceparent`)。 - 以上字段的加入旨在提升链路追踪与诊断效率,不影响现有字段的含义;若调用方不需要,可忽略这些字段。