Production-grade PHP APM SDK built on OpenTelemetry with custom vesvasi attributes for deep observability.
- 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
composer require vesvasi/vesvasi<?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();$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);| 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 |
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
// 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);$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
$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
$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
$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
$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
$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
$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']);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
$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');$config = Config::load([
'endpoint' => 'https://otlp.example.com:4318',
'filters' => [
'include_files' => ['/app/src/*', '/app/controllers/*'],
'exclude_files' => ['/app/vendor/*', '/app/tests/*'],
],
]);$config = [
'filters' => [
'include_classes' => ['App\\*', 'Domain\\*'],
'exclude_classes' => ['App\\Controller\\Base*', 'Vendor\\*'],
],
];$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
],
];$config = [
'sampling' => [
'head_percentage' => 10, // Sample 10% of spans
],
];$config = [
'sampling' => [
'head_percentage' => 10,
'always_sample_errors' => true, // Always sample error spans
],
];$config = [
'protocol' => 'http/protobuf',
'endpoint' => 'https://otlp.example.com:4318/v1/traces',
];$config = [
'protocol' => 'http/json',
'endpoint' => 'https://otlp.example.com:4318/v1/traces',
];$config = [
'protocol' => 'grpc',
'endpoint' => 'https://otlp.example.com:4317',
];$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',
],
],
];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']);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');// Flush pending spans and shutdown
Vesvasi::getInstance()->shutdown();
// Or reset entirely
Vesvasi::reset();- PHP 8.2+
- OpenTelemetry SDK
- ext-json
- ext-pdo (optional, for SQL integration)
MIT