tracekit/php-apm

TraceKit APM for PHP - Framework-agnostic distributed tracing and code monitoring

Installs: 12

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/tracekit/php-apm

v1.0.4 2026-01-18 17:39 UTC

This package is auto-updated.

Last update: 2026-01-18 17:40:47 UTC


README

Framework-agnostic distributed tracing and performance monitoring for any PHP application.

Packagist Version Downloads License

Features

  • Framework Agnostic - Works with any PHP application (vanilla PHP, Symfony, Slim, etc.)
  • OpenTelemetry Standard - Built on OpenTelemetry for industry-standard tracing
  • Automatic Context Propagation - Child spans automatically inherit from parent
  • Manual Instrumentation - Full control over what and how you trace
  • HTTP Request Tracing - Track requests, database queries, and external API calls
  • Error Tracking - Capture exceptions with full context
  • Code Monitoring - Live debugging with breakpoints and variable inspection
  • Low Overhead - Minimal performance impact

Installation

composer require tracekit/php-apm

Quick Start

Basic Usage

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

// Initialize TraceKit
$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-php-app',
    'endpoint' => 'https://app.tracekit.dev/v1/traces',
]);

// Start a trace (returns array with span and scope)
$span = $tracekit->startTrace('process-request', [
    'http.method' => $_SERVER['REQUEST_METHOD'],
    'http.url' => $_SERVER['REQUEST_URI'],
]);

try {
    // Your application logic here
    processRequest();

    $tracekit->endSpan($span, [
        'http.status_code' => 200,
    ]);
} catch (\Exception $e) {
    $tracekit->recordException($span, $e);
    $tracekit->endSpan($span, [], 'ERROR');
    throw $e;
}

// Important: flush traces before exit
$tracekit->flush();

Local Development

Debug your PHP application locally without creating a cloud account using TraceKit Local UI.

Quick Start

# Install Local UI globally
npm install -g @tracekit/local-ui

# Start it
tracekit-local

The Local UI will start at http://localhost:9999 and automatically open in your browser.

How It Works

When running in development mode (APP_ENV=local or APP_ENV=development), the SDK automatically:

  1. Detects if Local UI is running at http://localhost:9999
  2. Sends traces to both Local UI and cloud (if API key is present)
  3. Falls back gracefully if Local UI is not available

No code changes needed! Just set the environment variable:

export APP_ENV=development
export TRACEKIT_API_KEY=your-key  # Optional - works without it!
php app.php

You'll see traces appear in real-time at http://localhost:9999.

Features

  • Real-time trace viewing in your browser
  • Works completely offline
  • No cloud account required
  • Zero configuration
  • Automatic cleanup (1000 traces max, 1 hour retention)

Local-Only Development

To use Local UI without cloud sending:

# Don't set TRACEKIT_API_KEY
export APP_ENV=development
php app.php

Traces will only go to Local UI.

Disabling Local UI

To disable automatic Local UI detection:

export APP_ENV=production
# or don't run Local UI

Learn More

Code Monitoring (Live Debugging)

TraceKit includes production-safe code monitoring for live debugging without redeployment.

Enable Code Monitoring

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

// Enable code monitoring
$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-php-app',
    'endpoint' => 'https://app.tracekit.dev/v1/traces',
    'code_monitoring_enabled' => true,
    'code_monitoring_max_depth' => 3,      // Nested array/object depth
    'code_monitoring_max_string' => 1000,  // Truncate long strings
]);

Add Debug Points

Add checkpoints anywhere in your code to capture variable state and stack traces:

<?php

class CheckoutService
{
    private $tracekit;

    public function __construct($tracekit)
    {
        $this->tracekit = $tracekit;
    }

    public function processPayment($userId, $cart)
    {
        // Automatic snapshot capture with label
        $this->tracekit->captureSnapshot('checkout-validation', [
            'user_id' => $userId,
            'cart_items' => count($cart['items'] ?? []),
            'total_amount' => $cart['total'] ?? 0,
        ]);

        try {
            $result = $this->chargeCard($cart['total'], $userId);

            // Another checkpoint
            $this->tracekit->captureSnapshot('payment-success', [
                'user_id' => $userId,
                'payment_id' => $result['payment_id'],
                'amount' => $result['amount'],
            ]);

            return $result;

        } catch (Exception $e) {
            // Automatic error capture
            $this->tracekit->captureSnapshot('payment-error', [
                'user_id' => $userId,
                'amount' => $cart['total'],
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    private function chargeCard($amount, $userId)
    {
        // Simulate payment processing
        if ($amount > 1000) {
            throw new Exception('Amount exceeds limit');
        }

        return [
            'payment_id' => 'pay_' . uniqid(),
            'amount' => $amount,
            'status' => 'succeeded',
        ];
    }
}

// Usage
$checkout = new CheckoutService($tracekit);
$result = $checkout->processPayment(123, ['total' => 99.99, 'items' => ['item1']]);

Manual Breakpoint Polling

Since PHP doesn't have built-in background task scheduling, you need to poll for breakpoints manually:

// Option 1: Poll on every Nth request
if (rand(1, 100) <= 5) { // 5% of requests
    $tracekit->pollBreakpoints();
}

// Option 2: Use a cron job
// */1 * * * * php /path/to/poll-breakpoints.php

// poll-breakpoints.php
require 'vendor/autoload.php';
$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-php-app',
    'code_monitoring_enabled' => true,
]);
$tracekit->pollBreakpoints();

Automatic Breakpoint Management

  • Auto-Registration: First call to captureSnapshot() automatically creates breakpoints in TraceKit
  • Smart Matching: Breakpoints match by function name + label (stable across code changes)
  • Manual Polling: You must call pollBreakpoints() periodically to fetch active breakpoints
  • Production Safe: No performance impact when breakpoints are inactive

What Gets Captured

Snapshots include:

  • Variables: Local variables at capture point
  • Stack Trace: Full call stack with file/line numbers
  • Request Context: HTTP method, URL, headers, query params (when available)
  • Execution Time: When the snapshot was captured

Framework Integration Examples

Slim Framework

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;
use Slim\Factory\AppFactory;

$app = AppFactory::create();

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'slim-app',
    'code_monitoring_enabled' => true,
]);

$app->post('/checkout', function ($request, $response) use ($tracekit) {
    $data = $request->getParsedBody();

    // Poll breakpoints occasionally
    if (rand(1, 20) === 1) { // 5% chance
        $tracekit->pollBreakpoints();
    }

    // Capture snapshot
    $tracekit->captureSnapshot('checkout-start', [
        'user_id' => $data['user_id'],
        'amount' => $data['amount'],
    ]);

    // Process payment...
    $result = ['payment_id' => 'pay_' . uniqid()];

    return $response->withJson($result);
});

$app->run();

Symfony Controller

<?php

namespace App\Controller;

use TraceKit\PHP\TracekitClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

class PaymentController
{
    private $tracekit;

    public function __construct()
    {
        $this->tracekit = new TracekitClient([
            'api_key' => getenv('TRACEKIT_API_KEY'),
            'service_name' => 'symfony-app',
            'code_monitoring_enabled' => true,
        ]);
    }

    public function checkout(Request $request): JsonResponse
    {
        // Poll occasionally (you could also use a cron job)
        if (rand(1, 20) === 1) {
            $this->tracekit->pollBreakpoints();
        }

        $data = json_decode($request->getContent(), true);

        $this->tracekit->captureSnapshot('checkout-validation', [
            'user_id' => $data['user_id'],
            'cart_total' => $data['cart']['total'],
        ]);

        // Process payment...
        $result = $this->processPayment($data);

        $this->tracekit->captureSnapshot('checkout-complete', [
            'user_id' => $data['user_id'],
            'payment_id' => $result['payment_id'],
        ]);

        return new JsonResponse($result);
    }

    private function processPayment(array $data): array
    {
        // Payment logic here...
        return ['payment_id' => 'pay_' . uniqid()];
    }
}

Configuration

Basic Configuration

$tracekit = new TracekitClient([
    // Required: Your TraceKit API key
    'api_key' => getenv('TRACEKIT_API_KEY'),

    // Optional: Service name (default: 'php-app')
    'service_name' => 'my-service',

    // Optional: TraceKit endpoint (default: 'https://app.tracekit.dev/v1/traces')
    'endpoint' => 'https://app.tracekit.dev/v1/traces',

    // Optional: Enable/disable tracing (default: true)
    'enabled' => getenv('APP_ENV') === 'production',

    // Optional: Sample rate 0.0-1.0 (default: 1.0 = 100%)
    'sample_rate' => 0.5, // Trace 50% of requests

    // Optional: Enable live code debugging (default: false)
    'code_monitoring_enabled' => true,
    'code_monitoring_max_depth' => 3,      // Nested array/object depth
    'code_monitoring_max_string' => 1000,  // Truncate long strings

    // Optional: Map hostnames to service names for service graph
    'service_name_mappings' => [
        'localhost:8082' => 'payment-service',
        'localhost:8083' => 'user-service',
    ],
]);

Environment Variables

Create a .env file or set these environment variables:

TRACEKIT_API_KEY=ctxio_your_generated_api_key_here
TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
TRACEKIT_SERVICE_NAME=my-php-app

Automatic HTTP Client Instrumentation

TraceKit provides instrumentation for outgoing HTTP calls to create service dependency graphs.

How It Works

When your service makes an HTTP request:

  1. ✅ TraceKit creates a CLIENT span for the outgoing request
  2. ✅ Trace context is injected into request headers (traceparent)
  3. peer.service attribute is set based on the target hostname
  4. ✅ The receiving service creates a SERVER span linked to your CLIENT span
  5. ✅ TraceKit maps the dependency: YourService → TargetService

Supported HTTP Clients

cURL (with wrapper)

$ch = curl_init("http://payment-service/charge");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['amount' => 99.99]));

// Wrap curl_exec with TraceKit instrumentation
$instrumentation = $tracekit->getHttpClientInstrumentation();
$result = $instrumentation->wrapCurlExec($ch);

curl_close($ch);

What This Does:

  • Creates a CLIENT span for the cURL request
  • Sets peer.service = "payment-service"
  • Injects traceparent header for distributed tracing
  • Records HTTP status code and errors

Guzzle (with middleware)

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

// Create Guzzle client with TraceKit middleware
$stack = HandlerStack::create();
$stack->push($tracekit->getHttpClientInstrumentation()->getGuzzleMiddleware());

$client = new Client(['handler' => $stack]);

// All Guzzle requests now automatically create CLIENT spans!
$response = $client->post('http://payment-service/charge', [
    'json' => ['amount' => 99.99],
]);

$response = $client->get('http://inventory-service/check');

Service Name Detection

TraceKit intelligently extracts service names from URLs:

URL Extracted Service Name
http://payment-service:3000 payment-service
http://payment.internal payment
http://payment.svc.cluster.local payment
https://api.example.com api.example.com

This works seamlessly with:

  • Kubernetes service names
  • Internal DNS names
  • Docker Compose service names
  • External APIs

Custom Service Name Mappings

For local development or when service names can't be inferred from hostnames, use service_name_mappings:

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-service',
    // Map localhost URLs to actual service names
    'service_name_mappings' => [
        'localhost:8082' => 'payment-service',
        'localhost:8083' => 'user-service',
        'localhost:8084' => 'inventory-service',
        'localhost:5001' => 'analytics-service',
    ],
]);

// Now requests to localhost:8082 will show as "payment-service" in the service graph
$response = $httpClient->get('http://localhost:8082/charge');
// -> Creates CLIENT span with peer.service = "payment-service"

This is especially useful when:

  • Running microservices locally on different ports
  • Using Docker Compose with localhost networking
  • Testing distributed tracing in development

Complete Example: Multi-Service Application

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'checkout-service',
]);

// Setup Guzzle with TraceKit instrumentation
$stack = HandlerStack::create();
$stack->push($tracekit->getHttpClientInstrumentation()->getGuzzleMiddleware());
$httpClient = new Client(['handler' => $stack]);

// Start request trace
$requestSpan = $tracekit->startTrace('http-request', [
    'http.method' => 'POST',
    'http.url' => '/checkout',
]);

try {
    // These HTTP calls automatically create CLIENT spans
    $paymentResponse = $httpClient->post('http://payment-service/charge', [
        'json' => [
            'amount' => 99.99,
            'user_id' => 123,
        ],
    ]);

    $inventoryResponse = $httpClient->post('http://inventory-service/reserve', [
        'json' => ['item_id' => 456],
    ]);

    $tracekit->endSpan($requestSpan, ['http.status_code' => 200]);

    echo json_encode(['success' => true]);

} catch (\Exception $e) {
    $tracekit->recordException($requestSpan, $e);
    $tracekit->endSpan($requestSpan, [], 'ERROR');
    echo json_encode(['error' => $e->getMessage()]);
}

$tracekit->flush();

Viewing Service Dependencies

Visit your TraceKit dashboard to see:

  • Service Map: Visual graph showing which services call which
  • Service List: Table of all services with health metrics
  • Service Detail: Upstream/downstream dependencies with latency and error info

Why Manual Wrapping?

Unlike Node.js or Python, PHP doesn't support automatic function interception. Therefore:

  • cURL: Use the wrapper function wrapCurlExec()
  • Guzzle: Add the middleware once when creating the client
  • Other clients: Create middleware/wrappers as needed

This gives you full control while maintaining zero-overhead when not used.

Usage Examples

HTTP Request Tracing

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'api-server',
]);

// Start tracing the request
$requestSpan = $tracekit->startTrace('http-request', [
    'http.method' => $_SERVER['REQUEST_METHOD'],
    'http.url' => $_SERVER['REQUEST_URI'],
    'http.user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
]);

try {
    // Route the request
    $result = handleRequest($_SERVER['REQUEST_URI']);

    $tracekit->endSpan($requestSpan, [
        'http.status_code' => 200,
    ]);

    http_response_code(200);
    header('Content-Type: application/json');
    echo json_encode($result);

} catch (\Exception $e) {
    $tracekit->recordException($requestSpan, $e);
    $tracekit->endSpan($requestSpan, [
        'http.status_code' => 500,
    ], 'ERROR');

    http_response_code(500);
    echo json_encode(['error' => $e->getMessage()]);
}

$tracekit->flush();

Database Query Tracing

<?php

function getUserById($tracekit, $userId) {
    // Child span automatically links to active parent via context
    $span = $tracekit->startSpan('db.query.users', [
        'db.system' => 'mysql',
        'db.operation' => 'SELECT',
        'user.id' => $userId,
    ]);

    try {
        $pdo = new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');
        $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$userId]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        $tracekit->endSpan($span, [
            'db.rows_affected' => $stmt->rowCount(),
        ]);

        return $user;
    } catch (\PDOException $e) {
        $tracekit->recordException($span, $e);
        $tracekit->endSpan($span, [], 'ERROR');
        throw $e;
    }
}

External API Call Tracing

<?php

function fetchExternalData($tracekit, $url) {
    $span = $tracekit->startSpan('http.client.get', [
        'http.url' => $url,
        'http.method' => 'GET',
    ]);

    try {
        $response = file_get_contents($url);
        $data = json_decode($response, true);

        $tracekit->endSpan($span, [
            'http.status_code' => 200,
            'response.size' => strlen($response),
        ]);

        return $data;
    } catch (\Exception $e) {
        $tracekit->recordException($span, $e);
        $tracekit->endSpan($span, [], 'ERROR');
        throw $e;
    }
}

Nested Spans (Automatic Context Propagation)

<?php

function processOrder($tracekit, $orderId) {
    // Parent span
    $orderSpan = $tracekit->startSpan('process-order', [
        'order.id' => $orderId,
    ]);

    try {
        // Child spans automatically link to orderSpan via context

        // Validate order
        $validationSpan = $tracekit->startSpan('validate-order', [
            'order.id' => $orderId,
        ]);
        $valid = validateOrder($orderId);
        $tracekit->endSpan($validationSpan, ['valid' => $valid]);

        if (!$valid) {
            throw new \Exception('Invalid order');
        }

        // Process payment
        $paymentSpan = $tracekit->startSpan('process-payment', [
            'order.id' => $orderId,
        ]);
        $paymentResult = processPayment($orderId);
        $tracekit->endSpan($paymentSpan, ['payment.status' => $paymentResult]);

        // Ship order
        $shippingSpan = $tracekit->startSpan('ship-order', [
            'order.id' => $orderId,
        ]);
        $trackingId = shipOrder($orderId);
        $tracekit->endSpan($shippingSpan, ['tracking.id' => $trackingId]);

        $tracekit->endSpan($orderSpan, [
            'order.status' => 'completed',
        ]);

        return true;
    } catch (\Exception $e) {
        $tracekit->recordException($orderSpan, $e);
        $tracekit->endSpan($orderSpan, [], 'ERROR');
        throw $e;
    }
}

Framework Integration

Vanilla PHP

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-app',
]);

$span = $tracekit->startTrace('http-request', [
    'http.method' => $_SERVER['REQUEST_METHOD'],
    'http.url' => $_SERVER['REQUEST_URI'],
]);

// Your application logic
echo "Hello World!";

$tracekit->endSpan($span);
$tracekit->flush();

Symfony

<?php

namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use TraceKit\PHP\TracekitClient;

class TracekitListener
{
    private TracekitClient $tracekit;
    private $currentSpan;

    public function __construct()
    {
        $this->tracekit = new TracekitClient([
            'api_key' => $_ENV['TRACEKIT_API_KEY'],
            'service_name' => 'symfony-app',
        ]);
    }

    public function onKernelRequest(RequestEvent $event)
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $request = $event->getRequest();
        $this->currentSpan = $this->tracekit->startTrace('http-request', [
            'http.method' => $request->getMethod(),
            'http.url' => $request->getRequestUri(),
            'http.route' => $request->attributes->get('_route'),
        ]);
    }

    public function onKernelResponse(ResponseEvent $event)
    {
        if (!$event->isMainRequest() || !$this->currentSpan) {
            return;
        }

        $this->tracekit->endSpan($this->currentSpan, [
            'http.status_code' => $event->getResponse()->getStatusCode(),
        ]);
        $this->tracekit->flush();
    }

    public function onKernelException(ExceptionEvent $event)
    {
        if ($this->currentSpan) {
            $this->tracekit->recordException($this->currentSpan, $event->getThrowable());
            $this->tracekit->endSpan($this->currentSpan, [], 'ERROR');
            $this->tracekit->flush();
        }
    }
}

Slim Framework

<?php

use Slim\Factory\AppFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use TraceKit\PHP\TracekitClient;

require 'vendor/autoload.php';

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'slim-app',
]);

$app = AppFactory::create();

// Tracing middleware
$app->add(function (Request $request, $handler) use ($tracekit) {
    $span = $tracekit->startTrace('http-request', [
        'http.method' => $request->getMethod(),
        'http.url' => (string) $request->getUri(),
    ]);

    try {
        $response = $handler->handle($request);

        $tracekit->endSpan($span, [
            'http.status_code' => $response->getStatusCode(),
        ]);

        $tracekit->flush();
        return $response;
    } catch (\Exception $e) {
        $tracekit->recordException($span, $e);
        $tracekit->endSpan($span, [], 'ERROR');
        $tracekit->flush();
        throw $e;
    }
});

$app->get('/hello/{name}', function (Request $request, Response $response, $args) {
    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
});

$app->run();

How Context Propagation Works

TraceKit uses OpenTelemetry's Context API to automatically manage span relationships:

  1. Root Span: startTrace() creates a root span and activates it in the context
  2. Child Spans: startSpan() automatically inherits from the currently active span
  3. Scope Management: Each span has a scope that's detached when endSpan() is called
  4. Automatic Hierarchy: All spans within the same request share the same trace ID
// Root span (activated in context)
$rootSpan = $tracekit->startTrace('http-request');

    // Child 1 (inherits from root automatically)
    $child1 = $tracekit->startSpan('database-query');
    $tracekit->endSpan($child1);  // Detaches child1, root becomes active again

    // Child 2 (also inherits from root)
    $child2 = $tracekit->startSpan('api-call');

        // Grandchild (inherits from child2)
        $grandchild = $tracekit->startSpan('process-data');
        $tracekit->endSpan($grandchild);  // Detaches grandchild, child2 active

    $tracekit->endSpan($child2);  // Detaches child2, root active

$tracekit->endSpan($rootSpan);  // Detaches root

API Reference

TracekitClient

__construct(array $config)

Initialize the TraceKit client.

Parameters:

  • api_key (string, required) - Your TraceKit API key
  • service_name (string, optional) - Service name (default: 'php-app')
  • endpoint (string, optional) - TraceKit endpoint URL
  • enabled (bool, optional) - Enable/disable tracing (default: true)
  • sample_rate (float, optional) - Sample rate 0.0-1.0 (default: 1.0)

startTrace(string $operationName, array $attributes = []): array

Start a new root trace span (server request). Returns an array with the span and scope.

Returns: ['span' => SpanInterface, 'scope' => ScopeInterface]

startSpan(string $operationName, array $attributes = []): array

Start a new child span. Automatically inherits from the currently active span via context.

Returns: ['span' => SpanInterface, 'scope' => ScopeInterface]

endSpan(array $spanData, array $finalAttributes = [], ?string $status = 'OK'): void

End a span and detach its scope from the context.

Parameters:

  • $spanData - Array returned from startTrace() or startSpan()
  • $finalAttributes - Optional attributes to add before ending
  • $status - Span status: 'OK' or 'ERROR'

recordException(array $spanData, \Throwable $exception): void

Record an exception on a span.

addEvent(array $spanData, string $name, array $attributes = []): void

Add an event to a span.

flush(): void

Force flush all pending spans to the backend.

shutdown(): void

Shutdown the tracer provider.

Performance

TraceKit APM is designed to have minimal performance impact:

  • < 5% overhead on average request time
  • Asynchronous trace sending
  • Configurable sampling for high-traffic applications
  • Efficient context propagation

Requirements

  • PHP 8.1 or higher
  • Composer
  • PSR-18 HTTP Client (e.g., Guzzle)

Support

License

MIT License. See LICENSE for details.

Credits

Built with ❤️ by the TraceKit team using OpenTelemetry.