PHP项目告警汇总如何区分不同服务异常

wen PHP项目 29

本文目录导读:

PHP项目告警汇总如何区分不同服务异常

  1. 核心:服务标识(Service Identity)
  2. 异常捕获与标记(在入口或中间件)
  3. 告警数据路由与存储(区分的关键)
  4. 告警发送与通知(按服务配置不同通道)
  5. 聚合与去重:按服务+异常类型分组
  6. 高级:分布式追踪(区分调用链中的服务)
  7. 最小可行方案清单

在 PHP 项目中,要区分 不同服务 的异常告警,核心思路是:为每个服务/模块打上唯一的标识(标签/元数据),并在告警系统中基于这些标签进行路由、分组和展示。

以下是具体的实现方案,分为几个关键层面:

核心:服务标识(Service Identity)

你的PHP应用必须知道“我是谁”,这需要在应用启动时初始化一个全局上下文。

方案 A:环境变量(推荐用于K8s/Docker)

// config/app.php 或 bootstrap.php
define('SERVICE_NAME', getenv('SERVICE_NAME') ?: 'unknown-service');
define('SERVICE_ENV', getenv('APP_ENV') ?: 'production'); // dev, staging, prod
define('SERVICE_INSTANCE', gethostname()); // 或容器ID

方案 B:配置文件(适用于传统服务器集群)

// config/services.php
return [
    'user_service' => [
        'name' => 'user-service',
        'hosts' => ['192.168.1.10', '192.168.1.11'],
    ],
    'order_service' => [
        'name' => 'order-service',
        'hosts' => ['192.168.2.20'],
    ],
];
// 启动时根据IP或主机名匹配
$host = gethostname();
// 遍历配置匹配

异常捕获与标记(在入口或中间件)

在 PHP 的异常处理中心(如 Laravel 的 App\Exceptions\Handler、框架中间件或全局 set_exception_handler 中),将 服务标识 附加到告警数据包中。

// Laravel 异常处理 Handler
public function report(Throwable $e)
{
    // 构造告警上下文
    $alertData = [
        'title'       => get_class($e) . ': ' . $e->getMessage(),
        'service'     => SERVICE_NAME,          // 关键字段:服务名
        'environment' => SERVICE_ENV,
        'instance'    => SERVICE_INSTANCE,
        'exception'   => [
            'file'     => $e->getFile(),
            'line'     => $e->getLine(),
            'trace'    => $e->getTraceAsString(),
        ],
        'request'     => [
            'url'    => request()->fullUrl(),
            'method' => request()->method(),
            'ip'     => request()->ip(),
        ],
        'timestamp'   => now()->toIso8601String(),
    ];
    // 发送到告警系统(下文详述)
    AlertService::send($alertData);
}

告警数据路由与存储(区分的关键)

告警不能只存成一条字符串,必须结构化存储,你需要一个能够 按字段查询 的存储后端,而不是简单写入日志文件。

方案 A:使用专业的 APM/错误跟踪平台(推荐)

这些平台天然支持按服务名、环境、主机等标签过滤。

  • Sentry:设置 releasetags

    \Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
        $scope->setTag('service', SERVICE_NAME);
        $scope->setTag('env', SERVICE_ENV);
        $scope->setUser(['id' => auth()->id() ?? 'anonymous']);
    });

    结果:在 Sentry Dashboard 中可以直接选择 “service: user-service” 查看其所有异常。

  • Laravel Telescope / Flare:通过上下文数据区分。

方案 B:自建告警系统(使用 Elasticsearch 或类似)。

将上述 $alertData 存储到 Elasticsearch(ES),索引按天分(如 alerts-2023-10-01)。

查询示例:

// 查询 user-service 的错误
GET /alerts-*/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "service": "user-service" } },
        { "term": { "environment": "production" } }
      ]
    }
  }
}

方案 C:使用消息队列 + 日志中心

发送到 Kafka/RabbitMQ,然后由 Logstash/Fluentd 消费写入 ES 或 Grafana Loki。

告警发送与通知(按服务配置不同通道)

在告警规则层面,你可以根据 service 字段做路由:

  • 规则 1service = "payment-service" AND level = "critical" → 发送到 PagerDuty + 电话通知
  • 规则 2service = "user-service" AND level = "warning" → 发送到 企业微信/钉钉群 2
  • 规则 3service = "cron-job-service" AND statusCode > 500 → 发送到 飞书机器人

实现工具:

  • Prometheus + Alertmanager:虽然 Prometheus 通常拉取指标,但你也可以通过 pushgateway 发送告警事件,并利用 Alertmanager 的 group_by: ['service'] 和路由规则。
  • Grafana OnCall:支持丰富的标签路由。
  • 阿里云/腾讯云监控:自定义监控项,上报时带上维度(service, host)。

聚合与去重:按服务+异常类型分组

在展示汇总时(如每日邮件或群日报),可以按 service + exception_class 聚合。

SQL/ES 聚合伪代码:

SELECT 
    service, 
    exception_class, 
    COUNT(*) as count, 
    MAX(created_at) as last_occurrence
FROM alerts 
WHERE created_at > NOW() - INTERVAL 24 HOUR
GROUP BY service, exception_class;

结果输出到群消息:

【用户服务 - UserService】
- 500 Error (10次) | 最后触发: 10:23
- PDOException (3次) | 最后触发: 09:15
【订单服务 - OrderService】
- LogicException (7次) | 最后触发: 11:00
- RuntimeException (2次) | 最后触发: 08:30

高级:分布式追踪(区分调用链中的服务)

如果你的服务是微服务架构(A调用B,B调用C),异常可能发生在B,但影响的是A,此时建议集成 OpenTelemetry

$span = OpenTelemetry::getCurrentSpan();
$alertData['trace_id'] = $span->getContext()->getTraceId();
$alertData['span_id'] = $span->getContext()->getSpanId();
$alertData['parent_service'] = $span->getAttributes()->get('service.name');

这样你不仅知道异常发生在哪个服务,还能知道是哪个上游调用导致的。

最小可行方案清单

如果你不想引入复杂系统,可以按照以下步骤快速实现:

  1. 定义常量:每个服务器/容器设置 SERVICE_NAME 环境变量。
  2. 改写异常处理:在捕捉异常时,构造一个包含 serviceenvtrace 的 JSON 对象。
  3. 写入统一日志文件:例如每天一个 alert-{date}.log,每行一个 JSON。
  4. 使用 tail + jq + grep 查看
    // 只看 user-service 的5xx错误
    tail -f /var/log/alert-$(date +%F).log | jq -c 'select(.service == "user-service" and .http_code >= 500)'
  5. 发送到群聊:写一个简单的脚本,读取日志文件,按 service 分组,每10分钟发一次汇总消息到企业微信。

核心思想就是:给每一行告警打上 SERVICE 标签,所有的过滤、分组、通知都基于这个标签来做。

抱歉,评论功能暂时关闭!