# Laravel Integration Guide

> Integrate TraceKit with Laravel for automatic request tracing, dynamic logs, and production debugging. See database queries, API calls, and exceptions in real-time.

- Canonical: https://tracekit.dev/docs/frameworks/laravel

Learn how to instrument your Laravel applications with TraceKit APM for zero-config distributed tracing and performance monitoring.

## Zero-Config Automatic Tracing

TraceKit Laravel package automatically traces HTTP requests, database queries, queue jobs, cache operations, and more with minimal setup. Just install and configure your API key!

## Prerequisites

Before you begin, you need:

- PHP 8.1 or higher
- Laravel 10.x, 11.x, or 12.x
- 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

With TraceKit Laravel package installed, these operations are traced automatically with zero configuration:

| Component | Span Type | Captured Attributes | Auto-Traced? |
| --- | --- | --- | --- |
| HTTP Requests | SERVER | Route, method, status, duration | Yes |
| Database Queries | DB | SQL, bindings, duration, connection | Yes |
| Outgoing HTTP Calls | CLIENT | method, url, status_code, duration, peer.service | Yes (via Http facade) |
| Queue Jobs | Custom | job.class, queue, attempts, duration | Yes |
| Exceptions | N/A | exception.type, exception.message, stack_trace | Yes |
| Custom Spans | Custom | user-defined attributes | Manual |

## Installation

Install the TraceKit Laravel package via Composer:

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

**Package Autodiscovery** -- The service provider is automatically registered via Laravel's package discovery. No need to manually register it!

## Configuration

### 1. Publish Configuration (Optional)

If you want to customize settings, publish the configuration file:

```bash
php artisan vendor:publish --provider="TraceKit\Laravel\TracekitServiceProvider"
```

This creates `config/tracekit.php` where you can customize settings.

### 2. Add API Key to .env

Add your TraceKit API key to your `.env` file:

```bash
# TraceKit APM Configuration
TRACEKIT_API_KEY=ctxio_your_generated_api_key_here
TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
TRACEKIT_SERVICE_NAME=my-laravel-app
TRACEKIT_ENABLED=true
```

**Important Security Note** -- Never commit your API key to version control. Keep it in your `.env` file and use environment-specific values.

### 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 (auto-traced by middleware)
- DB spans for Eloquent and query builder queries (auto-traced by QueryListener)
- CLIENT spans for outgoing HTTP calls via the Http facade

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

## Laravel 12+ Middleware Setup

For Laravel 12+, middleware registration has changed. The package attempts automatic registration, but if needed, manually add the middleware to `bootstrap/app.php`:

```php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use TraceKit\Laravel\Middleware\TracekitMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            TracekitMiddleware::class,
        ]);
        $middleware->api(append: [
            TracekitMiddleware::class,
        ]);
    })
    // ... rest of your configuration
    ->create();
```

**Laravel 10 & 11** -- For Laravel 10 and 11, the middleware is registered automatically via the service provider. No manual configuration needed!

## Environment Variables

Available configuration options in `config/tracekit.php`:

```php
<?php

return [
    // Enable/disable tracing
    'enabled' => env('TRACEKIT_ENABLED', env('APP_ENV') !== 'local'),

    // Your TraceKit API key
    'api_key' => env('TRACEKIT_API_KEY', ''),

    // OTLP endpoint for sending traces
    'endpoint' => env('TRACEKIT_ENDPOINT', 'https://app.tracekit.dev/v1/traces'),

    // Service name as it appears in TraceKit
    'service_name' => env('TRACEKIT_SERVICE_NAME', env('APP_NAME', 'laravel-app')),

    // Sample rate (0.0 to 1.0)
    'sample_rate' => env('TRACEKIT_SAMPLE_RATE', 1.0),

    // Enable/disable specific features
    'features' => [
        'http' => env('TRACEKIT_HTTP_ENABLED', true),
        'database' => env('TRACEKIT_DATABASE_ENABLED', true),
        'cache' => env('TRACEKIT_CACHE_ENABLED', true),
        'queue' => env('TRACEKIT_QUEUE_ENABLED', true),
        'redis' => env('TRACEKIT_REDIS_ENABLED', true),
    ],

    // Routes to ignore
    'ignored_routes' => [
        '/health',
        '/up',
        '/_healthz',
    ],

    // Slow query threshold (ms)
    'slow_query_threshold' => env('TRACEKIT_SLOW_QUERY_MS', 100),

    // Include query bindings in traces
    'include_query_bindings' => env('TRACEKIT_INCLUDE_BINDINGS', true),
];
```

### Configuration Reference

| Option | Type | Default | Env Variable | Description |
| --- | --- | --- | --- | --- |
| `api_key` | string | "" | `TRACEKIT_API_KEY` | Your TraceKit API key for authentication |
| `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 |
| `service_name` | string | APP_NAME or "laravel-app" | `TRACEKIT_SERVICE_NAME` | Name of your service in the trace dashboard |
| `enabled` | bool | true (non-local) | `TRACEKIT_ENABLED` | Enable or disable tracing globally |
| `sample_rate` | float | 1.0 | `TRACEKIT_SAMPLE_RATE` | Trace sampling rate (0.0 to 1.0, where 1.0 = 100%) |
| `features.http` | bool | true | `TRACEKIT_HTTP_ENABLED` | Auto-trace incoming HTTP requests |
| `features.database` | bool | true | `TRACEKIT_DATABASE_ENABLED` | Auto-trace Eloquent and DB queries |
| `features.cache` | bool | true | `TRACEKIT_CACHE_ENABLED` | Auto-trace cache operations (Redis, Memcached, file) |
| `features.queue` | bool | true | `TRACEKIT_QUEUE_ENABLED` | Auto-trace queued jobs and events |
| `features.redis` | bool | true | `TRACEKIT_REDIS_ENABLED` | Auto-trace Redis commands |
| `slow_query_threshold` | int | 100 | `TRACEKIT_SLOW_QUERY_MS` | Threshold in ms to flag slow queries |
| `include_query_bindings` | bool | true | `TRACEKIT_INCLUDE_BINDINGS` | Include query parameter bindings in trace data |
| `code_monitoring.enabled` | bool | false | `TRACEKIT_CODE_MONITORING_ENABLED` | Enable dynamic log snapshot capture |
| `code_monitoring.poll_interval` | int | 30 | `TRACEKIT_CODE_MONITORING_POLL_INTERVAL` | How often to poll for active breakpoints (seconds) |
| `code_monitoring.max_variable_depth` | int | 3 | `TRACEKIT_CODE_MONITORING_MAX_DEPTH` | Max variable inspection depth for snapshots |
| `code_monitoring.max_string_length` | 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 |
| `service_name_mappings` | array | [] | code only | Map host:port to service names for service discovery |
| `ignored_routes` | array | ['/health', '/up', '/_healthz'] | code only | Routes excluded from tracing (health checks, etc.) |

Laravel auto-reads all TRACEKIT_* environment variables via the `env()` helper. Add them to your `.env` file and they take effect immediately (run `php artisan config:clear` if config is cached).

## Code Monitoring (Dynamic Logs)

**Production-Safe Dynamic Logs** -- Capture variable state, stack traces, and request context at any point in your code without redeployment. Perfect for debugging production issues! See the full [Code Monitoring Documentation](/docs/code-monitoring).

### Setup

To enable code monitoring for dynamic logs, add these settings to your `.env`:

```bash
# Enable code monitoring
TRACEKIT_CODE_MONITORING_ENABLED=true

# Polling interval (seconds)
# - 1-5: Development (fast updates, higher load)
# - 10-30: Production (recommended)
# - 60+: Low traffic/minimal overhead
TRACEKIT_CODE_MONITORING_POLL_INTERVAL=30

# Variable inspection depth (1-10)
TRACEKIT_CODE_MONITORING_MAX_DEPTH=3

# String truncation length
TRACEKIT_CODE_MONITORING_MAX_STRING=1000
```

### Usage: Add Debug Points

Use the `tracekit_snapshot()` helper to capture variable state:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function processOrder(Request $request)
    {
        $userId = $request->user()->id;
        $cartTotal = $request->input('cart_total');

        // Capture variable state at checkout start
        tracekit_snapshot('checkout-start', [
            'user_id' => $userId,
            'cart_total' => $cartTotal,
            'items_count' => $request->input('items', [])->count(),
        ]);

        try {
            $order = $this->createOrder($request->all());
            $payment = $this->processPayment($order);

            // Capture successful payment state
            tracekit_snapshot('payment-success', [
                'order_id' => $order->id,
                'payment_id' => $payment->id,
                'amount' => $payment->amount,
            ]);

            return response()->json(['order' => $order], 201);
        } catch (\Exception $e) {
            // Exception automatically captured with stack trace!
            throw $e;
        }
    }
}
```

### Features

- **Auto-Registration** -- Breakpoints automatically created on first call
- **Variable Capture** -- Deep inspection of arrays, objects, and primitives
- **Stack Traces** -- Full call stack with file and line numbers
- **Request Context** -- HTTP method, route, headers, and query params

### Production Safety

The Laravel SDK includes multiple layers of protection:

- **PII Scrubbing** -- 13 built-in sensitive data patterns enabled by default. Detected values are replaced with typed `[REDACTED:type]` markers before leaving your server.
- **Crash Isolation** -- All entry points wrap operations in `catch(\Throwable)` blocks. SDK errors are silently logged and never propagate to your application.
- **Circuit Breaker** -- 3 consecutive failures within 60 seconds triggers a 5-minute cooldown. Uses Laravel's `Cache` facade for cross-request persistence.
- **Remote Kill Switch** -- Toggle code monitoring on/off from the dashboard. Propagates instantly via SSE for Artisan commands and queue workers, or via polling for web requests.

**Real-Time Updates** -- The Laravel SDK uses `app()->runningInConsole()` to detect the execution context. Long-lived Artisan commands and queue workers receive kill switch and breakpoint updates via SSE (Server-Sent Events) for instant propagation. Web requests use lightweight polling on each snapshot call since PHP processes are short-lived.

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

## 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** -- The Laravel SDK auto-registers LLM instrumentation via the service provider. If your app makes OpenAI or Anthropic API calls via HTTP, TraceKit captures them automatically through event listeners.

### 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 title="config/tracekit.php"
// config/tracekit.php
return [
    'api_key' => env('TRACEKIT_API_KEY'),
    'service_name' => env('APP_NAME', 'my-laravel-app'),
    'llm' => [
        'enabled' => true,
        'openai' => true,
        'anthropic' => true,
        'capture_content' => false,
    ],
];
```

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 Laravel 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 local environment.

**Automatic Detection** -- The Laravel 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 (Laravel automatically uses APP_ENV=local):

```bash
php artisan serve
```

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 `APP_ENV=local`
- **Works Offline** -- No internet connection required

## Service Discovery

TraceKit automatically instruments outgoing HTTP calls made with Laravel's HTTP client to create service dependency graphs.

### Usage with Laravel HTTP Client

Just use Laravel's HTTP client as normal -- everything is traced automatically:

```php
<?php

use Illuminate\Support\Facades\Http;

// All HTTP calls are automatically traced!
Route::post('/checkout', function (Request $request) {
    // This creates a CLIENT span for the payment-service call
    $paymentResponse = Http::post('http://payment-service/charge', [
        'amount' => $request->input('amount'),
        'user_id' => auth()->id(),
    ]);

    // This creates another CLIENT span for the inventory-service call
    $inventoryResponse = Http::post('http://inventory-service/reserve', [
        'item_id' => $request->input('item_id'),
    ]);

    return response()->json([
        'status' => 'success',
        'payment_id' => $paymentResponse['payment_id'],
    ]);
});
```

### Custom Service Name Mappings

For local development or when service names can't be inferred from hostnames, configure service name mappings in `config/tracekit.php`:

```php
// config/tracekit.php
return [
    // ... other config

    // 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" in your service graph
// Http::get('http://localhost:8082/charge');
// -> Creates CLIENT span with peer.service = "payment-service"
```

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

**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 (Optional)

For custom business logic or specific operations you want to measure, you can manually create spans:

```php
<?php

use TraceKit\Laravel\TracekitClient;

class OrderController extends Controller
{
    public function processOrder(Request $request, TracekitClient $tracekit)
    {
        // Create a custom span
        $span = $tracekit->startSpan('process-order', null, [
            'order.id' => $request->input('order_id'),
            'user.id' => auth()->id(),
        ]);

        try {
            $order = $this->createOrder($request->all());
            $this->processPayment($order);
            $this->sendConfirmation($order);

            $tracekit->endSpan($span, [
                'order.total' => $order->total,
                'order.items_count' => $order->items()->count(),
            ]);

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

### Tracekit Facade API

| Facade Method | Parameters | Returns | Description |
| --- | --- | --- | --- |
| `Tracekit::startSpan($op, $attrs)` | string $operationName, array $attributes = [] | array | Start a new span via the Facade. Returns span data array. |
| `Tracekit::endSpan($data, $attrs, $status)` | array $spanData, array $attributes = [], ?string $status = null | void | End a span and optionally set additional attributes and status. |
| `Tracekit::captureSnapshot($label, $vars)` | string $label, array $variables = [] | void | Capture a code monitoring snapshot with variable state at this point. |
| `Tracekit::counter($name, $tags)` | string $name, array $tags = [] | Counter | Create or retrieve a counter metric via the Facade. |
| `Tracekit::gauge($name, $tags)` | string $name, array $tags = [] | Gauge | Create or retrieve a gauge metric via the Facade. |
| `Tracekit::histogram($name, $tags)` | string $name, array $tags = [] | Histogram | Create or retrieve a histogram metric via the Facade. |

The Tracekit Facade proxies all TracekitClient methods. Use it anywhere in your Laravel application without injecting the client directly.

## Complete Example

Here's a complete example showing automatic and manual tracing:

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use TraceKit\Laravel\TracekitClient;

class UserController extends Controller
{
    // HTTP requests are automatically traced!
    public function index()
    {
        // Database queries are automatically traced!
        $users = User::where('active', true)->get();

        return response()->json($users);
    }

    // You can add custom spans for specific operations
    public function processUserData(Request $request, TracekitClient $tracekit)
    {
        // Start custom span
        $span = $tracekit->startSpan('process-user-data', null, [
            'user.count' => $request->input('user_ids')->count(),
        ]);

        try {
            // Your business logic here
            $results = [];
            foreach ($request->input('user_ids') as $userId) {
                $user = User::find($userId);
                $results[] = $this->processUser($user);
            }

            $tracekit->endSpan($span, [
                'results.count' => count($results),
            ]);

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

    private function processUser($user)
    {
        // Database queries here are also automatically traced!
        $user->last_processed_at = now();
        $user->save();

        return $user;
    }
}
```

## 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\Laravel\Facades\Tracekit;

// Create a counter with optional tags
$counter = Tracekit::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 = Tracekit::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 = Tracekit::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:** Laravel config caching can hide .env changes, or the TraceKit service provider is not loaded.

**Fix:**
- Clear config cache: `php artisan config:clear`
- Verify `TRACEKIT_ENABLED=true` in `.env`
- Check that `TRACEKIT_API_KEY` is set
- Review `storage/logs/laravel.log` for export errors
- Ensure the TraceKit service provider is in your providers list (should be auto-discovered)

### Package not auto-discovered?

**Cause:** Laravel's package auto-discovery cache is stale or the package was installed with `--no-scripts`.

**Fix:**
- Run `php artisan package:discover` followed by `php artisan config:clear`
- Verify the provider appears in `bootstrap/cache/packages.php`
- If still missing, manually add `TraceKit\Laravel\TracekitServiceProvider::class` to config/app.php providers array

### Code monitoring not working?

**Cause:** Code monitoring is disabled by default in Laravel.

**Fix:**
- Set `TRACEKIT_CODE_MONITORING_ENABLED=true` in `.env`
- Run `php artisan config:clear`
- Add `captureSnapshot()` calls via the Tracekit facade: `Tracekit::captureSnapshot('label')`
- Wait for the poll interval (default 30s) before expecting results

### Authentication errors (401/403)?

**Cause:** API key in .env is wrong, has quotes wrapping it, or config cache is stale.

**Fix:**
- In `.env`, use `TRACEKIT_API_KEY=ctxio_xxx` (no quotes around value)
- Run `php artisan config:clear`
- Verify with `php artisan tinker` then `config('tracekit.api_key')`
- Regenerate key in dashboard if needed

### Database or queue traces missing?

**Cause:** Specific feature flags in the TraceKit config are disabled.

**Fix:**
- Verify feature flags in `.env`: `TRACEKIT_DATABASE_ENABLED=true`, `TRACEKIT_QUEUE_ENABLED=true`, `TRACEKIT_CACHE_ENABLED=true`
- Check that the `slow_query_threshold` (default 100ms) is not filtering out fast queries you expect to see
- Review ignored_routes in config/tracekit.php to ensure your routes are not excluded

## Next Steps

- Explore your traces on the [Traces page](https://app.tracekit.dev/traces) to identify slow queries and performance bottlenecks
- Configure [alert rules](/docs/alerts) to get notified when issues occur
- Add custom spans for specific business logic you want to measure
- Adjust sampling rates for high-traffic applications

**Pro Tip** -- Enable query bindings to see actual SQL values in traces. Just set `TRACEKIT_INCLUDE_BINDINGS=true` in your .env file. Perfect for debugging!
