Framework-Agnostic PHP Integration

PHP Language Integration

Add distributed tracing to any PHP application - vanilla PHP, Symfony, Slim, or any other framework. Built on OpenTelemetry for industry-standard observability.

Prerequisites

  • PHP 8.1 or higher
  • Composer package manager
  • TraceKit API key from your dashboard

🔍 What Gets Traced Automatically?

ComponentAutomaticDetails
HTTP RequestsManualAdd tracing in your request handler
Database QueriesManualInstrument your database calls
External APIsManualWrap HTTP client calls with spans
ExceptionsBuilt-inUse recordException() method

📦 Installation

1. Install via Composer

composer require tracekit/php-apm

2. Configure Environment

Create a .env file or set 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

🚀 Basic Usage

Simple HTTP Request Tracing

<?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
$span = $tracekit->startTrace('http-request', [
    'http.method' => $_SERVER['REQUEST_METHOD'],
    'http.url' => $_SERVER['REQUEST_URI'],
]);

try {
    // Your application logic here
    echo "Hello World!";

    $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();

🔍 Code Monitoring (Live Debugging)

Debug Production Code Without Redeploying

Set non-breaking breakpoints and capture variable state, stack traces, and request context in production. No code changes or redeployment needed!

Learn more about Code Monitoring

Enable Code Monitoring

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

// Initialize with code monitoring enabled
$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
]);

Capture Snapshots

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 - captures on success
            $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;
        }
    }
}

Manual Polling Required

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

<?php

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

// Option 2: Use a cron job (recommended for high-traffic apps)
// */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();
🎯

Auto-Registration

First call to captureSnapshot() automatically creates breakpoints

📸

Variable Capture

Capture all local variables and request context at breakpoint

📚

Stack Traces

Complete call stack with file names and line numbers

🚀

Production Safe

No performance impact when breakpoints are inactive

Framework Integration

🎵 Symfony

Create an event listener to automatically trace all HTTP requests:

<?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',
            'code_monitoring_enabled' => true,  // Enable code monitoring
        ]);
    }

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

        // Poll for breakpoints on 5% of requests
        if (rand(1, 20) === 1) {
            $this->tracekit->pollBreakpoints();
        }

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

        // Capture request start snapshot
        $this->tracekit->captureSnapshot('request-start', [
            'route' => $request->attributes->get('_route'),
            'method' => $request->getMethod(),
        ]);
    }

    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());
            
            // Capture exception snapshot
            $this->tracekit->captureSnapshot('exception-handler', [
                'exception' => get_class($event->getThrowable()),
                'message' => $event->getThrowable()->getMessage(),
            ]);
            
            $this->tracekit->endSpan($this->currentSpan, [], 'ERROR');
            $this->tracekit->flush();
        }
    }
}

Slim Framework

Use middleware to trace requests:

<?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',
    'endpoint' => 'https://app.tracekit.dev/v1/traces',
    'code_monitoring_enabled' => true,  // Enable code monitoring
]);

$app = AppFactory::create();

// Tracing & monitoring middleware
$app->add(function (Request $request, $handler) use ($tracekit) {
    // Poll for breakpoints on 5% of requests
    if (rand(1, 20) === 1) {
        $tracekit->pollBreakpoints();
    }

    $span = $tracekit->startTrace('http-request', [
        'http.method' => $request->getMethod(),
        'http.url' => (string) $request->getUri(),
    ]);

    // Capture request snapshot
    $tracekit->captureSnapshot('middleware-start', [
        'path' => $request->getUri()->getPath(),
        'method' => $request->getMethod(),
    ]);

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

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

        $tracekit->flush();
        return $response;
    } catch (Exception $e) {
        $tracekit->recordException($span, $e);
        
        // Capture exception snapshot
        $tracekit->captureSnapshot('middleware-error', [
            'exception' => get_class($e),
            'message' => $e->getMessage(),
        ]);
        
        $tracekit->endSpan($span, [], 'ERROR');
        $tracekit->flush();
        throw $e;
    }
});

$app->get('/hello/{name}', function (Request $request, Response $response, $args) use ($tracekit) {
    // Capture route handler snapshot
    $tracekit->captureSnapshot('hello-route', [
        'name' => $args['name'],
    ]);

    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
});

$app->run();

🔧 Vanilla PHP

For vanilla PHP applications, wrap your application logic:

<?php

require 'vendor/autoload.php';

use TraceKit\PHP\TracekitClient;

$tracekit = new TracekitClient([
    'api_key' => getenv('TRACEKIT_API_KEY'),
    'service_name' => 'my-app',
    'endpoint' => 'https://app.tracekit.dev/v1/traces',
    'code_monitoring_enabled' => true,  // Enable code monitoring
]);

// Poll for breakpoints on 5% of requests
if (rand(1, 20) === 1) {
    $tracekit->pollBreakpoints();
}

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

try {
    // Capture snapshot at app start
    $tracekit->captureSnapshot('app-start', [
        'uri' => $_SERVER['REQUEST_URI'],
        'method' => $_SERVER['REQUEST_METHOD'],
    ]);

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

    $tracekit->endSpan($span, [
        'http.status_code' => 200,
    ]);
} catch (Exception $e) {
    $tracekit->recordException($span, $e);
    
    // Capture exception snapshot
    $tracekit->captureSnapshot('app-error', [
        'exception' => get_class($e),
        'message' => $e->getMessage(),
    ]);
    
    $tracekit->endSpan($span, [], 'ERROR');
}

$tracekit->flush();

🤖 Auto-Instrumentation

PHP tracing typically requires manual instrumentation for most operations.

🚀 Local UI (Development Mode)

Debug your PHP application locally without creating an account. TraceKit Local UI runs on your machine at http://localhost:9999 and automatically receives traces when you run your app in development mode.

💡

Automatic Detection

The PHP SDK automatically detects when Local UI is running on port 9999 and sends traces to both Local UI and cloud (if you have an API key configured).

Quick Start

1. Install the Local UI:

npm install -g @tracekit/local-ui

2. Start the Local UI:

tracekit-local

3. Run your app in development mode:

ENV=development php -S localhost:8000

4. Open your browser:

http://localhost:9999

Features

🔍

Auto-Detection

SDK checks for Local UI at localhost:9999 on startup

Real-Time Updates

See traces instantly with WebSocket live updates

🔒

Development Only

Only activates when ENV=development

🌐

Works Offline

No internet connection required - everything runs locally

Benefits

  • See your first trace in under 60 seconds
  • Debug locally without switching to the cloud dashboard
  • Stay in your flow - everything runs on your machine
  • Works completely offline
  • Perfect for development and demos
💡

Troubleshooting

If traces aren't appearing in Local UI, check:

  • Local UI is running (curl http://localhost:9999/api/health)
  • ENV=development is set
  • SDK version is v1.0.4 or higher
  • Check console for "🔍 Local UI detected" message

🌐 Service Discovery

TraceKit automatically instruments outgoing HTTP calls to create service dependency graphs. When your service makes an HTTP request to another service, TraceKit creates CLIENT spans and injects trace context headers.

Custom Service Name Mappings

For local development or when service names can't be inferred from hostnames, configure service name mappings:

<?php

use TraceKit\PHP\TracekitClient;

$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',
    ],
]);

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

This maps localhost:8082 to "payment-service" in your service graph.

Service Name Detection

TraceKit intelligently extracts service names from URLs:

URLExtracted Service Name
http://payment-service:3000payment-service
http://payment.internalpayment
http://payment.svc.cluster.localpayment
https://api.example.comapi.example.com
💡

Viewing Service Dependencies

Visit your TraceKit dashboard to see the Service Map - a visual graph showing which services call which, with health metrics and latency data.

🔧 Manual Instrumentation

Database Queries

<?php

function getUserById($tracekit, $userId) {
    $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 Calls

<?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

<?php

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

    try {
        // 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]);

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

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

🔐 Environment Variables

Store your API key and configuration in environment variables as shown in the installation section above.

⚙️ Configuration Options

Customize the TracekitClient with these configuration options:

<?php

$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 code monitoring (default: false)
    'code_monitoring_enabled' => true,
    'code_monitoring_max_depth' => 3,      // Nested array/object depth
    'code_monitoring_max_string' => 1000,  // Truncate long strings
]);

Custom Metrics

Track custom metrics like request counts, queue sizes, and response times using the TraceKit metrics API.

Counter

Track monotonically increasing values (requests, events):

use TraceKit\PHP\MetricsRegistry;

// Initialize the registry
$metrics = new MetricsRegistry($endpoint, $apiKey, 'my-service');

// Create a counter with optional tags
$counter = $metrics->counter('http.requests.total', [
    'service' => 'api',
    'method' => 'GET',
]);

// Increment by 1
$counter->inc();

// Add a specific value
$counter->add(5);

Gauge

Track values that can go up or down (queue size, connections):

// Create a gauge
$gauge = $metrics->gauge('http.connections.active');

// Set to specific value
$gauge->set(42);

// Increment/decrement
$gauge->inc();
$gauge->dec();

Histogram

Track value distributions (latencies, sizes):

// Create a histogram with tags
$histogram = $metrics->histogram('http.request.duration', [
    'unit' => 'ms',
]);

// Record values
$histogram->record(45.2);
$histogram->record(123.5);

🔧 Troubleshooting

Traces not appearing?

  • Verify your API key is correct and active
  • Check that enabled is set to true
  • Ensure you're calling flush() before script termination

Composer dependency issues?

  • Ensure PHP 8.1+ is installed: php -v
  • Update Composer: composer self-update
  • Clear cache: composer clear-cache

Performance concerns?

  • Use sampling: 'sample_rate' => 0.1 (10% of requests)
  • Disable in development: 'enabled' => getenv('APP_ENV') === 'production'

Complete Example

See the framework integration examples above for complete working code.