引言
「可观测性」(Observability)是系统运行的 X 光——不看源码也能推断内部状态。PHP 的可观测生态围绕三支柱:Metrics(指标数数量)、Logs(日志文本)、Traces(链路追踪)。从 var_dump() 调试到生产级 Monolog + OpenTelemetry,从单机日志到分布式链路,本文走完整条可观测链路:日志架构、结构化输出、链路追踪、APM 集成、指标采集与告警。让 PHP 应用不只是「能跑」,而是「跑在哪、跑多快、哪里慢、为什么错」都一目了然。
前置:/php-laravel-authentication/(认证授权)、/php-database-migrations-architecture/(数据库治理)。
目录
- 1. 可观测性三支柱:MetricsLogsTraces
- 2. 日志系统:Monolog 架构与处理器
- 3. 结构化日志:JSONECS 与上下文
- 4. 分布式链路追踪:OpenTelemetry
- 5. APM 工具:TelescopeNew RelicDatadog
- 6. 自定义指标与性能埋点
- 7. 日志聚合:ELKLoki 与低成本方案
- 8. 健康检查与告警体系
- 9. PHP 特有的可观测陷阱
- 10. 速查表与一句话记忆
- 延伸阅读
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>5s | Slack+邮件 | < 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 | 请求全链路 |
| Monolog | Logger+Handler+Formatter+Processor |
| 结构化日志 | JSON+ECS 字段 |
| OpenTelemetry | Trace+Span+SpanContext |
| Telescope | Laravel 开发调试 |
| Prometheus | Counter/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-laravel-authentication/ — 认证授权安全基础
- /php-performance-tuning/ — PHP 性能优化
- DevOps 专题 — CI/CD 与监控告警
- 运维监控专题 — 系统级可观测
- OpenTelemetry 官方文档
- Laravel Telescope 指南
- Prometheus 最佳实践
- Grafana Loki 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。