monkeyscloud / monkeyslegion-telemetry
Comprehensive telemetry package with PSR-3 logging, metrics (Prometheus/StatsD), and distributed tracing for MonkeysLegion projects
Package info
github.com/MonkeysCloud/MonkeysLegion-Telemetry
pkg:composer/monkeyscloud/monkeyslegion-telemetry
Requires
- php: ^8.4
- psr/log: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- promphp/prometheus_client_php: ^2.14
- psr/http-message: ^2.0
- psr/http-server-middleware: ^1.0
Suggests
- ext-redis: Recommended for Prometheus storage with Redis
- promphp/prometheus_client_php: Required for PrometheusMetrics adapter (^2.14)
- psr/http-message: Required for HTTP middleware (^2.0)
- psr/http-server-middleware: Required for PSR-15 middleware (^1.0)
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.
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 Drivers — NullMetrics, 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