Skip to content

操作日志、访问日志、异常日志 ​

这篇解决什么 ​

系统里有两类、四种日志:审计侧记「谁对哪条业务做了什么」、登录侧记进出账号;接口侧记每次 HTTP 调用,以及未吞掉的系统异常。读完能给 Service 加 @LogRecord、给接口加 @ApiAccessLog,并在菜单里对上四张列表。

默认端口 48080,管理端前缀 /admin-api。审计日志在 system 模块落库;API 日志在 infra 模块落库。访问日志、操作日志、错误日志的插入都走 @Async,不挡响应。

示意图:请求进 yudao-server;业务模块写审计,框架写访问和异常。

名称说明仓库路径
操作日志谁、何时、对哪条业务、做了什么表 system_operate_log
登录日志登录、登出;成功和失败都记表 system_login_log
访问日志每次 REST 的 URL、耗时、结果码表 infra_api_access_log
错误日志未处理的系统异常和堆栈表 infra_api_error_log
操作 / 登录页系统管理 → 审计日志ruoyi-office-vben/apps/web-antd/src/views/system/operatelog/、loginlog/
访问 / 错误页基础设施 → API 日志.../views/infra/apiAccessLog/、apiErrorLog/

四页路由分别是 /system/log/operate-log、/system/log/login-log、/infra/log/api-access-log、/infra/log/api-error-log。官方菜单 id:108 审计日志,500 / 501 操作与登录,1083 API 日志,1078 / 1084 访问与错误。

操作日志 ​

记「谁」在「什么时间」对「什么对象」做了「什么事情」。要人手在 Service 上标 @LogRecord,框架不会按 HTTP 方法自动补一条业务语义。

操作日志列表

本系统截图:系统管理 → 审计日志 → 操作日志。列是操作人、模块、操作名、内容、业务编号。

组件与落库 ​

记录由 yudao-spring-boot-starter-security 的 operatelog 包完成,底层是 mzt-biz-log。@EnableLogRecord 在自动配置里打开。AOP 切完后,LogRecordServiceImpl 补用户、模块、URL,再异步写入 system。

示意图:注解在业务方法上;当前线程填 DTO,插入在线程池。

名称说明仓库路径
@EnableLogRecord打开操作日志切面ruoyi-office/yudao-framework/yudao-spring-boot-starter-security/.../YudaoOperateLogConfiguration.java
LogRecordServiceImpl把切面结果转成 DTO.../operatelog/core/service/LogRecordServiceImpl.java
异步入口@Async 默认方法yudao-framework/yudao-common/.../OperateLogCommonApi.java
落库insertruoyi-office/yudao-module-system/.../service/logger/OperateLogServiceImpl.java
列表接口@RequestMapping("/system/operate-log").../controller/admin/logger/OperateLogController.java
查询 APIGET /page、GET /getruoyi-office-vben/apps/web-antd/src/api/system/operate-log/index.ts
导出权限system:operate-log:export同上 Controller 的 /export-excel

OperateLogDO 里不那么直观的字段:

名称说明仓库路径
traceId和访问日志、错误日志、控制台对上.../dal/dataobject/logger/OperateLogDO.java
type / subType大模块、操作名,例如 SYSTEM 用户 / 创建用户同上
bizId业务主键,来自注解 bizNo同上
action整句明细,可含 diff同上
extra扩展 JSON同上
userType管理端 / 会员UserTypeEnum

常量建议集中在各模块的 LogRecordConstants,方便检索,不是强制。

创建用户 ​

AdminUserServiceImpl#createUser 在插入后把 user 放进上下文,模板才能读到编号和昵称。

java
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
        success = SYSTEM_USER_CREATE_SUCCESS)
public Long createUser(UserSaveReqVO createReqVO) {
    // ...
    userMapper.insert(user);
    LogRecordContext.putVariable("user", user);
    return user.getId();
}

对应文案:

java
String SYSTEM_USER_TYPE = "SYSTEM 用户";
String SYSTEM_USER_CREATE_SUB_TYPE = "创建用户";
String SYSTEM_USER_CREATE_SUCCESS = "创建了用户【{{#user.nickname}}】";

路径:ruoyi-office/yudao-module-system/yudao-module-system-api/.../enums/LogRecordConstants.java。

角色的创建、更新、删除同一套,常量前缀是 SYSTEM_ROLE_*。

修改用户 ​

更新要用 _DIFF,才能写出「【备注】从 A 改成 B」。先把旧对象放进 DiffParseFunction.OLD_OBJECT,再让模板 diff 入参。

java
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
        success = SYSTEM_USER_UPDATE_SUCCESS)
public void updateUser(UserSaveReqVO updateReqVO) {
    AdminUserDO oldUser = validateUserForCreateOrUpdate(/* ... */);
    // 更新库表 ...
    LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
    LogRecordContext.putVariable("user", oldUser);
}

成功文案模板是「更新了用户【#user.nickname】: {_DIFF{#updateReqVO}}」,# 前面是双花括号,mzt-biz-log 取值语法。

UserSaveReqVO 上要标 @DiffLogField。name 是中文列名;function 把 id 译成名称。

名称说明仓库路径
@DiffLogField参与 diff 的字段.../controller/admin/user/vo/user/UserSaveReqVO.java
DeptParseFunctionfunction = getDeptById.../framework/operatelog/core/DeptParseFunction.java
PostParseFunction岗位名同目录
SexParseFunction性别同目录
角色 VO角色名称、标志、状态同样打了注解.../permission/vo/role/RoleSaveReqVO.java

先插库再放上下文

bizNo 写成双花括号包着 #user.id,依赖插入后的主键。创建路径必须先 insert,再 LogRecordContext.putVariable。

更多写法 ​

操作日志不会扫所有 Controller。system 里目前是用户、角色;CRM 的 LogRecordConstants 最全,线索、客户、商机、合同、回款都有创建、更新、转移、跟进。

路径:ruoyi-office/yudao-module-crm/yudao-module-crm-api/.../enums/LogRecordConstants.java。详情页用公共组件 OperateLog 按 bizId 拉时间线,见 ruoyi-office-vben/apps/web-antd/src/components/operate-log/。

CRM 另有 /crm/operate-log/page,按业务对象过滤,不是系统菜单那张总表。

登录日志 ​

记录登录、登出,包括失败。AdminAuthServiceImpl 在账号错、用户停用、验证码错、登录成功、主动登出、强制踢下线时各写一条。

登录日志列表

本系统截图:系统管理 → 审计日志 → 登录日志。登录类型、结果用字典展示。

名称说明仓库路径
写入同步 createLoginLogruoyi-office/yudao-module-system/.../service/auth/AdminAuthServiceImpl.java
表@TableName("system_login_log").../dal/dataobject/logger/LoginLogDO.java
列表@RequestMapping("/system/login-log").../controller/admin/logger/LoginLogController.java
类型100 账号、101 社交、103 手机、104 短信;200 自己登出、202 强制退出.../enums/logger/LoginLogTypeEnum.java
结果0 成功、10 账号或密码错、20 停用、30 / 31 验证码.../enums/logger/LoginResultEnum.java
导出权限system:login-log:exportLoginLogController

username 是冗余字段:账号后来改了,历史行仍能按当时的名字搜。成功登录还会 updateUserLogin,刷新用户上的最后登录时间和 IP。

失败也要落库

密码错、停用、验证码错都会写日志,userId 在用户还不存在时可以为空。排障时先看结果枚举,再看操作日志。

访问日志 ​

记录每次管理端 REST:URL、方法、耗时、结果码,以及可读的操作模块、操作名。由 Filter 自动记,不必每个接口手写。

API 访问日志列表

本系统截图:基础设施 → API 日志 → 访问日志。应用名来自 spring.application.name。

示意图:Filter 在请求线程组包,插入异步。

名称说明仓库路径
Filter请求结束后组包;异常也会记ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../ApiAccessLogFilter.java
开关yudao.access-log.enable,没配则开启.../apilog/config/YudaoApiLogAutoConfiguration.java
注解关日志、脱敏、补操作名.../apilog/core/annotation/ApiAccessLog.java
落库有租户直接插;没有则 executeIgnoreruoyi-office/yudao-module-infra/.../ApiAccessLogServiceImpl.java
列表@RequestMapping("/infra/api-access-log").../controller/admin/logger/ApiAccessLogController.java
查询权限infra:api-access-log:query同上
清理 Job物理删 14 天前,每批 100 条.../job/logger/AccessLogCleanJob.java

requestParams 最长 8000,resultMsg 最长 512,超长截断。operateType 见 OperateTypeEnum:GET / CREATE / UPDATE / DELETE / EXPORT / IMPORT / OTHER。没标注解时,按 HTTP 方法猜:GET→查询,POST→新增,PUT→修改,DELETE→删除。

注解 ​

名称说明仓库路径
enable默认 true。轮询接口应关掉@ApiAccessLog
requestEnable默认记请求参数同上
responseEnable默认不记响应,列表接口体量大同上
sanitizeKeys额外脱敏字段同上
operateModule空则用类上 @Tag 的 name同上
operateName空则用方法 @Operation 的 summary同上
operateType空则按 HTTP 方法OperateTypeEnum

全局默认脱敏键:password、token、accessToken、refreshToken。注解里的 sanitizeKeys 会再并上去。

未读站内信数量会被前端轮询,必须关掉:

java
@GetMapping("/get-unread-count")
@ApiAccessLog(enable = false)
public CommonResult<Long> getUnreadNotifyMessageCount() {
    return success(notifyMessageService.getUnreadNotifyMessageCount(
            getLoginUserId(), UserTypeEnum.ADMIN.getValue()));
}

路径:ruoyi-office/yudao-module-system/.../controller/admin/notify/NotifyMessageController.java。

导出接口通常标操作类型,避免被猜成「查询」:

java
@GetMapping("/export-excel")
@PreAuthorize("@ss.hasPermission('infra:api-access-log:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportApiAccessLogExcel(/* ... */) { /* ... */ }

为什么访问日志也有操作模块

操作日志要手写,容易漏。访问日志总会记。补上 operateModule / operateName / operateType 后,运维可以按业务名筛,而不只看 URL。

本地默认不落库

application-local.yaml、application-dev.yaml、application-prod.yaml 都写了 yudao.access-log.enable: false。列表里若只有旧数据,把当前 profile 改成 true 再重启。没配这项时,自动配置按开启处理。

控制台 ​

ApiAccessLogInterceptor 始终注册。非 prod 时打两条:[开始请求] 带 URL 和参数,[完成请求] 带耗时。prod 不打印。它还把 HandlerMethod 放进 request,Filter 才能读到 @ApiAccessLog 和 Swagger 注解。

路径:ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../interceptor/ApiAccessLogInterceptor.java。

错误日志 ​

记录未按业务异常处理的系统错误:请求、用户、异常名、根因、堆栈。ServiceException 只回 CommonResult,不进这张表。

API 错误日志列表

本系统截图:基础设施 → API 日志 → 错误日志。行内可标「已处理」或「已忽略」。

示意图:兜底异常处理器异步落库。

名称说明仓库路径
记录defaultExceptionHandler 调 createExceptionLogruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../GlobalExceptionHandler.java
异步入口@Asyncyudao-framework/yudao-common/.../ApiErrorLogCommonApi.java
表@TableName("infra_api_error_log")ruoyi-office/yudao-module-infra/.../dal/dataobject/logger/ApiErrorLogDO.java
列表@RequestMapping("/infra/api-error-log").../controller/admin/logger/ApiErrorLogController.java
处理状态0 未处理、1 已处理、2 已忽略.../enums/logger/ApiErrorLogProcessStatusEnum.java
改状态PUT /update-status,权限 infra:api-error-log:update-statusApiErrorLogController
清理 Job物理删 14 天前,每批 100 条.../job/logger/ErrorLogCleanJob.java

exceptionClassName / exceptionMethodName / exceptionLineNumber 取堆栈第一帧,方便从列表跳到代码。processUserId、processTime 在点「已处理 / 已忽略」时写入。

业务异常不会进错误日志

ServiceException 走 serviceExceptionHandler,只返回错误码。只有兜底的 Exception(以及 cause 不是业务异常的情况)才异步插入。参数校验失败、权限拒绝不要来这张表里找。

排障时用同一条 traceId 串访问日志、错误日志、操作日志和控制台。访问日志的异步细节见 异步任务。错误码和 CommonResult 见 异常处理(错误码)。

配置与操作 ​

操作日志、登录日志在 系统管理 → 审计日志 只读查询,没有「开启写入」开关,登录和 @ApiAccessLog 会自动落库。API 访问 / 错误日志在 基础设施 → API 日志。

清理靠定时任务或库策略,页面保存不会改采集规则。排障用同一条 traceId。开启见 系统功能设计。截图见上文各列表。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版