vesvasi / vesvasi-php
Production-grade PHP APM SDK built on OpenTelemetry with custom vesvasi attributes
v0.1
2026-04-26 11:52 UTC
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- open-telemetry/exporter-otlp: ^1.0
- open-telemetry/sdk: ^1.0
- open-telemetry/transport-grpc: ^1.0
- psr/log: ^3.0
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^10.0 || ^11.0
README
Production-grade PHP APM SDK built on OpenTelemetry with custom vesvasi attributes for deep observability.
Features
- OpenTelemetry Native: Built on OpenTelemetry SDK with full OTLP support
- Custom Attributes:
vesvasi.error,vesvasi.sql,vesvasi.http,vesvasi.commandfor easy filtering - Multi-Protocol Support: HTTP/protobuf, HTTP/JSON, gRPC exporters
- Configurable Sampling: Head-based and tail-based (error-focused) sampling
- Resource Monitoring: CPU, memory, and custom metrics collection
- Auto-Instrumentation: SQL, HTTP, commands, and exception tracking
- Flexible Filtering: Include/exclude by files, classes, methods, URLs, commands
- API Key Support:
x-api-keyheader for authentication - Auto-Instrumentation: SQL, HTTP, commands, and exception tracking
Installation
composer require vesvasi/vesvasi
Quick Start
<?php require_once 'vendor/autoload.php'; use Vesvasi\Vesvasi; // Configure Vesvasi Vesvasi::configure([ 'api_key' => 'your-api-key', 'endpoint' => 'https://otlp.example.com:4318', 'protocol' => 'http/protobuf', 'service' => [ 'name' => 'my-application', 'version' => '1.0.0', 'environment' => 'production', ], ]); // Start tracing $span = Vesvasi::startSpan('my-operation', ['user.id' => 123]); // ... your code ... $span->end();
Configuration
Full Configuration Example
$config = [ 'api_key' => 'your-api-key', 'endpoint' => 'https://otlp.example.com:4318', 'protocol' => 'http/protobuf', 'timeout' => 30, 'max_queue_size' => 2048, 'max_batch_size' => 512, 'debug' => false, 'service' => [ 'name' => 'my-application', 'version' => '1.0.0', 'environment' => 'production', 'namespace' => 'production', 'instance_id' => '', ], 'sampling' => [ 'head_percentage' => 10, 'always_sample_errors' => true, 'sampling_ratio' => 1000, 'max_traces_per_second' => 100, 'use_parent_sampling' => true, 'use_root_span_sampling' => true, ], 'filters' => [ 'cpu_threshold' => 5, 'memory_threshold' => 10, 'duration_threshold' => 100, 'include_files' => ['*'], 'exclude_files' => ['/vendor/*'], 'include_classes' => ['App\\*'], 'exclude_classes' => ['Vendor\\*'], 'include_methods' => ['*'], 'exclude_methods' => [], 'include_urls' => ['*'], 'exclude_urls' => ['/health'], 'include_commands' => ['*'], 'exclude_commands' => [], ], 'metrics' => [ 'enabled' => true, 'collect_cpu' => true, 'collect_memory' => true, 'collect_disk' => false, 'collect_network' => false, 'collection_interval' => 60000, 'export_interval' => 60000, 'enable_runtime_metrics' => true, ], 'logs' => [ 'enabled' => true, 'levels' => ['error', 'critical'], 'max_message_length' => 10000, 'include_stack_trace' => true, 'include_context' => true, 'redact_fields' => ['password', 'token', 'secret'], ], 'network' => [ 'proxy_url' => '', 'connect_timeout' => 10, 'read_timeout' => 30, 'write_timeout' => 30, 'verify_ssl' => true, 'ca_bundle_path' => null, 'extra_headers' => [], ], ]; Vesvasi::configure($config);
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
string | - | API key for authentication |
endpoint |
string | required | OTLP endpoint URL |
protocol |
string | http/protobuf |
Export protocol |
timeout |
int | 30 | Request timeout in seconds |
max_queue_size |
int | 2048 | Max spans in queue |
max_batch_size |
int | 512 | Batch export size |
Service Attributes
Standard OpenTelemetry service attributes:
service.name- Service identifierservice.version- Service versionservice.environment- Environment (prod/staging/dev)service.namespace- Service namespaceservice.instance.id- Unique instance identifier
Vesvasi Custom Attributes
Error Tracking
// Automatically sets vesvasi.error = true Vesvasi::recordError(new \RuntimeException('Something went wrong')); // Or via tracer $span = Vesvasi::startHttpSpan('GET', 'https://api.example.com'); $span->setAttribute('error', true);
SQL/Database Tracking
$vesvasi = Vesvasi::getInstance(); $sqlIntegration = $vesvasi->instrumentation()->getSqlIntegration(); // Trace a query $result = $sqlIntegration->traceQuery( 'SELECT * FROM users WHERE id = ?', 'my_database', 'mysql', [1], fn() => $db->query($sql) ); // Trace a transaction $sqlIntegration->traceTransaction( function () use ($db) { $db->beginTransaction(); $db->query('INSERT INTO logs (message) VALUES (?)', [$msg]); $db->commit(); }, 'my_database', 'mysql' );
Attributes set:
vesvasi.sql = truevesvasi.db.name = <database>db.system,db.statement,db.operation
HTTP Tracking
$vesvasi = Vesvasi::getInstance(); $httpIntegration = $vesvasi->instrumentation()->getHttpIntegration(); // Trace HTTP request $result = $httpIntegration->traceRequest( 'POST', 'https://api.example.com/users', ['Content-Type' => 'application/json'], '{"name":"John"}', fn() => $client->post($url, $body) );
Attributes set:
vesvasi.http = truehttp.method,http.url,http.status_code
Command Tracking
$vesvasi = Vesvasi::getInstance(); $commandIntegration = $vesvasi->instrumentation()->getCommandIntegration(); // Trace command execution $result = $commandIntegration->trace('artisan migrate', ['--force']); // Trace process $result = $commandIntegration->traceProcess('python script.py');
Attributes set:
vesvasi.command = truecommand,command.arguments,vesvasi.exit_code
Request/Performance Tracking
$vesvasi = Vesvasi::getInstance(); $requestIntegration = $vesvasi->instrumentation()->getRequestIntegration(); // Start a request span with full tracking $span = $requestIntegration->startRequestSpan( 'POST', 'https://api.example.com/users', ['Content-Type' => 'application/json'], '{"name":"John"}' ); // ... process request ... // End the span with response details $requestIntegration->endRequestSpan($span, 201, ['Content-Type' => 'application/json'], 128);
Attributes set:
vesvasi.request = truevesvasi.duration_ms- Total request durationvesvasi.cpu_user_ms- CPU user timevesvasi.memory_used_mb- Memory usedvesvasi.memory_peak_mb- Peak memoryrequest.method,request.url,request.path,request.headersresponse.status_code,response.time_ms,response.size
Cache Tracking
$vesvasi = Vesvasi::getInstance(); $cacheIntegration = $vesvasi->instrumentation()->getCacheIntegration(); // Trace a cache GET $value = $cacheIntegration->traceGet( 'user:123', 'redis', 'redis', fn() => $cache->get('user:123') ); // Trace a cache SET with TTL $cacheIntegration->traceSet( 'user:123', ['id' => 123, 'name' => 'John'], 3600, 'redis', 'redis', fn() => $cache->set('user:123', $data, 3600) ); // Trace multi-get $results = $cacheIntegration->traceMultiGet( ['user:123', 'product:456'], 'redis', 'redis', fn() => $cache->getMultiple(['user:123', 'product:456']) ); // Get cache statistics $stats = $cacheIntegration->getStats();
Attributes set:
vesvasi.cache = truecache.operation = get/set/delete/multi_get/multi_setcache.store,cache.key,cache.driver,cache.commandcache.hit,cache.miss,cache.existscache.ttl_seconds,cache.value_size_bytescache.hit_rate
Queue Tracking
$vesvasi = Vesvasi::getInstance(); $queueIntegration = $vesvasi->instrumentation()->getQueueIntegration(); // Push a job to queue $jobId = $queueIntegration->tracePush( 'SendWelcomeEmail', ['user_id' => 123, 'email' => 'john@example.com'], 'emails', 'redis' ); // Push a delayed job $queueIntegration->traceLater( 'SendReminderEmail', ['user_id' => 123], 3600, 'emails', 'redis' ); // Process a job with tracking $result = $queueIntegration->traceJob( 'SendWelcomeEmail', $jobId, 'emails', 'redis', 1, 3, function () { // Job logic return ['sent' => true]; } ); // Get queue statistics $stats = $queueIntegration->getStats(); $queueStats = $queueIntegration->getQueueStats('emails');
Attributes set:
vesvasi.queue = truequeue.operation = push/later/bulk/process/popqueue.name,queue.connection,queue.driverqueue.job_name,queue.job_idqueue.status = queued/processing/completed/failed/delayedqueue.delay_seconds,queue.attempts,queue.max_attemptsqueue.payload_size_bytes,queue.duration_msqueue.failed_reason
Metrics
System Metrics
$vesvasi = Vesvasi::getInstance(); $metrics = $vesvasi->metrics(); // Record CPU usage $metrics->recordCpuUsage(75.5); // Record memory usage $metrics->recordMemoryUsage(512.0, 2048.0); // Record custom metric $metrics->recordCustomMetric('requests.count', 1000, ['endpoint' => '/api/users']); // Increment counter $metrics->incrementCounter('errors.total', 1.0, ['type' => 'timeout']);
Available Metrics
system.cpu.usage- CPU usage percentagesystem.memory.usage- Memory usage in MBsystem.memory.available- Available memoryruntime.memory.heap_used- PHP heap usageruntime.gc.runs- Garbage collection runs
Logging
$vesvasi = Vesvasi::getInstance(); $logger = $vesvasi->logger(); // Basic logging $logger->info('User logged in', ['user_id' => 123]); $logger->error('Request failed', ['url' => '/api/users', 'status' => 500]); // With channel $logger->withChannel('auth')->warning('Invalid token'); // With context $logger->withContext(['request_id' => 'abc123'])->error('Error occurred');
Filtering
File Filtering
$config = Config::load([ 'endpoint' => 'https://otlp.example.com:4318', 'filters' => [ 'include_files' => ['/app/src/*', '/app/controllers/*'], 'exclude_files' => ['/app/vendor/*', '/app/tests/*'], ], ]);
Class Filtering
$config = [ 'filters' => [ 'include_classes' => ['App\\*', 'Domain\\*'], 'exclude_classes' => ['App\\Controller\\Base*', 'Vendor\\*'], ], ];
Resource Thresholds
$config = [ 'filters' => [ 'cpu_threshold' => 5, // Only trace if CPU > 5% 'memory_threshold' => 10, // Only trace if memory > 10MB 'duration_threshold' => 100, // Only trace if duration > 100ms ], ];
Sampling
Head-Based Sampling
$config = [ 'sampling' => [ 'head_percentage' => 10, // Sample 10% of spans ], ];
Error-Focused Sampling (Tail-Based)
$config = [ 'sampling' => [ 'head_percentage' => 10, 'always_sample_errors' => true, // Always sample error spans ], ];
OTLP Protocols
HTTP/protobuf (Default)
$config = [ 'protocol' => 'http/protobuf', 'endpoint' => 'https://otlp.example.com:4318/v1/traces', ];
HTTP/JSON
$config = [ 'protocol' => 'http/json', 'endpoint' => 'https://otlp.example.com:4318/v1/traces', ];
gRPC
$config = [ 'protocol' => 'grpc', 'endpoint' => 'https://otlp.example.com:4317', ];
Network Configuration
$config = [ 'network' => [ 'proxy_url' => 'http://proxy:8080', 'connect_timeout' => 10, 'read_timeout' => 30, 'write_timeout' => 30, 'verify_ssl' => true, 'ca_bundle_path' => '/path/to/ca-bundle.crt', 'extra_headers' => [ 'X-Custom-Header' => 'value', ], ], ];
Static Helpers
use Vesvasi\Vesvasi; // Start a span $span = Vesvasi::startSpan('operation'); // SQL span $span = Vesvasi::startSqlSpan('SELECT', 'SELECT * FROM users', 'db_name', 'mysql'); // HTTP span $builder = Vesvasi::startHttpSpan('GET', 'https://api.example.com'); // Command span $builder = Vesvasi::startCommandSpan('artisan migrate'); // Record error Vesvasi::recordError($exception); // Record metric Vesvasi::recordMetric('requests.count', 1000); // Increment counter Vesvasi::incrementCounter('errors.total', 1); // Log Vesvasi::log('error', 'Something went wrong', ['context' => 'value']);
Exception Handling
use Vesvasi\Vesvasi; Vesvasi::configure([/* config */]); // Enable exception tracking $vesvasi = Vesvasi::getInstance(); $exceptionIntegration = $vesvasi->instrumentation()->getExceptionIntegration(); $exceptionIntegration->register(); // Now all uncaught exceptions are automatically tracked throw new \RuntimeException('This will be tracked');
Shutdown
// Flush pending spans and shutdown Vesvasi::getInstance()->shutdown(); // Or reset entirely Vesvasi::reset();
Requirements
- PHP 8.2+
- OpenTelemetry SDK
- ext-json
- ext-pdo (optional, for SQL integration)
License
MIT