操作日志、访问日志、异常日志
这篇解决什么
系统里有两类、四种日志:审计侧记「谁对哪条业务做了什么」、登录侧记进出账号;接口侧记每次 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 |
| 落库 | insert | ruoyi-office/yudao-module-system/.../service/logger/OperateLogServiceImpl.java |
| 列表接口 | @RequestMapping("/system/operate-log") | .../controller/admin/logger/OperateLogController.java |
| 查询 API | GET /page、GET /get | ruoyi-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 放进上下文,模板才能读到编号和昵称。
@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();
}对应文案:
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 入参。
@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 |
DeptParseFunction | function = 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 在账号错、用户停用、验证码错、登录成功、主动登出、强制踢下线时各写一条。

本系统截图:系统管理 → 审计日志 → 登录日志。登录类型、结果用字典展示。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 写入 | 同步 createLoginLog | ruoyi-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:export | LoginLogController |
username 是冗余字段:账号后来改了,历史行仍能按当时的名字搜。成功登录还会 updateUserLogin,刷新用户上的最后登录时间和 IP。
失败也要落库
密码错、停用、验证码错都会写日志,userId 在用户还不存在时可以为空。排障时先看结果枚举,再看操作日志。
访问日志
记录每次管理端 REST:URL、方法、耗时、结果码,以及可读的操作模块、操作名。由 Filter 自动记,不必每个接口手写。

本系统截图:基础设施 → 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 |
| 落库 | 有租户直接插;没有则 executeIgnore | ruoyi-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 会再并上去。
未读站内信数量会被前端轮询,必须关掉:
@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。
导出接口通常标操作类型,避免被猜成「查询」:
@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 日志 → 错误日志。行内可标「已处理」或「已忽略」。
示意图:兜底异常处理器异步落库。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 记录 | defaultExceptionHandler 调 createExceptionLog | ruoyi-office/yudao-framework/yudao-spring-boot-starter-web/.../GlobalExceptionHandler.java |
| 异步入口 | @Async | yudao-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-status | ApiErrorLogController |
| 清理 Job | 物理删 14 天前,每批 100 条 | .../job/logger/ErrorLogCleanJob.java |
exceptionClassName / exceptionMethodName / exceptionLineNumber 取堆栈第一帧,方便从列表跳到代码。processUserId、processTime 在点「已处理 / 已忽略」时写入。
业务异常不会进错误日志
ServiceException 走 serviceExceptionHandler,只返回错误码。只有兜底的 Exception(以及 cause 不是业务异常的情况)才异步插入。参数校验失败、权限拒绝不要来这张表里找。
排障时用同一条 traceId 串访问日志、错误日志、操作日志和控制台。访问日志的异步细节见 异步任务。错误码和 CommonResult 见 异常处理(错误码)。
配置与操作
操作日志、登录日志在 系统管理 → 审计日志 只读查询,没有「开启写入」开关,登录和 @ApiAccessLog 会自动落库。API 访问 / 错误日志在 基础设施 → API 日志。
清理靠定时任务或库策略,页面保存不会改采集规则。排障用同一条 traceId。开启见 系统功能设计。截图见上文各列表。
