生产环境必须禁用app_debug=1,应通过symfony/error-handler捕获错误并上报至rollbar等服务,启用silencederrorcontext处理@静默错误,配合monolog处理器、ci检测和缓存预热实现全链路错误监控。

生产环境必须禁用APP_DEBUG=1,但错误仍需可见
开启APP_DEBUG=1在生产环境会暴露敏感路径、变量和堆栈,属于严重安全风险。真正可行的方案是保留APP_ENV=prod,同时让错误被捕获、结构化、上报,而不是静默丢弃或写进混杂日志里。
关键点在于:Symfony 4.4+ 已弃用 symfony/debug,应统一使用 symfony/error-handler 组件。它默认启用,但默认行为是仅记录到 Monolog,不主动上报。
- 确认
config/packages/prod/monolog.yaml中至少有一个handlers指向文件(如main)且级别为error或更低 - 避免在生产配置中意外启用
debug: true(该选项仅用于开发调试器,非错误追踪) -
error_handler默认会捕获E_ERROR、E_PARSE、E_COMPILE_ERROR等,但对@静默错误无效——需额外启用SilencedErrorContext
用SilencedErrorContext抓出被@压制的错误
PHP 中的 @file_get_contents() 这类写法会让错误彻底消失,连日志都不进。Symfony 的 SilencedErrorContext 是唯一能还原这类上下文的机制,但它**不会自动启用**,必须显式配置。
在 config/services.yaml 中添加:
services:
Symfony\Component\ErrorHandler\ErrorHandler:
arguments:
$silencedErrorReporting: true
这会让所有被 @ 压制的错误生成 SilencedErrorContext 实例,并作为上下文附加到后续异常中。配合自定义异常处理器,你就能看到「这个 @ 出现在 UserService.php 第 42 行」这样的信息。
- 该配置仅在
symfony/error-handler ^5.4或更高版本有效 - 若项目仍在用旧版
symfony/debug,请先升级,否则SilencedErrorContext不生效 - 注意性能:开启后每次
@操作都会触发上下文收集,高频调用处慎用
把错误实时推给 Rollbar / Sentry / Datadog
Monolog 写文件只是兜底,生产环境需要的是实时告警。推荐用 monolog 的 Handler 机制桥接第三方服务,而非重写整个错误处理器。
以 Rollbar 为例,安装 SDK 后,在 config/packages/prod/monolog.yaml 中配置:
monolog:
handlers:
rollbar:
type: service
id: App\Handler\RollbarHandler
level: error
然后实现 App\Handler\RollbarHandler,继承 Monolog\Handler\AbstractProcessingHandler,在 write() 中调用 Rollbar::log() 并传入 $record['context']['exception']。
- 不要在
ExceptionHandler中直接调用 Rollbar——那会绕过 Monolog 的 channel 和 level 控制 - 确保
ROLLBAR_ACCESS_TOKEN通过环境变量注入,绝不可硬编码 - 对敏感字段(如
$_POST、数据库连接串)做白名单过滤,monolog的processors可完成此任务
翻译错误、模板错误等特定类型要单独监控
普通 PHP 错误只是冰山一角。Symfony Translation 缺失键、Twig 模板语法错误、路由未匹配等,都可能返回 500 却不触发 ErrorHandler 的标准流程。
必须补上两层防护:
- CI 阶段强制运行
bin/console lint:translations translations/和bin/console lint:twig templates/,失败即阻断发布 - 运行时在
Kernel::boot()中预加载翻译目录并 catchInvalidResourceException,立即上报 - 对
TranslatorInterface::trans()的缺失键,可包装一层代理,在返回原始 key 时记录告警(如 key 包含.error.前缀则视为高危)
最易忽略的是缓存失效后的首次加载错误——它发生在 CLI 环境下,不会经过 Web 请求管道,必须在部署脚本中显式执行 cache:warmup 并捕获其输出。











