操作日志
本页将帮助你快速了解 Ape‑Volo‑Admin 的操作日志使用与配置。
你将获得
- 如何开启/关闭操作日志
- 支持的采集字段与数据来源
- 直写数据库 vs 经 Redis 队列异步入库
- 全局注册方式与忽略记录的注解
- 常见问题与最佳实践(脱敏、体积控制、保留策略)
操作过滤器
位置:Ape.Volo.Infrastructure/ActionFilter/OperateLogFilter.cs
/// <summary>
/// 操作日志过滤器
/// </summary>
public class OperateLogFilter : IAsyncActionFilter
{
private readonly IOperateLogService _operateLogService;
private readonly ISettingService _settingService;
private readonly IBrowserDetector _browserDetector;
private readonly ISearcher _ipSearcher;
public OperateLogFilter(IOperateLogService operateLogService, ISearcher searcher,
ISettingService settingService, IBrowserDetector browserDetector)
{
_operateLogService = operateLogService;
_settingService = settingService;
_browserDetector = browserDetector;
_ipSearcher = searcher;
}
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
return Execute(context, next);
}
/// <summary>
/// 执行审计功能
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
private async Task Execute(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
var sw = new Stopwatch();
sw.Start();
var resultContext = await next();
sw.Stop();
var action = (ControllerActionDescriptor)context.ActionDescriptor;
if (action.MethodInfo.IsDefined(typeof(NotOperateAttribute), false))
{
return;
}
//执行结果
//var action = context.ActionDescriptor as ControllerActionDescriptor;
//var isTrue = action.MethodInfo.IsDefined(typeof(DescriptionAttribute), false);
var saveDb = await _settingService.GetSettingValue<bool>("IsOperateLogSaveDB");
if (saveDb && resultContext.Result.IsNotNull())
{
var operateLog = CreateOperateLog(context);
operateLog.ResponseData = resultContext.Result switch
{
ContentResult contentResult => contentResult.Content,
NoContentResult okResult => okResult.ToJson(),
OkObjectResult okResult => okResult.Value?.ToJson(),
FileContentResult fileContentResult => GetFileContentResult(fileContentResult),
ObjectResult objectResult => objectResult.Value?.ToJson(),
_ => null // 处理其他未知类型
};
//用时
operateLog.ExecutionDuration = sw.ElapsedMilliseconds;
if (App.GetOptions<SystemOptions>().UseRedisCache &&
App.GetOptions<MiddlewareOptions>().RedisMq.Enabled)
{
// 实时队列
// await App.GetService<ICache>().GetDatabase()
// .ListLeftPushAsync(MqTopicNameKey.OperateLogQueue, operateLog.ToJson());
//延迟队列
var stopTimeStamp = DateTime.Now.AddSeconds(10).ToUnixTimeStampSecond();
await App.GetService<ICache>().GetDatabase()
.SortedSetAddAsync(MqTopicNameKey.OperateLogQueue, operateLog.ToJson(), stopTimeStamp);
}
else
{
await Task.Factory.StartNew(() => _operateLogService.CreateAsync(operateLog))
.ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0";
var ipAddress = _ipSearcher.Search(remoteIp);
LogHelper.WriteLog(ExceptionHelper.ErrorFormat(context.HttpContext, remoteIp, ipAddress, ex,
App.HttpUser?.Account,
_browserDetector.Browser?.OS, _browserDetector.Browser?.DeviceType, _browserDetector.Browser?.Name,
_browserDetector.Browser?.Version), null);
}
}
/// <summary>
/// 创建审计对象
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private OperateLog CreateOperateLog(ActionExecutingContext context)
{
//略
return operateLog;
}
private string GetFileContentResult(FileContentResult fileContentResult)
{
//略
}
}功能概览
操作日志用于记录接口调用的关键信息,便于审计与问题追溯。其核心通过全局 IAsyncActionFilter 拦截请求,收集请求上下文、返回结果、执行耗时、客户端环境等信息,并根据开关选择“直接写库”或“写入 Redis 队列由后台异步入库”。
为什么需要操作日志(逐项说明系统带来的具体好处):
审计合规(Audit)
- 作用:保留谁在何时、从哪儿、通过哪个接口对系统做了什么的不可篡改记录。
- 带来价值:满足审计与合规要求(如变更审计、合规检查、法务取证),降低合规风险。
事故溯源与根因定位
- 作用:记录请求参数、返回结果与执行耗时,形成完整调用快照。
- 带来价值:快速重现问题场景(重放输入)、定位异常接口或异常请求链,缩短故障定位时间(MTTR)。
安全与异常检测
- 作用:捕获异常请求、异常返回、频繁失败或异常耗时的调用。
- 带来价值:检测越权、暴力破解、异常流量或滥用行为,便于触发告警与封禁策略。
责任追踪与审查
- 作用:将操作与用户/会话绑定(Account、IP、User‑Agent 等)。
- 带来价值:明确责任人,支持事后核查、审批与争议处理。
性能监控与容量规划
- 作用:按接口统计 ExecutionDuration、失败率与响应数据量。
- 带来价值:发现慢接口、识别热点 API、为优化、扩容与限流提供数据依据。
运营与产品分析
- 作用:记录业务事件(谁做了哪类操作、请求参数的业务维度)。
- 带来价值:分析功能使用率、用户行为路径、为产品决策与优先级提供量化支持。
回滚与补偿操作的依据
- 作用:提供操作历史与输入快照。
- 带来价值:在误操作或数据异常时,用日志作为回滚或补偿操作的参考。
测试与质量保障
- 作用:在灰度/回归验证期间记录外部交互与响应。
- 带来价值:验证变更影响,评估回归风险,帮助构建更可靠的测试用例。
运营告警与自动化响应
- 作用:基于日志统计触发阈值告警(消费积压、落库失败、异常增长等)。
- 带来价值:自动化运维响应与故障降级,降低人工处理成本与业务中断风险。
合理化存储与隐私控制(实践价值)
- 作用:结合脱敏、截断、白名单策略控制落库内容与体积。
- 带来价值:在满足审计要求的同时控制成本、降低泄露风险,并满足隐私合规(如脱敏、保留策略)。
简短建议(落地指引):
- 必需字段优先采集(路由、用户、时间、IP、参数摘要、耗时、结果摘要),避免直接落库敏感明文;
- 高流量/大体积场景优先使用异步队列(Redis 延迟队列)以降低接口 RT;
- 制定日志保留与归档策略,结合监控对消费、失败率和存储成本进行定期评估。
开关与前置条件
- IsOperateLogSaveDB(系统设置项):是否保存操作日志到数据库。
- 从
ISettingService.GetSettingValue<bool>("IsOperateLogSaveDB")读取。
- 从
- SystemOptions.UseRedisCache(配置项):启用后端 Redis。
- MiddlewareOptions.RedisMq.Enabled(配置项):启用 Redis 消息队列模块。
组合行为:
- 当 IsOperateLogSaveDB=true 且 UseRedisCache=true 且 RedisMq.Enabled=true 时,操作日志写入 Redis(延迟队列)等待后台消费入库。
- 当 IsOperateLogSaveDB=true,但任一 Redis 开关为 false 时,直接异步写入数据库。
- 当 IsOperateLogSaveDB=false 时,不进行持久化(仍会执行拦截但不落库)。
注意
IsOperateLogSaveDB 来源于系统设置服务(而非 appsettings.json)。请确保后台已配置该键,或在初始化阶段提供默认值。
采集范围与字段
默认情况下,所有未显式忽略的接口都会被记录,包含但不限于:
- 路由信息:Area、Controller、Action、Method、RequestUrl
- 文案描述:
[Description]特性值(支持多语言转换App.L.R) - 请求参数:Query/Form/Body 汇总(
HttpHelper.GetAllRequestParams) - 调用人:
App.HttpUser?.Account,登录接口特殊处理为提交的用户名 - 客户端环境:IP、地理位置(
ISearcher)、User-Agent、操作系统、设备类型、浏览器名称与版本(IBrowserDetector) - 执行耗时:毫秒(
Stopwatch) - 返回结果:根据结果类型提取(
OkObjectResult/ObjectResult/ContentResult/NoContentResult/FileContentResult等)- 对文件下载,记录文件名/大小/MIME 并附加 MD5 摘要
字段示例(实体 OperateLog):
- Id、CreateBy、CreateTime
- Area、Controller、Action、Method、Description、RequestUrl
- RequestParameters、RequestIp、IpAddress、UserAgent、OperatingSystem、DeviceType、BrowserName、Version
- ResponseData、ExecutionDuration
工作模式
- 直接写库:当未启用 Redis 或未启用 RedisMq 时,调用
_operateLogService.CreateAsync(operateLog)入库。 - Redis 队列:当同时启用 Redis 与 RedisMq 时,向
MqTopicNameKey.OperateLogQueue写入延迟任务:- 采用有序集合(SortedSet)写入,score 为未来 10 秒的 UNIX 时间戳,实现“延迟队列”。
- 后台消费方(独立任务/服务)按到期时间拉取并批量入库,降低接口响应时延与写库压力。
参见《消息队列(Redis)》文档了解队列与消费者的部署要点与运维监控。
全局注册
将操作日志过滤器注册为全局过滤器即可对所有接口生效:
services.AddControllers(options =>
{
options.Filters.Add<OperateLogFilter>();
});依赖项请确保已注册:IOperateLogService、ISettingService、IBrowserDetector、ISearcher 等。
忽略记录
对不需要记录的接口,可使用 [NotOperate] 注解标记方法(或控制器),过滤器将自动跳过:
[HttpGet]
[AllowAnonymous]
[NotOperate]
[Route("index")]
public Task<ActionResult> Index()
{
return Task.FromResult(Ok(new OperateResult { IsSuccess = true }));
}常见忽略场景:健康检查、内部回调、文件直传签名、频繁轮询等。
最佳实践
- 脱敏:对密码、令牌、身份证号、手机号等敏感字段进行掩码或过滤,避免明文落库。
- 体积控制:
ResponseData宜限制体积(如分页数据仅记录查询条件与总量,或对大对象进行截断)。 - 白/黑名单:结合路由前缀或标签进行有选择记录,降低存储压力。
- 异步入库:在高并发场景建议开启 RedisMq 模式,减少接口 RT。
- 监控与告警:对消费堆积、落库失败与异常进行监控,避免日志丢失。
常见问题(FAQ)
为什么没有生成日志?
- 检查 IsOperateLogSaveDB 设置是否为 true。
- 检查是否被
[NotOperate]标记忽略。 - 若启用队列,确认 Redis 连接与消费者服务运行正常。
返回文件如何记录?
- 通过
GetFileContentResult仅记录元信息与 MD5 摘要,不保存文件二进制,避免库膨胀。
- 通过
多语言描述如何生效?
- 通过
[Description]特性与App.L.R转换,确保资源文件存在对应键值。
- 通过
相关阅读:
- 《异常日志》
- 《Serilog 集成》

