PHP 可观测性实战:日志、指标、链路追踪与 APM

PHP 可观测性实战:日志系统选型(Monolog 处理器/格式化/上下文)、结构化日志(JSON/ECS)、分布式链路追踪(OpenTelemetry/OpenTracing)、APM 工具集成(Laravel Telescope/New Relic/Datadog/Blackfire)、自定义指标与性能埋点、日志聚合(ELK/Loki)、健康检查与告警、可观测性三支柱(Metrics/Logs/Traces)的工程落地。

引言

「可观测性」(Observability)是系统运行的 X 光——不看源码也能推断内部状态。PHP 的可观测生态围绕三支柱:Metrics(指标数数量)、Logs(日志文本)、Traces(链路追踪)。从 var_dump() 调试到生产级 Monolog + OpenTelemetry,从单机日志到分布式链路,本文走完整条可观测链路:日志架构、结构化输出、链路追踪、APM 集成、指标采集与告警。让 PHP 应用不只是「能跑」,而是「跑在哪、跑多快、哪里慢、为什么错」都一目了然。

前置:/php-laravel-authentication/(认证授权)、/php-database-migrations-architecture/(数据库治理)。


目录


1. 可观测性三支柱:Metrics、Logs、Traces

1.1 定义对比

支柱类型用途粒度
Metrics数值“当前的 QPS 是 1200”聚合时间序列
Logs文本/结构化“用户 42 在 14:03 登录失败”离散事件
Traces调用链“请求从网关→Auth→DB 总耗时 230ms,DB 占 180ms”请求级详细

1.2 三者的关系

一条慢请求 → Trace 揭示"DB 查询慢" → 关联 Log 看具体 SQL → Metrics 显示"DB 连接池饱和"
三支柱互补:Trace 带 Span ID / Trace ID → Log 带相同 ID → Metrics 按标签聚合 → 三条线索串成完整故障图

记忆:三支柱——Metrics(数值聚合,选样看趋势)、Logs(离散事件,查细节)、Traces(请求全链路,定位瓶颈);一条故障用 Trace 找到慢点→Log 看具体→Metrics 确认趋势,三者互补。


2. 日志系统:Monolog 架构与处理器

2.1 Monolog 核心组件

Logger:记录消息的入口
Handler:决定怎么处理/发往哪(文件/数据库/远程/邮件)
Formatter:格式化输出(LineFormatter/JsonFormatter/HtmlFormatter)
Processor:添加额外上下文(WebProcessor/IntrospectionProcessor)

2.2 处理器速查

Handler用例
StreamHandler写本地文件(最常用)
RotatingFileHandler按日期/大小轮转
SyslogHandler写 syslog
MongoDBHandler结构化入 Mongo
SlackHandler错误发 Slack
ElasticsearchHandler直接入 ES
RedisHandler入 Redis List

2.3 配置示例

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\JsonFormatter;

$logger = new Logger('app');
$logger->pushHandler(
    new RotatingFileHandler(__DIR__.'/logs/app.log', 7, Logger::INFO)
);

// 错误级别单独文件
$errorHandler = new StreamHandler(__DIR__.'/logs/error.log', Logger::WARNING);
$logger->pushHandler($errorHandler);

// JSON 格式化
$logger->getHandlers()[0]->setFormatter(new JsonFormatter());

记忆:Monolog = Logger(入口)+ Handler(处理/发送)+ Formatter(格式)+ Processor(上下文);Handler 按目的地选——文件轮转用 RotatingFile、结构入 ES 用 ElasticsearchHandler、告警用 SlackHandler。


3. 结构化日志:JSON、ECS 与上下文

3.1 为什么结构化

非结构化:"User admin logged in from 192.168.1.1 at 2024-01-15 10:00"
结构化:{"@timestamp":"2024-01-15T10:00:00Z","event.action":"login","user.name":"admin","source.ip":"192.168.1.1"}
# 结构化 = 可查询、可聚合、可索引;搜索引擎和 SIEM 的基石

3.2 ECS(Elastic Common Schema)

# Elastic 定义的标准字段命名
@timestamp      时间
message         原始消息
event.action    事件动作(login/logout/create)
user.name       用户名
source.ip       来源 IP
http.request.method   HTTP 方法
service.name    服务名

3.3 Laravel 中的结构化日志

// config/logging.php 自定义 channel
'elastic' => [
    'driver' => 'custom',
    'via' => App\Logging\ElasticLoggerFactory::class,
    'tap' => [App\Logging\AddEcsMetadata::class],
],

// Processor 添加上下文
$logger->pushProcessor(function ($record) {
    $record['extra']['request_id'] = request()->header('X-Request-ID');
    $record['extra']['user_id'] = auth()->id();
    return $record;
});

记忆:结构化日志=JSON 格式+标准字段(ECS)=可搜索可聚合;Laravel 自定义 channel + Processor 添加上下文(request_id/user_id/trace_id);message 是原始消息、@timestamp 是时间、event.action 是动作。


4. 分布式链路追踪:OpenTelemetry

4.1 核心概念

Trace:一次请求的完整链路(如 API 调用)
Span:链路中的一个操作(如 DB 查询、HTTP 调用)
SpanContext:Trace ID + Span ID + Flags(传递上下文)
Baggage:跨 Span 传递的键值对(用户 ID、租户 ID)

4.2 PHP 集成 OpenTelemetry

composer require open-telemetry/sdk
composer require open-telemetry/exporter-otlp

# 初始化 Tracer
$tracerProvider = (new TracerProviderBuilder())
    ->addSpanProcessor(new BatchSpanProcessor(
        new OTLPExporter(new Client(), StreamFactory::create(), new HttpTransport())
    ))
    ->build();

# 创建 Trace
$span = $tracerProvider->getTracer('app')->spanBuilder('process-order')->startSpan();
$span->setAttribute('order.id', $orderId);

# 嵌套 Span
$childSpan = $tracerProvider->getTracer('app')
    ->spanBuilder('db-query')
    ->setParent($span->storeInContext($context))
    ->startSpan();
$childSpan->end();
$span->end();

4.3 自动埋点框架

# Laravel 用 open-telemetry/opentelemetry-auto-laravel
# 自动追踪 Controller、DB 查询、Queue Job、Cache 操作
# 配置 OTEL_EXPORTER_OTLP_ENDPOINT → Jaeger/Tempo/Datadog Agent

记忆:OpenTelemetry = Trace(全链路)+ Span(单操作)+ SpanContext(Trace/Span ID 传递)+ Baggage(跨 Span 键值);PHP 用 SDK 手动埋点或 Laravel auto-instrumentation 自动追踪;导出到 Jaeger/Tempo/Datadog。


5. APM 工具:Telescope、New Relic、Datadog

5.1 Laravel Telescope(开发调试)

composer require laravel/telescope --dev
php artisan telescope:install

# 自动记录:请求、异常、日志、数据库查询、队列、邮件、通知、缓存、调度
# 生产环境:telescope:prune 定期清理,或 filter 只记慢查询/异常
# 不适合:高流量生产环境的实时APM

5.2 New Relic(成熟商业 APM)

# 安装 PHP Agent → 修改 php.ini
newrelic.appname = "MyApp"
newrelic.license = "..."

# 自动追踪:Web Transctions、DB、外部调用、错误
# Dashboard:Apdex、吞吐量、错误率、慢事务追踪
# 成本:按主机数计费,小团队偏贵

5.3 Datadog(全栈可观测)

# 安装 dd-trace-php
DD_AGENT_HOST=localhost ddtrace-php

# 功能:APM + 基础设施监控 + 日志 + 安全检测
# 优势:一键关联「慢请求对应的日志和主机指标」
# 成本:按主机 + 按量计费

5.4 Blackfire(性能分析)

# 专用 PHP 性能分析工具
# CPU 时间、内存消耗、I/O 时间、函数调用图
# 集成:CLI、PHPUnit、Symfony/Laravel Profile
# 定位:代码级瓶颈(不是系统级 APM)

记忆:APM 工具各司其职——Telescope 本地调试全记录、New Relic/Datadog 生产全栈监控、Blackfire 代码级性能分析;选型平衡「功能深度」与「成本」。


6. 自定义指标与性能埋点

6.1 Prometheus 风格指标

# Counter:只增(请求数、错误数)
# Gauge:可增可减(内存使用量、队列长度)
# Histogram:分布(响应时间分桶)
# Summary:分位数

6.2 PHP 指标采集

// 用 Jimdo/prometheus_client_php
$registry = new CollectorRegistry(new RedisAdapter());

$counter = $registry->getOrRegisterCounter('app', 'requests_total', 'Total requests', ['method', 'endpoint']);
$counter->inc(['GET', '/api/users']);

$histogram = $registry->getOrRegisterHistogram('app', 'response_time', 'Response time', ['endpoint'], [0.01, 0.05, 0.1, 0.5, 1, 2, 5]);
$histogram->observe($duration, ['/api/users']);

// 暴露端点:/metrics → Prometheus 抓取

6.3 埋点最佳实践

# 入口层:请求数、错误率、响应时间 P99
# 业务层:订单创建数、支付成功率、用户注册数
# 资源层:DB 连接数、Redis 命令/秒、队列积压长度
# 标签(Labels): endpoint/method/status/service → 高基数标签会爆炸存储

记忆 指标三类型——Counter(只增计数)、Gauge(可增减状态)、Histogram(时间分布);Prometheus 拉取 /metrics 端点;埋点三层——入口吞吐量、业务KPI、资源饱和度;标签高基数会炸存储。


7. 日志聚合:ELK、Loki 与低成本方案

7.1 ELK Stack

Logstash/Filebeat → 收集日志
Elasticsearch → 索引存储
Kibana → 可视化查询

# Filebeat 轻量采集器 → Logstash 解析转换 → ES 索引 → Kibana 看板
# 成本:ES 内存要求不低,日志量大时存储成本高

7.2 Grafana Loki(云原生低成本)

# 只索引标签(不索引日志内容)→ 成本低、水平扩展
# Promtail 采集 → Loki 存储 → Grafana 查询
# 查询用 LogQL:{app="api", env="prod"} |= "error" | json | line_format "..."
# 与 Prometheus 共享标签体系 → 关联 Metrics + Logs

7.3 低成本方案

# Serverless:CloudWatch Logs(AWS)/Cloud Logging(GCP)/Azure Monitor
# SaaS:Logtail/Sematext(按量付费)
# 自建:rsyslog → 本地文件 → 定期压缩归档(冷数据)

记忆 日志聚合——ELK 功能全但成本高(ES 内存大户)、Loki 云原生低成本(只索标签不索内容、LogQL 查询);低成本用云厂商托管日志或 SaaS 按量;冷数据压缩归档。


8. 健康检查与告警体系

8.1 健康检查端点

// Laravel 健康检查
Route::get('/health', function () {
    $checks = [
        'db' => DB::connection()->getPdo() ? 'ok' : 'fail',
        'cache' => Cache::store()->get('health-check') !== false ? 'ok' : 'fail',
        'queue' => Queue::size() < 1000 ? 'ok' : 'warn',
    ];
    $status = in_array('fail', $checks) ? 503 : 200;
    return response()->json($checks, $status);
});

8.2 告警规则

# Prometheus Alertmanager 规则
- alert: HighErrorRate
  expr: rate(app_requests_total{status=~"5.."}[5m]) > 0.05
  for: 5m
  labels: severity: critical
  annotations: summary: "错误率 > 5% 持续 5 分钟"

- alert: SlowResponse
  expr: histogram_quantile(0.99, rate(app_response_time_bucket[5m])) > 2
  for: 3m
  labels: severity: warning

8.3 告警分级与响应

级别触发条件通知方式响应时效
P0 (Critical)服务不可用/错误率>10%电话+短信+Slack< 5 min
P1 (High)错误率>5%/P99>5sSlack+邮件< 15 min
P2 (Warning)P99>2s/队列积压Slack< 1h
P3 (Info)资源利用率>80%邮件日报< 1d

记忆 健康检查三层——DB/Cache/Queue;告警用 Prometheus 规则+Alertmanager 分级;三级告警分时效——Critical 电话、Warning Slack、Info 邮件;避免「告警疲劳」——规则要准、阈值要稳。


9. PHP 特有的可观测陷阱

9.1 共享 Nothing 架构

# PHP-FPM 每个请求独立进程 → 内存态不共享
# 问题:Trace Context 不能存在内存变量,必须传 HTTP Header 或存外部(Redis)
# 解决:OpenTelemetry 的 Baggage 通过 Header 传递

9.2 短生命周期

# PHP 进程请求结束即销毁 → 内存缓冲(Buffer)易丢失
# 日志:必须用写文件/网络的方式缓冲,不能攒内存
# Trace Batch:Monolog BufferHandler 在外部维护,或每次请求后 flush

9.3 同步阻塞模型

# 默认 PHP-FPM 同步 → 外部 API 调用阻塞进程
# Trace Spans 会显示「外部调用占 90% 时间」→ 这是架构问题不是代码问题
# 考虑:ReactPHP/Swoole 异步,或 Gateway 层聚合外部请求

9.4 OpCache 与代码热更新

# OpCache 缓存编译后字节码 → 代码更新后仍可能执行旧逻辑
# 问题:观测到「旧 Bug 还存在」,但文件已改
# 解决:部署后 reload PHP-FPM / 新进程自然淘汰旧进程

记忆 PHP 可观测陷阱——共享 Nothing(状态传 Header/Redis)、短生命周期(日志必须落盘)、同步阻塞(外部 API 慢是架构问题)、OpCache(代码更新需 reload)。


10. 速查表与一句话记忆

概念一句话
Metrics数值聚合时间序列
Logs离散事件文本
Traces请求全链路
MonologLogger+Handler+Formatter+Processor
结构化日志JSON+ECS 字段
OpenTelemetryTrace+Span+SpanContext
TelescopeLaravel 开发调试
PrometheusCounter/Gauge/Histogram,拉取 /metrics
ELK全功能日志聚合(成本高)
Loki云原生低成本(只索标签)
告警分级P0 电话/P1 Slack/P2 邮件

一句话记忆:可观测性三支柱——Metrics(数值趋势)、Logs(事件细节)、Traces(请求链路)互补定位故障;Monolog + JSON/ECS = 结构化日志;OpenTelemetry Trace→Span→SpanContext 跨服务传递上下文;APM 工具分别负责——Telescope 本地调试、NewRelic/Datadog 生产监控、Blackfire 代码分析;Prometheus Counter/Gauge/Histogram 三指标类型 + 拉取 /metrics;日志聚合 ELK 全但贵、Loki 云原生省;PHP 特有陷阱——共享 Nothing 状态传 Header、短生命周期日志落盘、同步阻塞看架构、OpCache 改完 reload——「能观测才能优化,指标是趋势、日志是证据、链路是地图」。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. Laravel 事件广播:实时通知、WebSocket 与队列驱动架构
  2. PHP 数据库迁移治理:架构设计、版本控制与多环境管理
  3. WordPress 开发实战:主题定制、插件架构与 Headless CMS