Symfony2中实现数据库审计日志的关键是分层控制+主动埋点+结构化存储:用Doctrine事件监听器捕获实体级变更,或集成Sonata Entity Audit Bundle实现全量快照与版本回溯,并强制绑定用户、IP、请求上下文等可追责信息。

在 Symfony2 项目中实现数据库查询与变更的审计日志,关键不在于“拦截所有 SQL”,而在于**分层控制 + 主动埋点 + 结构化存储**。直接修改 PDO 或启用 MySQL 通用日志虽可行,但易失控、难过滤、影响性能。真正实用的方案应贴合 Symfony 的生命周期和 Doctrine 的抽象层。
用 Doctrine 事件监听器捕获实体级操作
这是最推荐的起点——轻量、可维护、语义清晰。Doctrine 提供 preUpdate、postPersist、onFlush 等事件钩子,适合记录“谁改了哪个实体、改了哪些字段”。
- 创建监听器类,实现
Doctrine\Common\EventSubscriber接口 - 在
getSubscribedEvents()中声明监听preUpdate和postPersist - 在
preUpdate(PreUpdateEventArgs $args)中获取变更前后的字段值:$args->getEntity()、$args->getOldValue('price')、$args->getNewValue('price') - 将审计数据写入独立日志表(如
audit_log),字段建议含:entity_class、entity_id、action(INSERT/UPDATE/DELETE)、changed_fields(JSON)、user_id(从 SecurityContext 获取)、ip、created_at
结合 Sonata Entity Audit Bundle 实现完整版本追踪
若需回溯任意时间点的实体快照(比如“还原 3 天前的产品价格”),Sonata Entity Audit Bundle 是成熟选择。它自动为被审计实体生成 _audit 表,并关联全局 revisions 表。
- 安装后,在实体类加注解:
@Audit\Auditable - 运行
php app/console doctrine:schema:update --force,自动生成 audit 表与 revisions 表 - 每次
$em->flush()时,Bundle 自动保存当前实体全量快照 + 当前用户(需配置username_provider) - 查询历史:用
$auditReader->find($entityClass, $id, $revision)即可取出指定版本的实体实例 - 注意:审计表体积增长快,建议对高频更新表谨慎启用,或配合定时归档策略
在数据库连接层统一记录原始 SQL(低侵入调试用)
当需要排查 ORM 未覆盖的原生查询(如 getConnection()->executeStatement()),可在 PDO 封装层添加日志逻辑。
- 扩展
Doctrine\DBAL\Connection或自定义LoggingPDO类,重写prepare()和executeStatement() - 记录内容包括:SQL 字符串、参数绑定值、执行耗时、调用栈(
debug_backtrace()截取前两层即可)、当前路由名或控制器名 - 日志建议输出到 Monolog 的独立通道(如
sql_audit),便于按 channel 过滤和轮转 - 生产环境务必关闭,仅在 dev 或特定 debug 模式下启用
补充权限与上下文信息(让日志真正可追责)
光有 SQL 或实体变更不够,必须绑定操作人和场景。否则日志里只看到 “admin 修改了订单状态”,却不知是运营手动改的,还是定时脚本触发的。
- 在审计记录中强制写入
security.token_storage->getToken()->getUser(),若为 CLI 脚本则标记为system:batch - 通过
RequestStack获取当前请求的client_ip、request_uri、method - 对非 Web 上下文(如 console 命令),在命令类构造函数中显式设置审计上下文:
$this->auditContext = ['source' => 'console', 'command' => $input->getArgument('name')] - 避免依赖 session 或 cookie,确保 CLI、Cron、API 调用等场景均有上下文落库











