monkeyscloud/monkeyslegion-telemetry

Comprehensive telemetry package with PSR-3 logging, metrics (Prometheus/StatsD), and distributed tracing for MonkeysLegion projects

Maintainers

Package info

github.com/MonkeysCloud/MonkeysLegion-Telemetry

Homepage

Issues

pkg:composer/monkeyscloud/monkeyslegion-telemetry

Statistics

Installs: 1 463

Dependents: 1

Suggesters: 2

Stars: 1

2.0.1 2026-04-28 02:16 UTC

This package is auto-updated.

Last update: 2026-04-28 02:17:04 UTC


README

A production-grade observability toolkit for PHP 8.4+ providing metrics, distributed tracing, and structured logging in a single, unified package. Ships with Prometheus, StatsD, W3C Trace Context, and PSR-3/PSR-15 integrations out of the box.

PHP Version License Tests

What's New in 2.0

Area v1 v2
PHP version 8.1+ 8.4+readonly, final, enums, named args
Metrics Basic Prometheus only MetricsInterface — Prometheus, StatsD, InMemory, Null drivers
Tracing Full W3C Trace Context — spans, events, exporters
Logging PSR-3 TelemetryLogger with automatic trace correlation
Middleware PSR-15 RequestMetricsMiddleware + RequestTracingMiddleware
Attributes #[Traced], #[Counted], #[Timed]
Factory Manual construction TelemetryFactory::create() — one-line bootstrapping
Facade Telemetry::counter(), Telemetry::trace(), Telemetry::log()

Features

📊 Metrics — Counter, Gauge, Histogram, Summary with Prometheus and StatsD backends

🔍 Distributed Tracing — W3C Trace Context compatible spans, events, and exception recording

📝 Structured Logging — PSR-3 logger with automatic trace/span ID injection

🌐 PSR-15 Middleware — Automatic HTTP request metrics and distributed tracing

🏷️ PHP 8 Attributes#[Traced], #[Counted], #[Timed] for declarative instrumentation

🏭 Factory Pattern — One-line bootstrap via TelemetryFactory::create()

Zero-cost Null DriversNullMetrics, NullTracer for safe development

Installation

composer require monkeyscloud/monkeyslegion-telemetry:^2.0

Optional Dependencies

# Prometheus exposition format
composer require promphp/prometheus_client_php

# PSR-15 middleware support
composer require psr/http-message psr/http-server-middleware

Quick Start

One-line Bootstrap

use MonkeysLegion\Telemetry\Telemetry;

Telemetry::init([
    'metrics' => ['driver' => 'memory', 'namespace' => 'myapp'],
    'tracing' => ['service_name' => 'myapp', 'exporter' => 'console'],
    'logging' => ['json' => true, 'level' => 'info'],
]);

// Metrics
Telemetry::counter('requests_total');
Telemetry::gauge('active_connections', 42);
Telemetry::histogram('response_time', 0.123);

// Tracing
$result = Telemetry::trace('fetch-user', fn () => $userRepo->find($id));

// Logging (with automatic trace correlation)
Telemetry::log()->info('User fetched', ['user_id' => $id]);

Metrics

All metric drivers implement MetricsInterface and support Counter, Gauge, Histogram, Summary, and Timer operations.

Drivers

Driver Class Use Case
null NullMetrics Development — zero overhead
memory InMemoryMetrics Testing, single-request
prometheus PrometheusMetrics Production — Prometheus scraping
statsd StatsDMetrics Production — StatsD / DogStatsD / Telegraf

Counter (Monotonically Increasing)

use MonkeysLegion\Telemetry\Telemetry;

// Increment by 1
Telemetry::counter('http_requests_total');

// Increment by N with dimensional labels
Telemetry::counter('http_requests_total', 1, [
    'method' => 'GET',
    'status' => '200',
]);

Gauge (Point-in-Time Value)

Telemetry::gauge('active_connections', 42);
Telemetry::gauge('queue_depth', 128, ['queue' => 'emails']);

Histogram (Distribution / Timing)

Telemetry::histogram('response_time_seconds', 0.157);

// Custom buckets
Telemetry::histogram('payload_size_bytes', 4096, [], [
    100, 500, 1000, 5000, 10000, 50000,
]);

Timer Helper

$stop = Telemetry::timer('db_query_duration');

$result = $db->query('SELECT ...');

$elapsed = $stop(['query' => 'user_lookup']);
// Records a histogram observation automatically

Direct Driver Construction

use MonkeysLegion\Telemetry\Metrics\StatsDMetrics;
use MonkeysLegion\Telemetry\Metrics\PrometheusMetrics;
use MonkeysLegion\Telemetry\Metrics\InMemoryMetrics;
use Prometheus\Storage\InMemory;

// StatsD
$metrics = new StatsDMetrics(
    host: '127.0.0.1',
    port: 8125,
    namespace: 'myapp',
    dogstatsd: true,    // DogStatsD tag format
    sampleRate: 0.5,    // 50% sampling
);

// Prometheus
$metrics = new PrometheusMetrics(
    adapter: new InMemory(),
    namespace: 'myapp',
);

// InMemory (testing)
$metrics = new InMemoryMetrics('myapp');
$metrics->counter('requests', 1, ['method' => 'GET']);
$all = $metrics->getMetrics(); // inspect recorded values

Distributed Tracing

W3C Trace Context compatible tracing with automatic parent-child span relationships.

Traced Callback (Recommended)

use MonkeysLegion\Telemetry\Telemetry;

$order = Telemetry::trace('create-order', function () use ($cart) {
    // Nested spans are automatically parented
    $items = Telemetry::trace('validate-items', fn () => $cart->validate());
    $payment = Telemetry::trace('charge-payment', fn () => $stripe->charge($cart));

    return new Order($items, $payment);
});

Manual Span Management

use MonkeysLegion\Telemetry\Telemetry;
use MonkeysLegion\Telemetry\Tracing\SpanKind;
use MonkeysLegion\Telemetry\Tracing\SpanStatus;

$span = Telemetry::startSpan('db.query', SpanKind::CLIENT, [
    'db.system'    => 'mysql',
    'db.statement' => 'SELECT * FROM users WHERE id = ?',
]);

try {
    $result = $db->query($sql, $params);
    $span->setStatus(SpanStatus::OK);
    $span->setAttribute('db.row_count', count($result));
} catch (\Throwable $e) {
    $span->recordException($e);
    $span->setStatus(SpanStatus::ERROR, $e->getMessage());
    throw $e;
} finally {
    $span->end();
}

Span Events

$span = Telemetry::startSpan('process-order');

$span->addEvent('payment.started', ['amount' => 99.99]);
// ... process payment ...
$span->addEvent('payment.completed', ['transaction_id' => 'txn_123']);

$span->end();

Trace Context Propagation

use MonkeysLegion\Telemetry\Factory\TelemetryFactory;

$tracer = TelemetryFactory::createTracer([
    'service_name' => 'api-gateway',
    'exporter'     => 'http',
    'endpoint'     => 'http://jaeger:4318/v1/traces',
]);

// Extract from incoming HTTP headers
$context = $tracer->extract($request->getHeaders());

// Inject into outgoing HTTP headers
$headers = $tracer->inject([]);
// $headers = ['traceparent' => '00-<trace_id>-<span_id>-01']

Span Kinds

Kind Use
SpanKind::INTERNAL Default — internal application logic
SpanKind::SERVER Incoming HTTP request handling
SpanKind::CLIENT Outgoing HTTP / DB / RPC call
SpanKind::PRODUCER Message queue publish
SpanKind::CONSUMER Message queue consume

Exporters

Exporter Class Use Case
console ConsoleExporter Local development / debugging
http JsonHttpExporter OTLP/HTTP — Jaeger, Tempo, Zipkin
InMemoryExporter Unit testing

Logging with Trace Correlation

PSR-3 compatible logging that automatically injects trace_id and span_id into every log entry.

use MonkeysLegion\Telemetry\Telemetry;

Telemetry::init([
    'tracing' => ['service_name' => 'api'],
    'logging' => ['json' => true, 'stream' => 'php://stderr'],
]);

Telemetry::trace('handle-request', function () {
    // trace_id and span_id injected automatically
    Telemetry::log()->info('Processing request', ['path' => '/api/users']);
    Telemetry::log()->warning('Slow query detected', ['duration_ms' => 450]);
});

// Output (JSON):
// {"level":"info","message":"Processing request","trace_id":"abc123...","span_id":"def456...","path":"/api/users","timestamp":"2026-04-27T20:00:00+00:00"}

Direct Logger Construction

use MonkeysLegion\Telemetry\Logging\TelemetryLogger;
use MonkeysLegion\Telemetry\Logging\StreamLogger;
use MonkeysLegion\Telemetry\Logging\JsonFormatter;
use MonkeysLegion\Telemetry\Logging\TracingContextProvider;
use Psr\Log\LogLevel;

$streamLogger = new StreamLogger(
    stream: '/var/log/app.log',
    minLevel: LogLevel::INFO,
    formatter: new JsonFormatter(prettyPrint: false),
);

$logger = new TelemetryLogger(
    logger: $streamLogger,
    contextProvider: new TracingContextProvider($tracer),
);

$logger->info('User created', ['user_id' => 42]);

// With explicit trace context
$logger->logWithTelemetry(
    level: 'info',
    message: 'Payment processed',
    context: ['amount' => 99.99],
    traceId: 'abc123',
    spanId: 'def456',
);

Log Processors

$logger->addProcessor(function (array $record): array {
    $record['context']['hostname'] = gethostname();
    $record['context']['pid'] = getmypid();
    return $record;
});

$logger->setDefaultContext(['app' => 'myapp', 'env' => 'production']);

PSR-15 Middleware

Request Metrics

Automatically records http_requests_total, http_request_duration_seconds, and http_requests_in_progress for every request.

use MonkeysLegion\Telemetry\Middleware\RequestMetricsMiddleware;

$middleware = new RequestMetricsMiddleware(
    metrics: $metrics,
    includeRoute:  true,   // label: route
    includeMethod: true,   // label: method
    includeStatus: true,   // label: status
);

// Add to your PSR-15 middleware pipeline
$app->pipe($middleware);

Recorded metrics:

Metric Type Labels
http_requests_total Counter method, route, status
http_request_duration_seconds Histogram method, route, status
http_requests_in_progress Gauge

Request Tracing

Creates a root SERVER span per request with W3C trace context propagation.

use MonkeysLegion\Telemetry\Middleware\RequestTracingMiddleware;

$middleware = new RequestTracingMiddleware(
    tracer: $tracer,
    propagateContext: true,  // inject traceparent in response headers
);

$app->pipe($middleware);

Span attributes:

Attribute Example
http.method GET
http.url https://api.example.com/users/42
http.target /users/42
http.status_code 200
http.host api.example.com
net.peer.ip 192.168.1.1

PHP 8 Attributes

Declarative instrumentation via attributes. Combine with an AOP framework or the MonkeysLegion service container for automatic weaving.

#[Traced] — Automatic Span Creation

use MonkeysLegion\Telemetry\Attribute\Traced;
use MonkeysLegion\Telemetry\Tracing\SpanKind;

class UserService
{
    #[Traced('fetch-user')]
    public function find(int $id): User { /* ... */ }

    #[Traced(kind: SpanKind::CLIENT, attributes: ['db.system' => 'mysql'])]
    public function query(string $sql): array { /* ... */ }
}

#[Counted] — Automatic Call Counting

use MonkeysLegion\Telemetry\Attribute\Counted;

class OrderController
{
    #[Counted('api_orders_total')]
    public function create(Request $request): Response { /* ... */ }

    #[Counted(labels: ['type' => 'refund'], countExceptions: true)]
    public function refund(string $orderId): void { /* ... */ }
}

#[Timed] — Automatic Duration Measurement

use MonkeysLegion\Telemetry\Attribute\Timed;

class ReportGenerator
{
    #[Timed('report_generation_duration')]
    public function generate(string $type): Report { /* ... */ }

    #[Timed(buckets: [0.1, 0.5, 1.0, 5.0, 10.0])]
    public function export(Report $report): string { /* ... */ }
}

Factory & Configuration

TelemetryFactory

use MonkeysLegion\Telemetry\Factory\TelemetryFactory;

// Create individual components
$metrics = TelemetryFactory::createMetrics([
    'driver'    => 'statsd',
    'namespace' => 'myapp',
    'host'      => '127.0.0.1',
    'port'      => 8125,
    'dogstatsd' => true,
    'sample_rate' => 0.5,
]);

$tracer = TelemetryFactory::createTracer([
    'enabled'      => true,
    'service_name' => 'api',
    'sample_rate'  => 1.0,
    'exporter'     => 'http',
    'endpoint'     => 'http://jaeger:4318/v1/traces',
    'headers'      => ['Authorization' => 'Bearer token'],
]);

$logger = TelemetryFactory::createLogger([
    'stream' => 'php://stderr',
    'level'  => 'info',
    'json'   => true,
    'pretty' => false,
]);

// Or create the full stack in one call
$stack = TelemetryFactory::create([
    'metrics' => ['driver' => 'prometheus'],
    'tracing' => ['service_name' => 'api', 'exporter' => 'http'],
    'logging' => ['json' => true],
]);
// $stack['metrics'], $stack['tracer'], $stack['logger']

Configuration Reference

Metrics

Key Type Default Description
driver string 'null' null, memory, prometheus, statsd
namespace string 'app' Metric name prefix
host string '127.0.0.1' StatsD host
port int 8125 StatsD port
dogstatsd bool false Enable DogStatsD tag format
sample_rate float 1.0 StatsD sampling rate (0.0–1.0)
prometheus_adapter Adapter InMemory Prometheus storage adapter

Tracing

Key Type Default Description
enabled bool true Enable/disable tracing
service_name string 'app' Service identifier
sample_rate float 1.0 Trace sampling rate (0.0–1.0)
exporter string 'console' console, http, none
endpoint string 'http://localhost:4318/v1/traces' OTLP endpoint
headers array [] Extra HTTP headers for exporter

Logging

Key Type Default Description
stream string|resource 'php://stderr' Output stream
level string 'debug' Minimum log level (PSR-3)
json bool false Enable JSON formatting
pretty bool false Pretty-print JSON

Complete Application Example

use MonkeysLegion\Telemetry\Telemetry;
use MonkeysLegion\Telemetry\Tracing\SpanKind;
use MonkeysLegion\Telemetry\Middleware\RequestMetricsMiddleware;
use MonkeysLegion\Telemetry\Middleware\RequestTracingMiddleware;

// ── Bootstrap ──────────────────────────────────────────────
Telemetry::init([
    'metrics' => [
        'driver'    => 'statsd',
        'namespace' => 'ecommerce',
        'host'      => 'statsd.internal',
    ],
    'tracing' => [
        'service_name' => 'order-service',
        'exporter'     => 'http',
        'endpoint'     => 'http://jaeger:4318/v1/traces',
        'sample_rate'  => 0.1,
    ],
    'logging' => [
        'json'   => true,
        'level'  => 'info',
        'stream' => '/var/log/app.log',
    ],
]);

// ── Middleware Pipeline ────────────────────────────────────
$app->pipe(new RequestTracingMiddleware(Telemetry::tracer()));
$app->pipe(new RequestMetricsMiddleware(Telemetry::metrics()));

// ── Application Code ───────────────────────────────────────
function placeOrder(Cart $cart): Order
{
    return Telemetry::trace('place-order', function () use ($cart) {
        Telemetry::log()->info('Order started', ['items' => count($cart)]);

        // Validate stock (traced)
        $stock = Telemetry::trace('check-inventory', function () use ($cart) {
            $stop = Telemetry::timer('inventory_check_duration');
            $result = InventoryService::check($cart->items());
            $stop(['warehouse' => 'us-east']);
            return $result;
        });

        // Charge payment (traced as CLIENT span)
        $payment = Telemetry::trace(
            'charge-payment',
            fn () => PaymentGateway::charge($cart->total()),
            SpanKind::CLIENT,
            ['payment.provider' => 'stripe'],
        );

        Telemetry::counter('orders_completed_total', 1, ['region' => 'us']);
        Telemetry::log()->info('Order completed', ['order_id' => $payment->orderId]);

        return new Order($stock, $payment);
    });
}

Prometheus Metrics Endpoint

use MonkeysLegion\Telemetry\Metrics\PrometheusMetrics;
use Prometheus\Storage\Redis;
use Prometheus\RenderTextFormat;

// Shared Redis-backed storage for multi-process
$metrics = new PrometheusMetrics(
    adapter: new Redis(['host' => 'redis']),
    namespace: 'myapp',
);

// Exposition route: GET /metrics
$renderer = new RenderTextFormat();
$registry = $metrics->getRegistry();

header('Content-Type', $renderer->getMimeType());
echo $renderer->render($registry->getMetricFamilySamples());

Architecture

MonkeysLegion\Telemetry\
├── Telemetry               # Static facade
├── Factory/
│   └── TelemetryFactory    # Component factory
├── Metrics/
│   ├── MetricsInterface    # Driver contract
│   ├── AbstractMetrics     # Base class (timer, default labels)
│   ├── NullMetrics         # No-op
│   ├── InMemoryMetrics     # Testing
│   ├── PrometheusMetrics   # Prometheus via promphp/prometheus_client_php
│   └── StatsDMetrics       # UDP-based StatsD / DogStatsD
├── Tracing/
│   ├── TracerInterface     # Tracer contract
│   ├── Tracer              # W3C Trace Context implementation
│   ├── SpanInterface       # Span contract
│   ├── Span                # Span implementation
│   ├── SpanKind            # Enum: INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER
│   ├── SpanStatus          # Enum: UNSET, OK, ERROR
│   ├── NullTracer          # No-op tracer
│   ├── NullSpan            # No-op span
│   └── Exporter/
│       ├── SpanExporterInterface
│       ├── ConsoleExporter
│       ├── InMemoryExporter
│       └── JsonHttpExporter
├── Logging/
│   ├── TelemetryLoggerInterface  # PSR-3 extension
│   ├── TelemetryLogger           # Core logger with processors
│   ├── StreamLogger              # File/stdout/stderr writer
│   ├── JsonFormatter             # Structured JSON output
│   ├── ContextProvider           # Context injection contract
│   └── TracingContextProvider    # Auto-injects trace/span IDs
├── Middleware/
│   ├── RequestMetricsMiddleware  # PSR-15 HTTP metrics
│   └── RequestTracingMiddleware  # PSR-15 distributed tracing
├── Attribute/
│   ├── Traced      # #[Traced] — automatic span creation
│   ├── Counted     # #[Counted] — automatic call counting
│   └── Timed       # #[Timed] — automatic duration measurement
└── Exception/
    ├── TelemetryException
    ├── MetricsException
    └── TracingException

Testing

composer test                # Run all tests
composer test:coverage       # Generate HTML coverage report
composer analyse             # PHPStan level 8
composer cs-check            # Code style check
composer cs-fix              # Auto-fix code style

License

MIT © MonkeysCloud