# PHP Language Integration

> Integrate TraceKit with vanilla PHP applications. Automatic distributed tracing with dynamic logs for debugging production PHP code without redeploying.

- Canonical: https://tracekit.dev/docs/languages/php

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

**Let AI set up TraceKit for you** -- AI assistants can guide you through the entire setup process. [Try AI Setup](/docs/ai)

## Prerequisites

- PHP 8.1 or higher
- Composer package manager
- A TraceKit account ([create one free](https://app.tracekit.dev/register))
- A generated API key from the [API Keys](https://app.tracekit.dev/api-keys) page

## What Gets Traced Automatically

| Component | Span Type | Captured Attributes | Auto-Traced? |
|-----------|-----------|-------------------|--------------|
| HTTP Requests | SERVER | method, route, status_code, duration, client_ip | Yes (via TracekitClient middleware) |
| Database Queries | DB | db.system, db.statement, db.name, duration | Yes (via PDO wrapper) |
| External API Calls | CLIENT | method, url, status_code, duration | Yes (via HTTP client wrapper) |
| Exceptions | N/A | exception.type, exception.message, stack_trace | Yes (built-in) |

## Installation

### 1. Install via Composer

```bash
composer require tracekit/php-apm
```

### 2. Configure Environment

Create a `.env` file or set environment variables:

```bash
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
<?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();
```

**Verify It Works** -- Start your application and make a few requests. Then open the [Traces page](https://app.tracekit.dev/traces) in your TraceKit dashboard. You should see:

- SERVER spans for each incoming HTTP request with route and status
- CLIENT spans for outgoing HTTP calls (if instrumented)
- DB spans for database queries (if instrumented)

Traces typically appear within 30 seconds. If you don't see them, check the [Troubleshooting](#troubleshooting) section.

## Code Monitoring (Dynamic Logs)

**Debug Production Code Without Redeploying** -- Add dynamic logs and capture variable state, stack traces, and request context in production. No code changes or redeployment needed! [Learn more about Code Monitoring](/docs/code-monitoring)

### Enable Code Monitoring

```php
<?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
<?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
<?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();
```

### Features

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

### Production Safety

The PHP SDK includes built-in safety mechanisms to ensure code monitoring never impacts your production application:

- **PII Scrubbing** -- 13 built-in patterns detect sensitive fields (password, token, secret, api_key, etc.) and replace values with typed `[REDACTED:type]` markers. Enabled by default -- no configuration needed.
- **Crash Isolation** -- All SDK entry points wrap operations in `catch(\Throwable)` blocks. If the SDK encounters an error, your application continues running unaffected.
- **Circuit Breaker** -- After 3 consecutive failures within 60 seconds, the SDK enters a 5-minute cooldown period and stops making requests. Automatically recovers after cooldown.
- **Remote Kill Switch** -- Instantly disable code monitoring from the dashboard. The kill signal propagates via SSE for CLI/worker processes or polling for web requests. No redeployment required.

**Real-Time Updates** -- CLI and worker processes receive breakpoint updates and kill switch signals in real time via Server-Sent Events (SSE). Web requests use a polling fallback since PHP's process-per-request model does not support persistent connections.

### Code Monitoring API

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `captureSnapshot()` | string $label, array $variables | void | Captures a snapshot of variable state. Auto-registers breakpoint on first call. |
| `pollBreakpoints()` | none | void | Polls the server for active breakpoints. Call on a percentage of requests or via cron. |

### End-to-End Workflow

1. **Enable Code Monitoring** -- Set `'code_monitoring_enabled' => true` in the TracekitClient config.
2. **Add Capture Points** -- Place `captureSnapshot` calls at code paths you want to debug.
3. **Set Up Polling** -- Call `pollBreakpoints()` on a percentage of requests or via cron.
4. **Deploy and Verify** -- Deploy and check traces at [/traces](https://app.tracekit.dev/traces).
5. **Navigate to Code Monitoring** -- Go to [/snapshots](https://app.tracekit.dev/snapshots) to view captured data.
6. **Set Breakpoints** -- Breakpoints are auto-registered on first `captureSnapshot` call.
7. **View Snapshot** -- Click a captured snapshot to view variables, call stack, and request context.

### v25: Enhanced Code Monitoring

#### Log-Point Mode

When a breakpoint is set to **log-point mode** in the dashboard, the SDK captures only the specified expressions -- no local variables, stack traces, or request context. This dramatically reduces overhead for high-frequency code paths.

No code changes needed. The SDK reads the breakpoint's `mode` field from the server and adjusts capture behavior automatically.

#### SDK-Side Condition Evaluation

Breakpoint conditions (e.g., `status > 200`) are now evaluated **locally in the SDK** without a server round-trip. The server classifies each condition as `sdk-evaluable` or `server-only` at creation time. Simple expressions (comparisons, boolean logic, property access) are evaluated in-process.

No code changes needed. The SDK checks the `condition_eval` field and evaluates locally when possible.

#### Per-Breakpoint Capture Limits

New breakpoints default to:
- **max_depth: 5** -- variables nested deeper than 5 levels are truncated
- **max_payload_bytes: 128KB** -- snapshots larger than 128KB are truncated

These limits are configurable per breakpoint in the dashboard. The SDK respects per-breakpoint overrides automatically.

#### Auto-Expire

New breakpoints **auto-expire after 48 hours of inactivity**. Each capture resets the timer. Pin a breakpoint in the dashboard to prevent auto-expiry.

Existing breakpoints are not affected -- auto-expire only applies to breakpoints created after the v25 update.

## Framework Integration

### Symfony

Create an event listener to automatically trace all HTTP requests:

```php
<?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
<?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
<?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();
```

## LLM Instrumentation

TraceKit automatically instruments OpenAI and Anthropic API calls when detected. LLM calls appear as spans with [OTel GenAI semantic convention](https://opentelemetry.io/docs/specs/semconv/gen-ai/) attributes.

**Zero-config auto-instrumentation** -- If your PHP app uses Guzzle for OpenAI or Anthropic API calls, TraceKit instruments them automatically via middleware. No code changes required.

### Captured Attributes

| Attribute | Description |
|-----------|-------------|
| `gen_ai.system` | Provider name (openai, anthropic) |
| `gen_ai.request.model` | Model name (gpt-4o, claude-sonnet-4-20250514, etc.) |
| `gen_ai.usage.input_tokens` | Prompt token count |
| `gen_ai.usage.output_tokens` | Completion token count |
| `gen_ai.response.finish_reasons` | Finish reason (stop, end_turn, tool_calls) |

### Configuration

```php
$client = new TracekitClient([
    'apiKey' => getenv('TRACEKIT_API_KEY'),
    'serviceName' => 'my-php-service',
    'llm' => [
        'enabled' => true,        // Master toggle
        'openai' => true,         // OpenAI instrumentation
        'anthropic' => true,      // Anthropic instrumentation
        'captureContent' => false, // Capture prompts/completions (off by default)
    ],
]);
```

### Environment Variable Override

Use `TRACEKIT_LLM_CAPTURE_CONTENT=true` to enable prompt/completion capture without code changes. Useful for enabling in staging but not production.

### Streaming Support

Streaming responses produce a single span that covers the entire stream. Token counts are accumulated from the final chunk. No special configuration needed.

### LLM Dashboard

View LLM cost, token usage, and latency metrics on the dedicated **LLM Observability** dashboard at `/ai/llm` in your TraceKit instance.

## 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:**

```bash
npm install -g @tracekit/local-ui
```

**2. Start the Local UI:**

```bash
tracekit-local
```

**3. Run your app in development mode:**

```bash
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 Local UI** -- If traces aren't appearing, 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

```php
<?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"
```

### Service Name Detection

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

**Viewing Service Dependencies** -- Visit your TraceKit dashboard to see the [Service Map](https://app.tracekit.dev/services) -- a visual graph showing which services call which, with health metrics and latency data.

## Manual Instrumentation

### Database Queries

```php
<?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
<?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
<?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;
    }
}
```

### Tracing API Reference

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `extractTraceparent($headers)` | array $headers | ContextInterface | Extracts W3C Trace Context from incoming request headers for distributed tracing. |
| `startServerSpan($op, $attrs, $ctx)` | string $operationName, array $attributes = [], ?ContextInterface $parentContext = null | array | Creates a SERVER span with optional parent context. |
| `addEvent($spanData, $name, $attrs)` | array $spanData, string $name, array $attributes = [] | void | Records a timestamped event on the current span. |

### Distributed Tracing Example

Receive trace context from an upstream service and create linked spans:

```php
<?php

// Extract trace context from incoming headers
$parentContext = $tracekit->extractTraceparent(getallheaders());

// Create a server span linked to the upstream trace
$span = $tracekit->startServerSpan('handle-payment', [
    'payment.amount' => $amount,
], $parentContext);

// Your logic...
$tracekit->endSpan($span);
$tracekit->flush();
```

### Lifecycle API Reference

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `getTracer()` | none | TracerInterface | Returns the underlying OpenTelemetry Tracer for advanced instrumentation. |
| `shouldSample()` | none | bool | Returns whether the current request should be sampled based on sample_rate. |

## Environment Variables

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

### Configuration Reference

| Option | Type | Default | Env Variable | Description |
|--------|------|---------|-------------|-------------|
| `api_key` | string | "" | TRACEKIT_API_KEY | Your TraceKit API key for authentication |
| `service_name` | string | "php-app" | TRACEKIT_SERVICE_NAME | Name of your service in the trace dashboard |
| `endpoint` | string | "app.tracekit.dev" | TRACEKIT_ENDPOINT | TraceKit collector endpoint URL |
| `traces_path` | string | "/v1/traces" | TRACEKIT_TRACES_PATH | HTTP path for trace data export |
| `metrics_path` | string | "/v1/metrics" | TRACEKIT_METRICS_PATH | HTTP path for metrics data export |
| `enabled` | bool | true | TRACEKIT_ENABLED | Enable or disable tracing globally |
| `sample_rate` | float | 1.0 | TRACEKIT_SAMPLE_RATE | Trace sampling rate (0.0 to 1.0) |
| `service_name_mappings` | array | [] | -- | Map host:port to service names for service discovery |
| `code_monitoring_enabled` | bool | false | TRACEKIT_CODE_MONITORING_ENABLED | Enable live code debugging / snapshot capture |
| `code_monitoring_max_depth` | int | 3 | TRACEKIT_CODE_MONITORING_MAX_DEPTH | Max variable inspection depth for snapshots |
| `code_monitoring_max_string` | int | 1000 | TRACEKIT_CODE_MONITORING_MAX_STRING | Max string length captured in snapshots |
| `suppress_errors` | bool | true | TRACEKIT_SUPPRESS_ERRORS | Suppress SDK errors to prevent app crashes |

The PHP SDK does not auto-read environment variables. Read them with `getenv()` and pass to the TracekitClient constructor.

## Configuration Options

Customize the TracekitClient with these configuration options:

```php
<?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):

```php
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):

```php
// 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):

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

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

[View Custom Metrics Guide](/docs/metrics)

## Troubleshooting

### Traces not appearing?

**Cause:** PHP scripts terminate after each request; unflushed spans are lost.

**Fix:**
- Call `$client->flush()` before script termination or in a shutdown handler: `register_shutdown_function([$client, 'flush'])`
- Verify API key is correct
- Check that `enabled` is `true` in the config array

### Composer dependency issues?

**Cause:** PHP version or package version conflicts prevent installation.

**Fix:**
- Verify PHP 8.1+: `php -v`
- Update Composer: `composer self-update`
- Clear cache: `composer clear-cache`
- If version conflicts persist, try `composer update --with-all-dependencies`

### Code monitoring not working?

**Cause:** Code monitoring is disabled by default in the PHP SDK.

**Fix:**
- Set `'code_monitoring_enabled' => true` in the config array
- Add `$client->captureSnapshot('label')` calls
- Note that `code_monitoring_max_depth` (default 3) limits captured variable nesting -- increase if needed

### Authentication errors (401/403)?

**Cause:** API key is missing, empty, or contains whitespace.

**Fix:**
- Verify with `echo getenv('TRACEKIT_API_KEY');`
- The PHP SDK defaults api_key to empty string -- passing no key silently fails
- Regenerate in dashboard if expired

### Connection refused errors?

**Cause:** The TraceKit endpoint is unreachable or `suppress_errors` is hiding the real error.

**Fix:**
- Temporarily set `'suppress_errors' => false` to see full OTel error output
- Test with `curl -X POST https://app.tracekit.dev/v1/traces` (expect 401)
- Check php.ini for `allow_url_fopen` if using HTTP transport

## Migrating from OpenTelemetry

If you are currently using OpenTelemetry directly, TraceKit wraps it with a simpler API. Replace your OTel setup with the TracekitClient and remove manual exporter configuration. TraceKit handles the OTel pipeline internally.

## Performance Overhead

TraceKit is built on OpenTelemetry's efficient batch processing pipeline. The SDK adds minimal overhead to your PHP application.

| Metric | Impact | Details |
|--------|--------|---------|
| Request Tracing | < 1ms per request | Spans are batched and exported asynchronously |
| Code Monitoring (Idle) | Zero overhead | No performance impact when no active breakpoints |
| Code Monitoring (Capture) | < 5ms per snapshot | Includes variable serialization and security scanning |
| Memory Footprint | ~5-10 MB | SDK runtime and span buffer |

Performance characteristics are typical for production workloads and may vary with application complexity and request volume. Use sampling configuration to reduce overhead in high-traffic services.

## Best Practices

- **DO: Always call `flush()` before script exit** -- PHP's process-per-request model means unflushed spans are lost. Use `register_shutdown_function([$client, 'flush'])` as a safety net.
- **DO: Use environment variables for configuration** -- Keep API keys, endpoints, and service names in environment variables.
- **DO: Poll breakpoints on a percentage of requests** -- Use `rand(1, 20) === 1` to poll on ~5% of requests for code monitoring.
- **DON'T: Forget to initialize before tracing** -- The TracekitClient must be created before any span operations.
- **DON'T: Capture sensitive data in snapshots** -- Use the built-in PII scrubbing or be selective about what variables you capture.

## Next Steps

- [View Your Traces](https://app.tracekit.dev/traces) -- Explore distributed traces
- [Set Up Alerts](/docs/alerts) -- Get notified of issues
- Add manual spans for database queries and external API calls
- Configure sampling rates for high-traffic applications
