PHP 自定义指标暴露接口

wen PHP项目 2

本文目录导读:

PHP 自定义指标暴露接口

  1. Prometheus 格式(最常用)
  2. JSON 格式(自定义监控系统)
  3. 使用框架集成(以 Laravel 为例)
  4. 完整的企业级实现
  5. 安全防护
  6. 部署建议
  7. 关键要点

在 PHP 中实现自定义指标暴露接口,通常用于监控系统(如 Prometheus、Grafana 等),以下是几种常见的实现方案:

Prometheus 格式(最常用)

使用 promphp/prometheus_client_php

<?php
// metrics.php - Prometheus 指标暴露接口
require_once 'vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\InMemory;
use Prometheus\RenderTextFormat;
// 创建注册表
$registry = new CollectorRegistry(new InMemory());
// 定义自定义指标
$counter = $registry->registerCounter(
    'app_requests_total',
    'Total number of requests',
    ['method', 'endpoint']
);
$gauge = $registry->registerGauge(
    'app_memory_usage_bytes',
    'Current memory usage',
    ['service']
);
$histogram = $registry->registerHistogram(
    'app_request_duration_seconds',
    'Request duration in seconds',
    ['method'],
    [0.1, 0.5, 1, 2, 5]
);
// 记录指标数据
$counter->incBy(10, ['GET', '/api/users']);
$counter->incBy(5, ['POST', '/api/users']);
$gauge->set(memory_get_usage(true), ['api-service']);
$gauge->set(memory_get_usage(true), ['web-service']);
// 模拟请求耗时数据
$histogram->observe(0.3, ['GET']);
$histogram->observe(1.2, ['POST']);
// 渲染并输出指标
$renderer = new RenderTextFormat();
$result = $renderer->render($registry->getMetricFamilySamples());
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $result;

轻量级实现(不依赖第三方库)

<?php
// custom_metrics.php
header('Content-Type: text/plain; version=0.0.4');
// 自定义指标数据存储(实际应用中可能从数据库或缓存读取)
$metrics = [
    'app_requests_total' => [
        'help' => 'Total number of HTTP requests',
        'type' => 'counter',
        'values' => [
            ['labels' => ['method="GET"', 'endpoint="/home"'], 'value' => 12345],
            ['labels' => ['method="POST"', 'endpoint="/api/data"'], 'value' => 6789],
        ]
    ],
    'app_http_request_duration_seconds' => [
        'help' => 'HTTP request duration in seconds',
        'type' => 'histogram',
        'values' => [
            ['labels' => ['le="0.1"'], 'value' => 100],
            ['labels' => ['le="0.5"'], 'value' => 300],
            ['labels' => ['le="1"'], 'value' => 400],
            ['labels' => ['le="2"'], 'value' => 450],
            ['labels' => ['le="+Inf"'], 'value' => 500],
        ]
    ]
];
// 输出 Prometheus 格式
foreach ($metrics as $name => $metric) {
    echo "# HELP {$name} {$metric['help']}\n";
    echo "# TYPE {$name} {$metric['type']}\n";
    foreach ($metric['values'] as $data) {
        $labels = implode(',', $data['labels']);
        echo "{$name}{{$labels}} {$data['value']}\n";
    }
    echo "\n";
}

JSON 格式(自定义监控系统)

<?php
// json_metrics.php
header('Content-Type: application/json');
class MetricsCollector {
    private $metrics = [];
    public function increment($name, $labels = [], $value = 1) {
        $key = $this->buildKey($name, $labels);
        if (!isset($this->metrics[$key])) {
            $this->metrics[$key] = [
                'name' => $name,
                'labels' => $labels,
                'value' => 0,
                'timestamp' => time()
            ];
        }
        $this->metrics[$key]['value'] += $value;
        $this->metrics[$key]['timestamp'] = time();
    }
    public function gauge($name, $value, $labels = []) {
        $key = $this->buildKey($name, $labels);
        $this->metrics[$key] = [
            'name' => $name,
            'labels' => $labels,
            'value' => $value,
            'timestamp' => time()
        ];
    }
    public function histogram($name, $value, $labels = []) {
        $key = $this->buildKey($name, $labels);
        if (!isset($this->metrics[$key])) {
            $this->metrics[$key] = [
                'name' => $name,
                'labels' => $labels,
                'count' => 0,
                'sum' => 0,
                'buckets' => [0.1, 0.5, 1, 2, 5],
                'bucket_counts' => array_fill(0, 6, 0)
            ];
        }
        $this->metrics[$key]['count']++;
        $this->metrics[$key]['sum'] += $value;
        foreach ($this->metrics[$key]['buckets'] as $i => $bucket) {
            if ($value <= $bucket) {
                $this->metrics[$key]['bucket_counts'][$i]++;
            }
        }
    }
    private function buildKey($name, $labels) {
        return $name . '|' . json_encode($labels);
    }
    public function render() {
        return json_encode([
            'timestamp' => time(),
            'service' => gethostname(),
            'metrics' => $this->metrics
        ], JSON_PRETTY_PRINT);
    }
}
// 使用示例
$collector = new MetricsCollector();
$collector->increment('http_requests', ['endpoint' => '/home', 'method' => 'GET']);
$collector->increment('http_requests', ['endpoint' => '/api/data', 'method' => 'POST'], 5);
$collector->gauge('memory_usage', memory_get_usage(true), ['service' => 'api']);
$collector->histogram('request_duration', 0.3, ['endpoint' => '/home']);
echo $collector->render();

使用框架集成(以 Laravel 为例)

<?php
// routes/api.php
use App\Services\MetricsService;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;
Route::get('/metrics', function () {
    // 使用 Redis 存储(适合多实例)
    $registry = new CollectorRegistry(new Redis(['host' => 'redis', 'port' => 6379]));
    // 或者使用 APCu 存储
    // $registry = new CollectorRegistry(new APCng());
    $renderer = new RenderTextFormat();
    $result = $renderer->render($registry->getMetricFamilySamples());
    return response($result)
        ->header('Content-Type', RenderTextFormat::MIME_TYPE);
});
// app/Services/MetricsService.php
class MetricsService {
    private $registry;
    public function __construct() {
        $this->registry = new CollectorRegistry(new InMemory());
    }
    public function incrementCounter($name, $labels = []) {
        $counter = $this->registry->getOrRegisterCounter(
            'app',
            $name,
            'Application counter: ' . $name,
            array_keys($labels)
        );
        $counter->incBy(1, array_values($labels));
    }
}

完整的企业级实现

<?php
// enterprise_metrics.php
require_once 'vendor/autoload.php';
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;
use Predis\Client;
class EnterpriseMetrics {
    private $registry;
    private $appName;
    public function __construct($redisConfig, $appName = 'my_app') {
        $this->appName = $appName;
        // 配置 Redis 存储
        $redis = new Client($redisConfig);
        $this->registry = new CollectorRegistry(new Redis($redis));
        // 注册基本指标
        $this->registerBasicMetrics();
    }
    private function registerBasicMetrics() {
        // 系统指标
        $this->registry->registerGauge(
            'system_cpu_usage_percent',
            'CPU usage percentage',
            ['host', 'app']
        );
        $this->registry->registerGauge(
            'system_memory_usage_bytes',
            'Memory usage in bytes',
            ['host', 'app']
        );
        // 业务指标
        $this->registry->registerCounter(
            'business_requests_total',
            'Total business requests',
            ['app', 'endpoint', 'status']
        );
        $this->registry->registerHistogram(
            'business_request_duration_seconds',
            'Business request duration',
            ['endpoint'],
            [0.1, 0.5, 1, 2, 5, 10]
        );
    }
    public function updateSystemMetrics() {
        $host = gethostname();
        // CPU 使用率
        $cpu = sys_getloadavg()[0] / 100;
        $this->registry->getGauge('system_cpu_usage_percent')
            ->set($cpu, [$host, $this->appName]);
        // 内存使用
        $mem = memory_get_usage(true);
        $this->registry->getGauge('system_memory_usage_bytes')
            ->set($mem, [$host, $this->appName]);
    }
    public function recordRequest($endpoint, $status, $duration) {
        $counter = $this->registry->getCounter('business_requests_total');
        $counter->incBy(1, [$this->appName, $endpoint, $status]);
        $histogram = $this->registry->getHistogram('business_request_duration_seconds');
        $histogram->observe($duration, [$endpoint]);
    }
    public function render() {
        $renderer = new RenderTextFormat();
        return $renderer->render($this->registry->getMetricFamilySamples());
    }
}
// 在入口脚本中使用
$metrics = new EnterpriseMetrics([
    'scheme' => 'tcp',
    'host' => '127.0.0.1',
    'port' => 6379,
]);
// 更新系统指标
$metrics->updateSystemMetrics();
// 输出指标
header('Content-Type: ' . RenderTextFormat::MIME_TYPE);
echo $metrics->render();

安全防护

<?php
// secure_metrics.php
session_start();
class MetricsSecurity {
    private $allowedIPs = ['127.0.0.1', '192.168.1.0/24'];
    private $apiKeys = ['your-secure-api-key'];
    public function validateAccess() {
        // IP 白名单检查
        if (!$this->checkIP()) {
            http_response_code(403);
            die('Forbidden');
        }
        // API Key 检查
        $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
        if (!in_array($apiKey, $this->apiKeys)) {
            http_response_code(401);
            die('Unauthorized');
        }
        // 速率限制
        $this->rateLimit();
        return true;
    }
    private function checkIP() {
        $ip = $_SERVER['REMOTE_ADDR'];
        foreach ($this->allowedIPs as $allowedIP) {
            if (strpos($allowedIP, '/') !== false) {
                // CIDR 检查
                if ($this->ipInCIDR($ip, $allowedIP)) {
                    return true;
                }
            } elseif ($ip === $allowedIP) {
                return true;
            }
        }
        return false;
    }
    private function ipInCIDR($ip, $cidr) {
        list($subnet, $bits) = explode('/', $cidr);
        $ip_bin = ip2long($ip);
        $subnet_bin = ip2long($subnet);
        $mask = -1 << (32 - (int)$bits);
        return (($ip_bin & $mask) === ($subnet_bin & $mask));
    }
    private function rateLimit() {
        $key = 'metrics_limit:' . $_SERVER['REMOTE_ADDR'];
        $current = apcu_fetch($key) ?: 0;
        if ($current >= 60) { // 每分钟最多60次
            http_response_code(429);
            die('Too Many Requests');
        }
        apcu_store($key, $current + 1, 60);
    }
}
// 使用安全验证
$security = new MetricsSecurity();
$security->validateAccess();
// 然后输出指标...

部署建议

# Docker Compose 示例
version: '3'
services:
  php-app:
    image: php:8.2-apache
    volumes:
      - ./:/var/www/html
    ports:
      - "8080:80"
    environment:
      - METRICS_ENABLED=true
      - METRICS_API_KEY=your-secure-key
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
scrape_configs:
  - job_name: 'php-app'
    scrape_interval: 15s
    metrics_path: '/metrics'
    basic_auth:
      username: 'metrics'
      password: 'your-secure-password'
    static_configs:
      - targets: ['php-app:80']

关键要点

  1. 性能优化:使用 Redis/APCu 存储指标数据,避免阻塞主业务
  2. 安全防护:添加 IP 白名单、API Key 验证、速率限制
  3. 容器化改造:支持 Docker 部署,便于监控
  4. 多维度指标:包含系统指标(CPU、内存)和业务指标(请求数、延迟)
  5. 标准化格式:使用 Prometheus 标准格式,便于与主流监控系统集成
  6. 错误处理:确保指标暴露接口不会影响主业务

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