display_errors 与 log_errors 同时开启的风险

解读

在国内一线互联网公司的 PHP 面试中,这道题常被用来区分“能跑通代码”与“能扛住线上流量”的候选人。
面试官真正想听的是:

  1. 你是否理解两条指令对“输出面”与“落地面”的各自职责;
  2. 你是否清楚在“中国特有的合规与攻防环境”下,同时开启会带来哪些具体危害;
  3. 你是否具备“可灰度、可回滚、可审计”的线上治理思路,而不是简单地回答“关掉就完了”。

一句话:不是不能开,而是必须“开之有方、关之有时”。

知识点

  1. display_errors = On
    把错误信息直接回显到响应体,浏览器、小程序、App 都能直接看到。
  2. log_errors = On
    把错误信息写入指定日志文件或 syslog,用于后续排查。
  3. 国内合规要求
    《网络安全法》《个人信息保护法》要求企业“采取技术措施防止信息泄露”;回显堆栈极易带出绝对路径、数据库字段、内网 IP,属于“敏感信息泄露”类高危。
  4. 攻防场景
    国内黑产扫描器 7×24 小时自动嗅探 “Warning”“Fatal error” 关键字,一旦命中,立即触发目录爆破、SQL 注入二次探测;护网行动期间,这类漏洞会被 HW 裁判组直接判“高危”。
  5. 性能与可用性
    大促场景(双 11、618)下,display_errors=On 会让 PHP 把错误文本随 HTML 一起输出,增加响应体 20%~200%,CDN 回源流量暴涨;若错误频率高,日志写满 Docker Overlay2 层,可能触发 K8s 节点磁盘压力,Pod 被驱逐。
  6. 配置优先级
    php.ini < Apache vhost < .user.ini < ini_set();很多 PaaS 平台(阿里云函数计算、腾讯云 SCF)禁止运行时修改,必须提前在打包镜像时治理。
  7. 线上治理三板斧
    灰度:通过 Apollo、Nacos 配置中心按流量比例下发;
    隔离:错误日志落盘到独立 PVC,与业务日志分盘;
    审计:Filebeat + ELK 实时告警,匹配 “Stack trace”“mysqli::” 等关键字,5 分钟内工单升级。

答案

“display_errors 与 log_errors 同时开启”本身不是语法错误,但在国内线上环境属于高危配置,核心风险有三点:

  1. 敏感信息泄露:堆栈会暴露绝对路径、SQL 语句、内部类名,违反《个人信息保护法》第五十一条,可被监管罚款最高 5000 万元。
  2. 扩大攻击面:黑产利用回显信息快速定位框架版本(如 ThinkPHP 5.0.23 RCE),结合 0day 实现一键 GetShell;护网行动期间,该配置直接记高危并通报。
  3. 稳定性与成本:错误信息随 HTML 输出,页面体积膨胀,CDN 流量费增加;高并发时日志刷盘占用 IO,可能触发 K8s 节点磁盘压力,导致 Pod 频繁重启。

因此,线上环境必须保证
php_flag[display_errors] = Off php_flag[log_errors] = On php_value[error_log] = /var/log/php/app-error.log
并通过配置中心灰度下发,任何变更需要 MR + Code Review + 回归压测,确保“错误只进日志、不进响应”。

拓展思考

  1. 多语言混合场景
    如果业务是 PHP-FPM + Go 微服务并存,Go 侧使用 zap 日志,如何统一 TraceId?
    思路:在 Nginx 统一生成 requestid,通过fastcgiparam注入PHPrequest_id,通过 fastcgi_param 注入 PHP 的 _SERVER,同时在 Go 侧从 HTTP 头读取,日志落盘时把 TraceId 作为索引字段,方便全链路检索。
  2. 错误日志的二次利用
    把 PHP error log 实时打入 Flink,按“模块+错误码”聚合,5 分钟内错误率突增即可触发自动回滚;
    再结合 Prometheus 的 phpfpm_slow_requests 指标,可实现“慢请求→错误日志→版本回滚”的闭环自愈。
  3. 合规审计
    日志中若出现用户手机号、身份证,需立即脱敏;可在 Filebeat 阶段用 script processor 做正则替换,或直接在 php.ini 设置 error_log_format = "[%datetime%] %level%: %message%",禁止输出上下文参数,降低合规风险。