Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xray Laravel Laravel Package

napp/xray-laravel

Napp Xray gives Laravel developers an in-app dashboard for inspecting their application's structure, browsing routes, models, and classes, and quickly debugging how everything fits together without digging through files.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure

composer require napp/xray-laravel
php artisan vendor:publish --tag=xray-config   # confirms as config/xray.php

Then set, at minimum:

XRAY_ENABLED=true
XRAY_ENV=staging            # or "production"; becomes the annotation AWS filters on
XRAY_SERVICE_NAME=orders-api # if unset, falls back to APP_NAME — set it explicitly
XRAY_DAEMON_ADDRESS=127.0.0.1:2000

Note the XRAY_ env names are the package's own defaults — open config/xray.php first and confirm each key before wiring .env, because the published file is the only source of truth and a mismatched name fails silently.

Attach the middleware as the outermost layer. The trace only measures what it wraps, so it must be the first middleware to run:

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->prepend(\Napp\Xray\Middleware\Http\XrayMiddleware::class);
})

// app/Http/Kernel.php (Laravel 10 and below): $middleware[]

Run a daemon. X-Ray spans are shipped to a local collector, not straight to AWS. Locally: docker run -p 2000:2000/udp public.ecr.aws/xray/aws-xray-daemon. In ECS: sidecar container with UDP 2000 on localhost. In EKS: a DaemonSet with hostNetwork. On Lambda: you don't need this package at all — active tracing is a function setting.

Where to look first, in order: config/xray.php (what is instrumented, sampling rate, service naming) → the service provider's container binding (the xray singleton and facade accessor) → the middleware's handle/terminate pair, which tells you where the flush happens.

First use case: pick one request that a real user complains about — GET /orders/{id} — open the trace in the X-Ray console, and read the waterfall. You should see the root HTTP segment, child subsegments for each SQL query and Redis call, and any outbound HTTP calls. If the segment tree is flat (one segment, nothing nested) the middleware is running but instrumentation hooks are not — that is a config problem, not a daemon problem.

Budget ~10 minutes between a request being made and the trace being visibly queryable; do not judge a fresh install by an empty console refresh.

Implementation Patterns

Custom segments around the work you actually care about. The auto-instrumentation tells you where time went; manual segments tell you which business operation it belonged to. Always pair them with try/finally, or an exception path leaves an unclosed subsegment and the trace renders with a bogus duration:

use Napp\Xray\Facades\Xray;

Xray::addSegment('pricing.recalculate');
try {
    $quote = $this->pricer->quote($order);
} catch (\Throwable $e) {
    Xray::setError($e);   // marks the segment as faulted in the console
    throw $e;
} finally {
    Xray::endSegment('pricing.recalculate');
}

Annotations vs. metadata is the one API distinction that pays off daily. Annotations are indexed and filterable — keep them low-cardinality: tenant_tier=gold, route=orders.show, cache_status=miss. Metadata is a free-form blob that isn't indexed and is subject to per-segment size limits: order IDs, row counts, the cache key. Never put tokens, emails, or anything PII-adjacent in either; traces are retained for 30 days and readable by anyone with console access.

Namespace segment names by dependency, not by class. redis.get, s3.put, stripe.charge, elastic.search. The service map aggregates on these names, and App\Services\PricingService::calculate clusters into noise that tells you nothing.

Propagate context across the queue boundary. This is the most common gap: a job dispatched with dispatch() starts a new trace unless the X-Amzn-Trace-Id header travels in the payload. Capture it at dispatch time and restore it when the worker picks the job up — either via the package's queue hooks or a Queue::before/JobProcessing listener that opens a segment from the stored header. Symptom of a missing link: the job appears as its own one-segment trace instead of a child of the HTTP request that queued it.

Log the trace ID next to everything else. A middleware placed immediately after X-Ray:

Log::withContext(['trace_id' => $request->header('X-Amzn-Trace-Id')]);

Now a Sentry event, a Slack error, or a grep result becomes a click into the trace instead of a guess. This one line changes how the team uses the tool more than any sampling tweak.

Sampling is a per-environment decision, not a global one. 100% in staging and in the first week of production; then a reservoir (roughly 1 request/second) plus a fixed percentage. Set health checks, /horizon, /telescope, and static assets to zero — they will otherwise dominate both the service map and the bill. Raise the rate deliberately for the routes you'd page on (checkout.*), not for the whole app.

Outbound calls to non-Guzzle clients need the header by hand. The package's HTTP-client instrumentation covers Laravel's Http facade and Guzzle. Raw curl, a vendor SDK with its own transport, or a gRPC client will produce an orphan service unless you forward the trace header yourself. Verify by checking the downstream service's trace shows your span as its parent.

Console and scheduled commands: wrap long-running artisans (reports:rebuild) in a segment so scheduled work is visible next to request traffic. Same try/finally discipline — a scheduled command that crashes mid-segment is exactly the trace you want to read, and it's the one most likely to be malformed.

Treat the config file as code. Commit it, review changes to it, and put the segment-name prefix in it so staging traces are trivially separable from production ones in the console.

Gotchas and Tips

The dominant failure is a silent no-op. Enabled flag off, config:cache holding a stale value, missing xray:PutTraceSegments / xray:PutTelemetryRecords on the task role, or the daemon unreachable over UDP. Debug in that order, and check the daemon's own logs — it accepts UDP silently and drops what it can't forward.

env() inside a cached config returns null. If you extend config/xray.php, put literal defaults in the file and reference env only as an override; a missing env var should degrade to a sane value, not to false. This is also the classic upgrade failure: a renamed config key falls back to the default and changes behavior with no error.

X-Ray drops oversized segments without raising an exception. Dumping full query bindings or a big metadata array into a segment gets the batch rejected server-side. If traces are intermittently missing, that's the first hypothesis — look for unprocessed segment responses in the logs, then trim metadata to counts and IDs.

Don't trace Eloquent in hot loops. Automatic query instrumentation is the package's best feature and its biggest cost driver: a 5,000-row import becomes 5,000 subsegments and an unreadable trace. Disable query tracing for those code paths, or wrap the loop in one segment with a row count in metadata.

Octane and long-lived workers. State held on a singleton survives between requests under Octane in ways it never does under FPM. Before you trust it, run two sequential requests against octane:start locally and confirm the second trace starts clean rather than carrying the first request's open segments. octane:reload after any config change — workers cache the booted container.

Reach for extension points before forking. The xray container binding, the middleware class, and the facade are all overridable; the package is small and low-star, so a fork has a real maintenance cost. If you do fork, pin the fork and keep a note in the repo explaining why, plus the upstream commit you diverged from.

Pin the minor, read the changelog. This is a 1.x package — minors can carry behavior changes by design. On every bump, publish the config to a throwaway branch and git diff it against what's committed. The delta, not the install, is what you're approving.

Keep it off in tests and CI. XRAY_ENABLED=false in phpunit.xml; it removes a network dependency from the suite and keeps your trace history clean.

X-Ray is a debugging tool, not an alerting tool. It has no paging. Pair the error annotations you set with a CloudWatch or Sentry alert if you want to be woken up; use X-Ray to answer why after the fact.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style