PHPackages                             lookout/tracing - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. lookout/tracing

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

lookout/tracing
===============

Distributed tracing, sampled performance spans (limits, Laravel collectors), optional browser RUM beacons, breadcrumbs, structured logs and custom metrics ingest, exception reporting with enrichment pipeline, cron check-ins, and profiling ingest for Lookout.

2.14.0(1mo ago)0273MITPHPPHP ^8.3

Since Mar 28Pushed 1mo agoCompare

[ Source](https://github.com/shaferllc/lookout-tracing)[ Packagist](https://packagist.org/packages/lookout/tracing)[ RSS](/packages/lookout-tracing/feed)WikiDiscussions main Synced 2d ago

READMEChangelogDependencies (27)Versions (21)Used By (0)

lookout/tracing
===============

[](#lookouttracing)

PHP library for **Lookout** distributed tracing: compact **traceparent**-style propagation, **W3C baggage**, manual transactions/spans, and optional Laravel integration. Wire formats stay compatible with common PHP tracing clients without naming third-party vendors here.

**Free dashboard:** [Create a free Starter account](https://uselookout.app/register) (no credit card) — one project, thousands of events/month. Copy your project API key from Settings and view grouped errors in the web UI.

Install
-------

[](#install)

Requirements: **PHP 8.3+** and **Composer**. You always add the library with Composer; only Laravel gets auto-wiring via a service provider.

### Laravel application

[](#laravel-application)

Run these from your **Laravel project root** (the directory that contains `artisan` and `composer.json`):

1. **Add the package**

    ```
    cd /path/to/your-laravel-app
    composer require lookout/tracing
    ```

    Composer updates `composer.json` / `composer.lock` and downloads `lookout/tracing` into `vendor/`.
2. **Configure Lookout (pick one path)**

    - **Interactive (recommended):** `php artisan lookout:install` — choose **Create a new project** (Lookout web URL + your **API token** from Profile → **API tokens**) or **Use an existing DSN**. Create flow calls Lookout’s API (`GET /api/v1/me`, `POST /api/v1/projects`), then writes **`LOOKOUT_DSN`** using the new project’s ingest key. It also appends `LOOKOUT_LARAVEL=true` to `.env`.
    - **Manual:** add to `.env` yourself (see [Quick install](#quick-install) under **Laravel** below), or set `LOOKOUT_API_KEY` + `LOOKOUT_URL` if your team shares one Lookout host.
3. **Clear config cache** (if you use it in this environment)

    ```
    php artisan config:clear
    ```
4. **Optional — publish config** when you need every env knob (sampling, middleware, log/metrics toggles):

    ```
    php artisan vendor:publish --tag=lookout-tracing-config
    ```
5. **Tracing middleware** — for distributed traces, register `lookoutTracing.continueTrace` (and optionally `lookoutTracing.performance`) on your `web` / `api` stacks, or use `LOOKOUT_PERFORMANCE_AUTO_MIDDLEWARE=true` for the performance middleware only (see [Performance monitoring](#performance-monitoring-traces--spans)).

Laravel **auto-discovers** `Lookout\Tracing\Laravel\LookoutTracingServiceProvider`; you do not add it to `config/app.php` manually.

### Other PHP projects (Symfony, Slim, custom apps, libraries)

[](#other-php-projects-symfony-slim-custom-apps-libraries)

There is **no** Laravel service provider. Install the same Composer package and **configure the tracer in your bootstrap** (or a DI container):

```
cd /path/to/your-php-project
composer require lookout/tracing
```

Then wire **Lookout ingest** explicitly, for example:

```
use Lookout\Tracing\Tracer;

Tracer::instance()->configure([
    'api_key' => getenv('LOOKOUT_API_KEY') ?: null,
    'base_uri' => rtrim((string) getenv('LOOKOUT_URL'), '/') ?: null,
    'environment' => getenv('APP_ENV') ?: null,
]);
```

Use **`Tracer`**, **`Tracing`**, **`lookout_logger()`**, **`lookout_metrics()`**, PSR-15 middleware (e.g. `ContinueTracePsr15Middleware`), and **`GuzzleTraceMiddleware`** as needed; see **Propagation**, **Custom instrumentation**, **Lookout ingest**, and **Guzzle 7** below. For Slim / Mezzio, see the `slim/slim` suggestion in `composer.json`.

(This monorepo vendors the package from `packages/lookout-tracing` via a Composer path repository when developing Lookout itself.)

Propagation
-----------

[](#propagation)

- **Incoming:** parse the compact trace header (see `Lookout\Tracing\TraceWireHeaders::HTTP_TRACEPARENT`) and `baggage` (e.g. from `PSR-7` request headers or Laravel’s `Request`).
- **Outgoing:** add the same headers to downstream HTTP calls so other services can continue the trace.

```
use Lookout\Tracing\Tracer;
use Lookout\Tracing\TraceWireHeaders;

Tracer::instance()->continueTrace(
    $request->getHeaderLine(TraceWireHeaders::HTTP_TRACEPARENT),
    $request->getHeaderLine(TraceWireHeaders::HTTP_BAGGAGE),
);

$headers = Tracer::instance()->outgoingTraceHeaders();
// keys: TraceWireHeaders::HTTP_TRACEPARENT, TraceWireHeaders::HTTP_BAGGAGE
```

HTML meta tags for browser SDKs:

```
use Lookout\Tracing\HtmlTraceMeta;

echo HtmlTraceMeta::render();
```

Custom instrumentation
----------------------

[](#custom-instrumentation)

```
use Lookout\Tracing\SpanOperation;
use Lookout\Tracing\Tracing;

$tx = Tracing::startTransaction('GET /orders', SpanOperation::HTTP_SERVER);

Tracing::trace(function () {
    // …
}, SpanOperation::HTTP_CLIENT, 'GET https://api.example.com/v1/orders');

$tx->finish();
```

Common `op` values are defined on `Lookout\Tracing\SpanOperation` (`http.server`, `http.client`, `db.query`, `cache.get`, `queue.publish`, etc.).

Lookout ingest
--------------

[](#lookout-ingest)

- **`Tracer::errorIngestTraceFields()`** — `trace_id`, `span_id`, `parent_span_id`, `transaction` for your error JSON body to `POST /api/ingest`.
- **`Tracer::errorIngestPerformanceGroupingHints()`** — when **`reporting.performance_grouping.enabled`** is true (env **`LOOKOUT_REPORT_PERFORMANCE_GROUPING`**) and **`performance_enabled`** recorded spans in the same request, may add **`grouping_slow_path`** and **`grouping_db_time_ms`** so Lookout can fingerprint slow / DB-heavy errors separately (see Lookout ingest docs).
- **`Tracer::configure([...])`** + **`Tracer::flush()`** — send finished spans to `POST /api/ingest/trace` (set `api_key`, `base_uri`, optional `environment` / `release`). Use **`Tracer::flushWithResult()`** (or **`Tracing::flushWithResult()`**) when you need the HTTP **status** (e.g. **403** if the Lookout project disabled trace ingest).

Structured logs
---------------

[](#structured-logs)

**`lookout_logger()->info('User %s logged in', ['alice'])`**, optional **`flush()`**, and a **Monolog** handler. Rows go to **`POST /api/ingest/log`** with the same **`api_key`** / **`base_uri`** as tracing; enabled from the dashboard (**Monitoring → Signals**), with **`LOOKOUT_LOGS_ENABLED`** as a local override (env &gt; site). Laravel registers a **terminating** flush when **`logging.enabled`** and **`logging.flush_on_terminate`** are true. Long workers should call **`lookout_logger()->flush()`** on a timer or after batches.

```
lookout_logger()->info('order placed', null, ['order_id' => '42']);
lookout_logger()->flush();
```

```
use Lookout\Tracing\Logging\Monolog\LookoutMonologHandler;
use Monolog\Logger;

$log = new Logger('app');
$log->pushHandler(new LookoutMonologHandler());
```

Custom metrics
--------------

[](#custom-metrics)

**`lookout_metrics()->count('orders.completed', 1)`**, **`gauge()`**, **`distribution()`**, optional **`MetricUnit`**, and **`flush()`**. Samples go to **`POST /api/ingest/metric`**; the active **`trace_id`** is attached when a transaction is in flight so the Lookout UI can correlate rollups with traces. Enabled from the dashboard (**Monitoring → Signals**), with **`LOOKOUT_METRICS_ENABLED`** as a local override (env &gt; site). Laravel flushes on **terminating** when **`metrics.enabled`** and **`metrics.flush_on_terminate`** are true.

Optional **`MetricsIngestClient::configure(['before_send_metric' => fn (array $row): ?array => $row])`** drops or mutates rows before enqueue (return **`null`** to skip).

```
use Lookout\Tracing\Metrics\MetricUnit;

lookout_metrics()->count('button.click', 5, ['plan' => 'pro']);
lookout_metrics()->distribution('page.load_ms', 42.5, ['route' => '/checkout'], MetricUnit::millisecond());
lookout_metrics()->flush();
```

### Real User Monitoring (browser)

[](#real-user-monitoring-browser)

Optional **Web Vitals** + **SPA / Livewire** navigation beacons: `POST /api/ingest/rum` (same project API key; **performance ingest** must be enabled on the project). Vanilla script with no npm dependencies:

- **`resources/rum/lookout-rum.js`** — `LookoutRum.init({ endpoint, apiKey, livewireNavigate: true, traceId: () => … })`. Puts **`api_key` in the JSON body** so **`navigator.sendBeacon`** works without custom headers. Correlate with server traces via **`trace_id`** (32 hex), e.g. from **`HtmlTraceMeta`** / a `` you render from `Tracer::instance()->traceId` on the server.

Error reporting client
----------------------

[](#error-reporting-client)

Uncaught exceptions use **`Lookout\Tracing\Reporting\ErrorReportClient`**: middleware enriches the payload (Laravel + HTTP context, git metadata, `context.attributes` from **`Lookout\Tracing\Reporting\ReportScope`** and configurable **`AttributeProviderInterface`** classes, optional **`client_solutions`** strings), then **`ReportTruncator`** enforces Lookout size limits, optional **`ReportSampler`** drops a random fraction, and the payload is POSTed immediately or **queued** and flushed on **shutdown** (`reporting.queue` / `reporting.send_immediately`).

### Glows (Flare-style manual breadcrumbs)

[](#glows-flare-style-manual-breadcrumbs)

Similar in spirit to [Flare Laravel glows](https://flareapp.io/docs/laravel/data-collection/glows): **custom timeline notes** that appear with other **breadcrumbs** on the error in Lookout (chronological “what ran before this failed”).

```
use Lookout\Tracing\GlowBreadcrumb;

GlowBreadcrumb::glow('Payment branch: validated wallet', 'info', ['wallet_id' => $id]);
GlowBreadcrumb::glow('Skipping cache (feature flag)', 'debug');
```

- **`$message`** — required; trimmed, max length enforced with other breadcrumbs.
- **`$level`** — string such as `debug`, `info`, `warning`, `error` (default `info`).
- **`$data`** — optional associative array (subject to the same redaction as other breadcrumb payloads).

Internally these are breadcrumbs with **`type`** `glow` and **`category`** `glow`. They are **not** the Spatie **`Flare::glow()`** API—there is no drop-in facade. They attach to the **error ingest** breadcrumb list, not as separate **span events** on traces (Flare also shows glows on spans in performance; Lookout’s buffer is scoped to the next error report).

### Manual filesystem breadcrumbs

[](#manual-filesystem-breadcrumbs)

For disk I/O there is no universal Laravel hook; use **`FilesystemBreadcrumb::record()`**:

```
use Lookout\Tracing\FilesystemBreadcrumb;

FilesystemBreadcrumb::record('read', '/var/app/config.json', 'info', ['bytes' => 1024]);
```

Optional **breadcrumb recorders** (same config block as core instrumentation, `instrumentation.enabled` must be true): **cache** hits/misses, **Redis** commands, **views** (view composer `*`), **outbound HTTP** (`Illuminate\Http\Client` events), **response** metadata (`ResponsePrepared`), **database transactions** (`TransactionBeginning` / `Committed` / `RolledBack`), **`dump()`** via Symfony VarDumper, plus manual **`Lookout\Tracing\GlowBreadcrumb::glow()`** and **`Lookout\Tracing\FilesystemBreadcrumb::record()`**. Env flags: `LOOKOUT_INSTRUMENT_CACHE`, `_REDIS`, `_VIEWS`, `_OUTBOUND_HTTP`, `_RESPONSE_DETAIL`, `_DATABASE_TRANSACTIONS`, `_DUMP`. Set **`LOOKOUT_INSTRUMENT_COMPREHENSIVE_COLLECTION=true`** to turn on the optional recorders above (plus SQL breadcrumbs and performance collectors for cache, Redis, views, log) in one step.

**Broad Laravel error context (what maps where)**

AreaLookoutApplication info`context.laravel`: framework + PHP version, **application name**, **locale**, **config cached**, **debug**, **application\_env** (`APP_ENV`), route/command/queue hintsLaravel contextSame `context.laravel` + **`context.log_context`** from `context()` / `Illuminate\Log\Context\Repository`Exception context**`context.exception_context`** when the throwable implements **`context()`** (redacted)Stacktrace argumentsStructured **`stack_frames[].args`** when `reporting.include_stack_arguments` is true and PHP supplies trace args (`zend.exception_ignore_args=0`)Requests / URL / user`url`, `user`, `issue_route`, `context.server`; HTTP breadcrumbsServer info`context.server` (hostname, SAPI, OS, pid, limits, tz) + request `SERVER_ADDR` when presentGit informationDefault **`GitInformationMiddleware`** (commit, etc.)Solutions**`SolutionsMiddleware`** + `reporting.client_solutions`Console commandsBreadcrumbs + performance spans when enabledJobs and queuesBreadcrumbs + queue trace propagation + performanceQueriesSampled SQL breadcrumbs (default on); **DB spans** with fingerprint, bindings, slow flag + root rollup (`db.total_duration_ms`, `db.slow_query_count`) when performance DB collector onDatabase transactionsBreadcrumbs when `instrumentation.database_transactions` or `comprehensive_collection`Cache eventsCache breadcrumbs (default on) + cache **spans** (default on when performance enabled)Redis commandsBreadcrumbs + optional Redis **spans**External HTTPBreadcrumbs + **http.client** spans (Guzzle / `Http::`)ViewsView composer breadcrumbs + optional view **spans**LogsOptional `MessageLogged` breadcrumbs; optional log **spans**; structured **`/api/ingest/log`** via `lookout_logger()`Livewire**`context.livewire`** (component class + name) on Livewire requestsSpans / errors when tracing**`LOOKOUT_PERFORMANCE_ENABLED`**, `Tracer::markTraceMustExport` on error reportsDumps`instrumentation.dump` → **`DumpInstrumentation`**Glows / filesystemManual **`GlowBreadcrumb::glow()`**, **`FilesystemBreadcrumb::record()`**Customise report**`reporting.middleware`**, **`AttributeProviderInterface`**, **`ReportScope`**Global no-op: `LOOKOUT_DISABLED` or `reporting.disabled`. Ingest fields **`is_log`**, **`open_frame_index`**, and **`grouping_override`** (custom fingerprint when `fingerprint` is empty; camelCase aliases **`isLog`**, **`openFrameIndex`**, **`overriddenGrouping`**) are stored on the server. In the Lookout app, **Project → Monitoring → Signals** turns each signal type on/off per project while leaving error ingest always on.

### Signal control: dashboard + env overrides

[](#signal-control-dashboard--env-overrides)

Which signals are captured/sent is controlled from the Lookout dashboard (**Project → Monitoring → Signals**) and fetched by the SDK at boot via **`GET /api/config`** (cached for **`LOOKOUT_REMOTE_CONFIG_TTL`**, default 300s; disable the whole mechanism with **`LOOKOUT_REMOTE_CONFIG=false`**). Sample rates sync the same way. This replaced the per-signal `LOOKOUT_*_MONITORING_ENABLED` / `*_ENABLED` env vars as the *primary* switch and the old `LOOKOUT_PERFORMANCE_SYNC_FROM_API` (+ `LOOKOUT_SYNC_API_TOKEN`, `LOOKOUT_SYNC_PROJECT_ID`), which are gone.

**Precedence is env &gt; site:** an explicitly-set `LOOKOUT_*_ENABLED` / `*_SAMPLE_RATE` still wins over the dashboard. When env force-enables a signal the dashboard has off, the SDK sends **`X-Lookout-Env-Forced`** so the server accepts it, and reports its env overrides on the config fetch so the dashboard shows them as "Set by env."

### User feedback (crash page)

[](#user-feedback-crash-page)

When **`ErrorReportClient`** builds an error payload it ensures an **`occurrence_uuid`** (v4) and remembers it for **`lookout_last_error_occurrence_uuid()`** / **`ErrorReportClient::lastOccurrenceUuid()`**. On your custom error view, POST that UUID with the user’s message to **`POST /api/ingest/feedback`** (same project **`api_key`**; see Lookout **Ingest API → User feedback**). The comment appears on that occurrence’s thread in the app. Alternatively use the ingest response / read API **`event_id`** (ULID) as **`event_id`** in the feedback body.

Cron monitors
-------------

[](#cron-monitors)

Monitor check-ins: `in_progress` → `ok` / `error`, optional heartbeat, and monitor upsert via `monitor_config`.

```
use Lookout\Tracing\Cron\CheckInStatus;
use Lookout\Tracing\Cron\Client as CronClient;
use Lookout\Tracing\Cron\MonitorConfig;
use Lookout\Tracing\Cron\MonitorSchedule;

CronClient::configure([
    'api_key' => getenv('LOOKOUT_API_KEY'),
    'base_uri' => 'https://your-lookout-host.example',
    'cron_ingest_path' => '/api/ingest/cron',
]);

$config = MonitorConfig::make(MonitorSchedule::crontab('0 * * * *'), checkinMarginMinutes: 5);

$id = CronClient::captureCheckIn('hourly-job', CheckInStatus::inProgress(), monitorConfig: $config);
CronClient::captureCheckIn('hourly-job', CheckInStatus::ok(), $id);

CronClient::withMonitor('wrapped-job', fn () => doWork(), $config);

CronClient::captureCheckIn('heartbeat', CheckInStatus::ok(), null, 12.0);
```

Optional **`meta`** (string/number/bool values, size-limited server-side) on `captureCheckIn` attaches context to the check-in row and merges on completion.

Laravel: the same service provider configures `CronClient` from `config/lookout-tracing.php` (`cron_ingest_path` defaults to `/api/ingest/cron`).

Profiling (CPU / flame graphs)
------------------------------

[](#profiling-cpu--flame-graphs)

### Automatic (Laravel + Excimer) — zero code

[](#automatic-laravel--excimer--zero-code)

If the **Excimer** PECL extension is installed, turn on auto-profiling and the package captures a sampled fraction of web requests, console commands, and queue jobs and uploads them for you — no `ProfileClient::sendProfile()` calls. Without Excimer this is a silent no-op (use the manual paths below), and it never throws into your app.

```
LOOKOUT_PROFILING_ENABLED=true
LOOKOUT_PROFILING_SAMPLE_RATE=0.05   # 5% of transactions (keep low — each profile counts toward your event quota)
LOOKOUT_PROFILING_PERIOD_US=10000    # sample period in microseconds (10ms)
LOOKOUT_PROFILING_EVENT_TYPE=wall    # 'wall' (default) or 'cpu'
LOOKOUT_PROFILING_MIN_DURATION_MS=0  # only upload transactions at least this slow
```

Auto-profiling rides on performance instrumentation, so keep `performance.enabled = true`. `trace_id`, `transaction`, `environment`, and `release` are attached automatically so profiles cross-link from traces and errors. For non-Laravel apps or when Excimer is unavailable, capture manually:

### Manual capture

[](#manual-capture)

Capture with **Excimer** (speedscope JSON), **xhprof** / **Tideways**, **SPX**, or cooperative **`php.manual_pulse`** sampling (no extension), then POST to Lookout.

```
use Lookout\Tracing\Profiling\ProfileClient;

ProfileClient::configure([
    'api_key' => getenv('LOOKOUT_API_KEY'),
    'base_uri' => 'https://your-lookout-host.example',
    'profile_ingest_path' => '/api/ingest/profile',
]);

ProfileClient::sendProfile([
    'agent' => 'other',
    'format' => 'speedscope',
    'data' => [/* speedscope JSON object */],
    'trace_id' => 'abc123…',
    'transaction' => 'GET /checkout',
]);
```

**First-party aggregate hotspots** (`lookout.v1`):

```
use Lookout\Tracing\Profiling\LookoutProfileV1Payload;
use Lookout\Tracing\Profiling\ProfileClient;

ProfileClient::sendProfile(LookoutProfileV1Payload::aggregateIngestBody(
    [
        ['file' => 'app/Services/Checkout.php', 'line' => 120, 'samples' => 48],
    ],
    meta: ['source' => 'custom-collector'],
    context: ['trace_id' => 'abc123…', 'transaction' => 'POST /checkout'],
));
```

Package classes under `Lookout\Tracing\Profiling\` (e.g. `ExcimerExporter`, `XhprofLikeExporter`, `SpxPayload`, `ManualPulseSampler`, `LookoutProfileV1Payload`) help build `agent` / `format` / `data` for each backend. Laravel: `LookoutTracingServiceProvider` merges the same `api_key`, `base_uri`, and `profile_ingest_path` from `config/lookout-tracing.php`.

**Overhead:** Lookout does not sample profiles for you — wrap `ProfileClient::sendProfile()` (or your Excimer/Tideways hooks) so production only uploads a small fraction of requests or when duration exceeds a threshold, similar to `profiles_sample_rate` / slow-transaction rules elsewhere.

Laravel
-------

[](#laravel)

Auto-discovery registers `Lookout\Tracing\Laravel\LookoutTracingServiceProvider`.

### Quick install

[](#quick-install)

```
composer require lookout/tracing
php artisan lookout:install
```

`lookout:install` can either **create a project** on your Lookout instance (API token from Profile → API tokens + base URL) or **use an existing DSN**. Either way it appends to `.env`:

```
LOOKOUT_DSN="https://YOUR_PROJECT_API_KEY@your-lookout-host.example.com"
LOOKOUT_LARAVEL=true
```

- **`LOOKOUT_DSN`** — single line: `https://` + **project ingest API key** as the URL user + `@` + Lookout host (optional port). Percent-encode the key if it contains `@` or other reserved characters. The create-project flow obtains this key from the API after `POST /api/v1/projects`.
- **`LOOKOUT_LARAVEL=true`** — enables **uncaught exception reporting** (`LOOKOUT_REPORT_EXCEPTIONS`) and **trace auto-flush on HTTP terminate** (`LOOKOUT_TRACING_AUTO_FLUSH`) unless you override those env vars explicitly.

Non-interactive:

- Existing project: `php artisan lookout:install --dsn="https://PROJECT_KEY@host.example.com"`.
- New project: `php artisan lookout:install --url="https://host.example.com" --token="your_api_token"` (and **`--organization=ULID`** if your account has more than one organization). Optional **`--project-name="My App"`**. On the same Laravel host (e.g. this Lookout app), you can omit **`--url`** when **`APP_URL`** is set: `--token="…"` alone uses that origin.

Pass **`--no-quick`** to skip `LOOKOUT_LARAVEL=true`.

**API key only (team shares one Lookout URL):** set a default host once — `LOOKOUT_URL`, `LOOKOUT_BASE_URI`, or `config/services.php` → **`lookout.url`** — then each environment only needs **`LOOKOUT_API_KEY`**.

- Middleware alias: **`lookoutTracing.continueTrace`** — call `continueTrace()` from incoming headers.
- Publish config: `php artisan vendor:publish --tag=lookout-tracing-config`
- Env resolution order for **base URI**: `LOOKOUT_DSN` host → `LOOKOUT_BASE_URI` → `LOOKOUT_URL` → `config('services.lookout.url')` → `APP_URL`. Profile ingest path defaults to `/api/ingest/profile` (override in published config).

### Framework breadcrumbs &amp; exception reporting

[](#framework-breadcrumbs--exception-reporting)

The provider registers **event listeners** (when `instrumentation.enabled` is true) that append **breadcrumbs** for:

AreaLaravel events (indicative)HTTP`RouteMatched`, `RequestHandled`Console`CommandStarting`, `CommandFinished`Queue`JobProcessing`, `JobProcessed`, `JobFailed`, `JobExceptionOccurred`Optional`QueryExecuted` (sampled), `MessageLogged`, allowlisted domain events, or a wildcard listenerBreadcrumbs are **cleared** at each route match, Artisan command, or queue job so `queue:work` and Octane do not mix unrelated requests.

With **`LOOKOUT_LARAVEL=true`** or **`LOOKOUT_REPORT_EXCEPTIONS=true`** (and a resolved API key + base URI), the provider registers a **`reportable`** handler on the default exception handler. It POSTs to **`POST /api/ingest`** with:

- exception message, class, stack trace, and **stack frames**
- current **breadcrumbs**
- **trace** fields from `Tracer::errorIngestTraceFields()` when a transaction was started
- **`context.laravel`**: framework version, PHP version, route, queue job name, Artisan command, HTTP path/method when available

Tune knobs in `config/lookout-tracing.php` (`instrumentation.*`, `breadcrumbs_max`, `error_ingest_path`).

### Performance monitoring (traces &amp; spans)

[](#performance-monitoring-traces--spans)

Enabled from the dashboard (**Monitoring → Signals**), with **`LOOKOUT_PERFORMANCE_ENABLED`** as a local override (env &gt; site); needs a resolved API key and base URI from **`LOOKOUT_DSN`**, **`LOOKOUT_API_KEY`** + **`LOOKOUT_URL`**, etc. This turns on **sampled span recording**: OpenTelemetry-style **trace ids**, **spans**, and optional **span events**, sent to **`POST /api/ingest/trace`** via `Tracer::flush()` or **`LOOKOUT_TRACING_AUTO_FLUSH=true`**. If the project has trace ingest off in **Monitoring → Signals** the SDK stops sending (or sends **`X-Lookout-Env-Forced`** when env forces it on); otherwise the API returns **403**.

1. **Middleware (order matters):** register **`lookoutTracing.continueTrace`** first, then **`lookoutTracing.performance`**, or set **`LOOKOUT_PERFORMANCE_AUTO_MIDDLEWARE=true`** to append only the performance middleware to `web` and `api` (you still add `continueTrace` yourself if it is not already in those groups).
2. **Sampling:** default **`RateSampler`** at **10%** (`LOOKOUT_PERFORMANCE_SAMPLE_RATE=0.1`). Implement `Lookout\Tracing\Performance\Sampler` and set `performance.sampler.class` for custom logic. Traces continued from an incoming traceparent with **`sampled=0`** never record spans (propagation only). Optional **tail sampling** (`LOOKOUT_PERFORMANCE_TAIL_SAMPLING=true`): keep slow roots (`LOOKOUT_PERFORMANCE_TAIL_SLOW_MS`), errors / 5xx, optional `LOOKOUT_PERFORMANCE_TAIL_RESIDUAL_RATE` for a thin random sample of the rest — same theme as lowering head sample rates in production while still capturing outliers.
3. **Limits:** `performance.trace_limits` — max spans per export, max attributes per span / span event, max span events per span.
4. **Hooks:** `Tracing::configureSpans(fn (Span $span) => …)` and `Tracing::configureSpanEvents(fn (array $event) => …|null)` — return **`null`** from the span-event callback to drop an event.
5. **Collectors** (`performance.collectors.*`): HTTP server transaction, **database** queries (child `db.query` spans), **console** / **queue** root transactions, **log** lines as span events, and **HTTP client** spans when you attach **`GuzzleTraceMiddleware`** (see below).

CLI / queue: enable **`LOOKOUT_PERFORMANCE_FLUSH_CLI_QUEUE=true`** to flush after each command or job, or call **`Tracing::flush()`** yourself.

Rails
-----

[](#rails)

For Ruby on Rails, use the copy-paste module under **`packages/lookout-rails/`** in the Lookout repository (`lib/lookout_framework.rb` + README), or a git subtree mirror if you use `SPLIT_LOOKOUT_RAILS_REPO`: `ActiveSupport::Notifications` for controller and Active Job, optional SQL sampling, and `LookoutFramework.report_exception` from your error pipeline.

Guzzle 7
--------

[](#guzzle-7)

```
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Lookout\Tracing\Http\GuzzleTraceMiddleware;

$stack = HandlerStack::create();
$stack->push(GuzzleTraceMiddleware::create());

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

With **performance monitoring** enabled, the same middleware also records **`http.client`** child spans (when a parent span is active and sampling allows recording).

Requirements
------------

[](#requirements)

- PHP 8.3+
- `psr/http-message` (for the optional Guzzle middleware type hints)
- `guzzlehttp/guzzle` (optional, for `GuzzleTraceMiddleware` + promises)

SDK roadmap &amp; Lookout alignment
-----------------------------------

[](#sdk-roadmap--lookout-alignment)

The Lookout app surfaces **Traces**, **Transactions**, and **trace detail** in the web UI; the SDK sends **errors** (`POST /api/ingest`) and, when enabled, **spans** (`POST /api/ingest/trace`) with consistent **`trace_id`** and compact traceparent propagation.

Server behaviorSDK support**`performance_ingest_enabled`** falseTrace ingest returns **403**. The SDK reads this from remote config (see below) and stops sending traces to match the dashboard — no 403 spam. Override locally with **`LOOKOUT_PERFORMANCE_ENABLED`** (env &gt; site); when env forces it on, the SDK sends **`X-Lookout-Env-Forced`** so the server accepts it. Auto-flush and queue/cli flush log **`lookout.tracing.trace_forbidden`** when **`performance.log_forbidden_trace_ingest`** is true (default).**`GET /api/config`****`RemoteConfig::fetch()`** — per-project enabled flags + sample rates, cached and applied at boot (replaces the old `performance.sync_from_api` / `GET /api/v1/projects/{id}` path).**429 / flaky network****`trace_ingest.max_attempts`**, **`retry_delay_ms`**, **`retry_statuses`** (env: **`LOOKOUT_TRACE_INGEST_*`**) — **`Tracer::flushWithResult()`** uses **`HttpTransport::postJsonWithResponseRetries()`**. **403** is never retried.### Implemented building blocks

[](#implemented-building-blocks)

- **`Lookout\Tracing\Interop\OpenTelemetryTraceConverter`** — OTLP JSON → Lookout: **`toJobPayloads()`** (one row set per `traceId`), **`toLookoutIngestBody()`** when only one trace is present, **`fromLookoutIngestBody()`** for OTLP export from native bodies. Lookout HTTP: **`POST /api/ingest/trace/otlp`** (same auth/gate as **`/api/ingest/trace`**).
- **`Lookout\Tracing\Http\ContinueTracePsr15Middleware`** — PSR-15 traceparent / **`baggage`** parsing (Slim, Mezzio, etc.).
- **`Lookout\Tracing\Support\DataRedactor::redact()`** — recursive redaction for span **`data`** / context-style arrays.
- **`Lookout\Tracing\Testing\TracerInspection::traceIngestBody()`** — stable access to **`buildTraceIngestBody()`** in tests.

### Still optional / app-specific

[](#still-optional--app-specific)

- Dedicated **OpenTelemetry PHP SDK** exporter package (protobuf / gRPC) — HTTP JSON ingest is covered by **`/api/ingest/trace/otlp`** and the converter.
- **PSR-15 “performance”** middleware (auto HTTP transactions) — today use manual **`Tracing::startTransaction`** or stay on Laravel.
- **Queue-based async flush** with deduplication across workers.

Scope
-----

[](#scope)

**Tracing** supports **manual** transactions/spans (`Tracing::trace()`, `startTransaction`) and optional **performance mode**: sampled **auto spans** for HTTP (middleware), SQL, Artisan, queue, logs, and outbound Guzzle calls, flushed to Lookout’s trace ingest.

**Framework instrumentation** (above) still records **breadcrumbs** for **error reports**; performance collectors add **span trees** for the distributed trace UI when you flush to **`/api/ingest/trace`**.

**Crons:** Lookout stores check-ins and monitor metadata; it does **not** yet auto-open issues or email you on missed schedules like some hosted cron products—you can build alerting on top (e.g. scheduled jobs reading the API) or extend the app later.

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance93

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

20

Last Release

35d ago

Major Versions

1.8.0 → 2.0.02026-06-22

### Community

Maintainers

![](https://www.gravatar.com/avatar/3033e31411665568146bf823404e1d1c789fa0d93cba99ae3a891f100b4c279e?d=identicon)[tshafer](/maintainers/tshafer)

---

Top Contributors

[![tshafer](https://avatars.githubusercontent.com/u/299464?v=4)](https://github.com/tshafer "tshafer (44 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/lookout-tracing/health.svg)

```
[![Health](https://phpackages.com/badges/lookout-tracing/health.svg)](https://phpackages.com/packages/lookout-tracing)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.8k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[cakephp/authentication

Authentication plugin for CakePHP

1214.3M118](/packages/cakephp-authentication)[typo3/cms-core

TYPO3 CMS Core

3313.6M5.6k](/packages/typo3-cms-core)[typo3/cms-adminpanel

TYPO3 CMS Admin Panel - The Admin Panel displays information about your site in the frontend and contains a range of metrics including debug and caching information.

115.8M70](/packages/typo3-cms-adminpanel)[eliashaeussler/typo3-warming

Warming - Warms up Frontend caches based on an XML sitemap. Cache warmup can be triggered via TYPO3 backend or using a console command. Supports multiple languages and custom crawler implementations.

22272.4k](/packages/eliashaeussler-typo3-warming)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
