TrueAsync ServerTrueAsync Server: observabilidad

Observabilidad

(PHP 8.6+, true_async_server 0.10+)

El servidor puede informar estadísticas de solicitudes, escribir logs estructurados y emitir un registro de access log por cada solicitud. Todo lo de esta página está desactivado por defecto.

Estadísticas de solicitudes: getStats()

Activa las estadísticas con setStatsEnabled(true) y luego léelas con getStats():

php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;

$config = (new HttpServerConfig())
    ->addListener('0.0.0.0', 8080)
    ->setWorkers(4)
    ->setStatsEnabled(true);

$server = new HttpServer($config);
$server->addHttpHandler(fn ($req, $res) => $res->json(['ok' => true]));

$server->start();

getStats() devuelve los contadores por worker y un total combinado. Lanza una excepción si las estadísticas no estaban activadas.

php
[
    'enabled' => true,
    'workers' => [ 0 => [ /* los contadores de un worker */ ], 1 => [  ] ],
    'totals'  => [ /* sumados a través de los workers */ ],
]

totals contiene:

ContadorSignificado
total_requestssolicitudes completadas
responses_2xx_totalresponses_5xx_totalrespuestas por clase de status; las cuatro suman total_requests
conns_active_h1 / _h2 / _h3conexiones abiertas por protocolo

Los totales siguen creciendo a través de un reload(); los contadores de conexiones solo reflejan los workers vivos.

Prometheus y Grafana

El servidor no expone por sí mismo un endpoint /metrics: getStats() te da un array PHP normal, y tú lo conviertes en lo que espere tu stack de monitorización. Para Prometheus eso significa un pequeño manejador que da formato al array como el formato de exposición en texto:

php
use TrueAsync\HttpServer;
use TrueAsync\HttpServerConfig;

$config = (new HttpServerConfig())
    ->addListener('0.0.0.0', 8080)
    ->setWorkers(4)
    ->setStatsEnabled(true);

$server = new HttpServer($config);

$server->addHttpHandler(function ($req, $res) use ($server) {
    if ($req->getPath() === '/metrics') {
        $t = $server->getStats()['totals'];

        $body  = "# HELP http_requests_total Requests completed.\n";
        $body .= "# TYPE http_requests_total counter\n";
        $body .= "http_requests_total {$t['total_requests']}\n";

        $body .= "# HELP http_responses_total Responses by status class.\n";
        $body .= "# TYPE http_responses_total counter\n";
        foreach (['2xx', '3xx', '4xx', '5xx'] as $class) {
            $body .= "http_responses_total{class=\"{$class}\"} {$t["responses_{$class}_total"]}\n";
        }

        $body .= "# HELP http_connections_active Open connections by protocol.\n";
        $body .= "# TYPE http_connections_active gauge\n";
        foreach (['h1', 'h2', 'h3'] as $proto) {
            $body .= "http_connections_active{protocol=\"{$proto}\"} {$t["conns_active_{$proto}"]}\n";
        }

        $res->setHeader('Content-Type', 'text/plain; version=0.0.4')->end($body);
        return;
    }

    $res->json(['ok' => true]);
});

$server->start();

Apunta Prometheus al endpoint:

yaml
scrape_configs:
  - job_name: 'true-async-server'
    static_configs:
      - targets: ['your-server:8080']

Los contadores viven en una única tabla a nivel de proceso que cada worker actualiza y que getStats() lee, así que un solo scrape cubre todo el pool:

Flujo de métricas de los workers a Grafana

A partir de ahí Grafana grafica la tasa de solicitudes, las clases de status y las conexiones abiertas como cualquier otra fuente de Prometheus:

Dashboard de Grafana sobre las métricas del servidor

Logging: setLogSinks()

setLogSinks() envía cada registro de log a uno o varios destinos, cada uno con su propio formato y nivel mínimo:

php
use TrueAsync\LogSeverity;

$config->setLogSinks([
    ['type' => 'file', 'path' => '/var/log/app/access.log',
     'format' => 'json', 'category' => 'access', 'level' => LogSeverity::INFO],

    ['type' => 'stderr', 'format' => 'pretty', 'level' => LogSeverity::WARN],
]);

Hasta 8 destinos. Esto reemplaza el setLogSeverity() / setLogStream() de un solo stream.

A dónde va un registrotype es file, stdout, stderr, syslog o stream. Con un pool de workers, usa file (o stdout / stderr): un recurso stream abierto por el padre no puede compartirse con los hilos worker, así que solo se usa en el padre.

Cómo se veformat es plain, logfmt, json, pretty (una línea de consola con color) o template:

php
['type' => 'stdout', 'format' => 'template',
 'template' => '{ts:Y-m-d H:i:s.v} [{level}] {msg}{attrs}', 'level' => LogSeverity::INFO]

Marcadores: {ts} o {ts:PATTERN} (estilo date(): Y y m d H i s v), {level}, {msg}, {attrs}, {trace}, {span}. Cualquier otra cosa se imprime tal cual.

Un destino syslog habla RFC 5424 sobre TCP, UDP o un socket unix:

php
['type' => 'syslog', 'target' => 'udg:///dev/log',
 'facility' => 'local0', 'level' => LogSeverity::INFO]

Access log

Ajusta category para elegir qué recibe un destino: app (el valor por defecto) recibe el diagnóstico del servidor, access recibe un registro por cada solicitud completada y all recibe ambos. Así, un access log en JSON y una consola de diagnóstico legible pueden funcionar a la vez.

Los registros de acceso siguen las convenciones HTTP de OpenTelemetry. Un registro json:

json
{
    "Timestamp": "2026-07-15T07:03:37.740Z",
    "SeverityText": "INFO",
    "Body": "GET /x 200",
    "Attributes": {
        "http.request.method": "GET",
        "url.path": "/x",
        "http.response.status_code": 200,
        "network.protocol.version": "1.1",
        "http.response.body.size": 11,
        "http.server.request.duration": 9.266e-06,
        "client.address": "127.0.0.1",
        "client.port": 42336
    }
}

Se escribe un registro por cada solicitud sobre HTTP/1, HTTP/2 y HTTP/3, incluido bajo un pool de workers. Si la solicitud traía un trace context W3C, se incluye.

Contadores en runtime: getRuntimeStats()

getRuntimeStats() informa los propios pools de memoria del servidor y el tráfico de topics WebSocket entre workers, útil para atribuir el crecimiento de memoria a un subsistema. No hace falta activarlo. Entre las claves están el arena de conexiones (conn_arena_*), el pool de cuerpos de solicitud (body_pool*) y la entrega de topics (ws_topic_posted / ws_topic_skipped / ws_topic_dropped).

Contadores de HTTP/3: getHttp3Stats()

getHttp3Stats() devuelve una entrada por cada listener HTTP/3 con sus contadores QUIC (quic_packets_sent, quic_bytes_sent, recuentos de datagramas, etc.). Devuelve un array vacío en una build sin --enable-http3.

Véase también