Symfony2无法默认按请求路径分目录写日志,因Monolog配置不支持运行时变量插值;推荐用PathProcessor注入path字段,再通过shell命令(如grep+sort+uniq)按路径统计频次。

如何让 Symfony2 日志按模块路径自动分目录写入
默认的 Monolog 不支持按请求路径(如 /api/user、/admin/dashboard)自动创建子目录并写入日志文件。必须手动扩展处理器或改用路由上下文过滤,否则所有日志都挤在 dev.log 或 prod.log 里,没法做路径维度的统计分析。
核心思路是:在日志处理器中动态提取当前请求的 path info,并据此构造日志文件路径。但注意——Symfony2 的 Request 对象在日志处理器初始化时不可用,不能直接在 StreamHandler 构造阶段读取;必须通过 LoggerInterface 的上下文参数 + 自定义处理器实现延迟解析。
- 推荐做法:不改 Monolog 原生 Handler,而是用
Processor注入路径信息,在日志记录时带上path字段,再配合外部脚本做归类统计 - 硬核做法:继承
Monolog\Handler\StreamHandler,重写write()方法,在每次写入前从$record['context']提取path并拼进$this->url - 更稳妥的做法:在控制器入口统一埋点,用
$logger->info('request', ['path' => $request->getPathInfo(), 'method' => $request->getMethod()]),后续用grep或 ELK 按path聚合
为什么不能直接用 %kernel.environment%.log 实现路径分目录
%kernel.environment%.log 是静态占位符,由容器编译期解析,无法响应每次请求的动态路径。你写成 "%kernel.logs_dir%/api/%request.path%.log" 这种写法会直接报错——Monolog 配置不支持运行时变量插值,%request.path% 不是合法的容器参数名,也不会被替换。
常见错误现象:InvalidConfigurationException 报错提示 “The child node ‘path’ does not exist” 或 “Unable to replace alias”, 实际是因为 YAML 解析器把 %xxx% 当作容器参数去查,而 request 不是参数,是服务。
- 不要尝试在
monolog.yaml里写path: "%kernel.logs_dir%/%request.path_info%.log" - 不要依赖
Kernel::getLogDir()动态拼接路径后传给 Handler——Handler 初始化只执行一次,路径不会随请求变化 - 若强行用
eval或反射修改StreamHandler::$url,会导致并发写入冲突和文件句柄泄漏
用 Processor 实现请求路径自动注入
这是 Symfony2 兼容性最好、侵入性最小的方案。Processor 在每条日志写入前被调用,此时 Request 已可用(需确保你在 app_dev.php 或 AppKernel 中启用了 RequestContext)。
示例:在 src/YourBundle/Monolog/PathProcessor.php 中定义:
use Symfony\Component\HttpFoundation\RequestStack;
use Monolog\Processor\ProcessorInterface;
<p>class PathProcessor implements ProcessorInterface
{
private $requestStack;</p><pre class='brush:php;toolbar:false;'>public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function __invoke(array $record)
{
$request = $this->requestStack->getCurrentRequest();
if ($request) {
$record['extra']['path'] = $request->getPathInfo();
$record['extra']['method'] = $request->getMethod();
}
return $record;
}}
然后在 services.yml 中注册:
services:
monolog.processor.path:
class: YourBundle\Monolog\PathProcessor
arguments: ['@request_stack']
tags:
- { name: monolog.processor, handler: main }这样所有经 main handler 写出的日志都会带 extra.path 字段,后续用 awk 或 Python 脚本就能按路径分组统计频次。
统计请求路径频次的最小可行命令
别依赖 PHP 脚本做日志分析——慢且易内存溢出。直接用 shell 命令从 dev.log 提取路径字段并计数:
先确认日志格式含 "path":"\/api\/v1\/users" 这类 JSON 片段(取决于你的 Processor 输出格式),再执行:
grep -o '"path":"[^"]*"' var/log/dev.log | sort | uniq -c | sort -nr | head -20
这条命令能快速列出 Top 20 访问路径。如果日志是单行纯文本(如 [2026-08-13 12:05:22] app.INFO: request {"path":"/admin/login"} []),则改用:
grep 'request.*"path":"' var/log/dev.log | sed -n 's/.*"path":"\([^"]*\)".*/\1/p' | sort | uniq -c | sort -nr
注意:路径中含特殊字符(如 ?、&)会导致 grep 截断,此时应先用 jq 解析完整 JSON 行(前提是日志已格式化为每行一个 JSON 对象)。
真正容易被忽略的是时间窗口控制——dev.log 不自动轮转,跑几天就上百 MB,grep 会越来越慢。上线前务必配好 RotatingFileHandler 或用 logrotate 外部管理。


















