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 Package

pkerrigan/xray

Xray is a Laravel package for adding feature flags to your app. Define flags, toggle them at runtime, and gate features in PHP or Blade without redeploying—useful for gradual rollouts, beta access, and safely shipping incomplete work.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and prerequisites

composer require pkerrigan/xray

This is a thin, dependency-light instrumentation library — not an agent and not a Laravel package. Before writing code you need somewhere for segments to go:

  • EC2/ECS/EKS: run the AWS X-Ray daemon locally (sidecar or 127.0.0.1:2000/udp).
  • Lambda: use the X-Ray Lambda extension/daemon provided by the runtime; the default UDP submitter targets localhost.
  • IAM: the daemon (not your PHP process) needs xray:PutTraceSegments and xray:PutTelemetryRecords.
  • The package itself only needs PHP and Composer; check composer.json for the supported PHP range before adopting.

Where to look first

  1. README.md — the usage example is essentially the whole API surface.
  2. src/Trace.php — the root segment and lifecycle (begin(), end(), submit()).
  3. src/AbstractSegment.php / src/Segment.php / src/Subsegment.php — naming, timing, annotations, metadata, error flags, and addSubsegment().
  4. src/Submission/DaemonSegmentSubmitter.php — the default emitter; the place to look when tracing silently does nothing.
  5. src/Submission/SegmentSubmitter.php — the interface to implement for a custom transport or test double.

First use case: one trace per request, one subsegment around a DB call

use Pkerrigan\Xray\Trace;
use Pkerrigan\Xray\Subsegment;

$trace = new Trace();
$trace->setName(config('app.name'));
$trace->begin();

$sub = new Subsegment();
$sub->setName('users.find');
$sub->begin();
try {
    $user = User::findOrFail($id);
} catch (\Throwable $e) {
    $sub->setError(true);   // verify method name in src/AbstractSegment.php
    throw $e;
} finally {
    $sub->end();
}
$trace->addSubsegment($sub);

$trace->end();
$trace->submit(); // uses DaemonSegmentSubmitter by default

Verify in the X-Ray console that the trace appears. If it does not, the problem is almost always the daemon address, IAM, or an unclosed segment — not the PHP code.


Implementation Patterns

1. Laravel middleware lifecycle

Register a global or route middleware that starts the trace in handle() and submits it in terminate(). Bind the trace in the container so controllers, jobs, and services can add subsegments without passing it around.

public function handle($request, Closure $next)
{
    $trace = new \Pkerrigan\Xray\Trace();
    $trace->setName(config('app.name'));
    $trace->setUrl($request->fullUrl());
    $trace->setMethod($request->method());
    $trace->begin();

    app()->instance('xray.trace', $trace);

    return $next($request);
}

public function terminate($request, $response)
{
    $trace = app('xray.trace');
    $trace->addAnnotation('http.status', $response->getStatusCode());
    $trace->end();
    $trace->submit();
}

Use terminate() rather than the end of handle() so the trace covers the full response lifecycle. In Laravel, register it in bootstrap/app.php (Laravel 11+) or Kernel.php.

2. Wrap work in a helper

Manual instrumentation gets noisy. A helper keeps try/finally correct and encourages consistent naming:

function xray_segment(string $name, callable $fn, array $annotations = [])
{
    $trace = app('xray.trace');

    $sub = new \Pkerrigan\Xray\Subsegment();
    $sub->setName($name);
    foreach ($annotations as $k => $v) {
        $sub->addAnnotation($k, $v);
    }
    $sub->begin();

    try {
        return $fn();
    } catch (\Throwable $e) {
        $sub->setError(true);
        throw $e;
    } finally {
        $sub->end();
        $trace->addSubsegment($sub);
    }
}

Then: xray_segment('stripe.charge', fn () => $stripe->charges->create(...)).

3. Inbound header propagation

X-Ray clients send X-Amzn-Trace-Id: Root=1-...;Parent=...;Sampled=1. Parse it at the edge and attach it to your trace so upstream and downstream segments join the same trace. Read the request header, split on ;, validate the Root format, and set trace ID, parent ID, and sampled flag on your Trace. If the package exposes a header helper, prefer it over hand-parsing.

4. Outbound HTTP propagation

Add a Guzzle middleware (or Laravel HTTP client macro) that serializes the current trace header onto outbound requests:

$stack->push(Middleware::mapRequest(function ($request) {
    $trace = app('xray.trace');
    return $request->withHeader('X-Amzn-Trace-Id', $trace->getHeader());
}));

Also create a subsegment for the call, named after the downstream service, with namespace = remote if the API supports it. This is where most of the day-to-day value comes from: seeing the service map and latency between your app and external dependencies.

5. Queue/job propagation

Don't share a live segment object across processes. Serialize the trace header into the job payload, then in the worker create a new trace with the parent ID set from that header, start it, run the job, and submit. The web request should not stay open waiting for the job.

6. Custom submitter for testing or alternate transport

Implement SegmentSubmitter and either forward to the daemon, buffer to a file, or capture in memory for assertions. In tests, a recording submitter lets you assert segment names, annotations, and error flags without any AWS infrastructure. This is also the cleanest way to debug what JSON the library actually produces.

7. Sampling decisions

Decide early whether the request is sampled. If not sampled, return a no-op trace path and avoid building segments — the cost of instrumentation should be near-zero for unsampled traffic. Respect an upstream Sampled=0; don't override it.


Gotchas and Tips

  • No auto-instrumentation. There are no framework service providers, no query listener, no Guzzle handler bundled. Every span is manual. Budget time for a middleware plus a handful of helpers and a Guzzle hook; that's usually enough for useful traces.
  • Unclosed subsegments corrupt the trace. Always use try/finally. A subsegment that is begin()-ed but never end()-ed will either be dropped or produce an invalid document.
  • UDP failures are silent. The default daemon submitter sends fire-and-forget UDP. If the daemon isn't running, nothing throws. Check the daemon's own logs, docker logs, or temporarily swap in a TCP/file submitter to confirm payloads.
  • Daemon host in containers. 127.0.0.1:2000 is correct only for sidecar/localhost. In Docker Compose or Kubernetes you may need the sidecar hostname or the node IP.
  • Header format matters. Root, Parent, and Sampled are semicolon-separated. Root trace IDs have a specific 1-8hex-24hex shape. Malformed headers silently break trace continuity; validate before applying.
  • Parent/child nesting. Add nested subsegments to their logical parent, not always to the root trace. Flattening everything under the root produces a misleading service map.
  • Errors don't propagate automatically. Exceptions do not set error/fault on the segment. Set the flags explicitly in your catch blocks, otherwise failing requests look healthy in X-Ray.
  • Annotations vs. metadata. Annotations are indexed and searchable but limited in count and size; metadata is arbitrary but not indexed. Keep high-cardinality values (user IDs, raw SQL) in metadata; use annotations for filterable dimensions like route, status code, and dependency name.
  • Segment size limits. X-Ray caps the serialized segment document (tens of KB). Logging large payloads into metadata will cause rejection; truncate aggressively.
  • Submit once. Calling submit() more than once (e.g., in both middleware and a shutdown function) duplicates traces or produces partial documents. Pick a single lifecycle owner.
  • Long-running workers. Under Octane, Swoole, or queue workers, a container-bound trace will leak across requests. Create a fresh Trace per request/job and reset any static state.
  • Clock and duration limits. Very long-running segments may be rejected or truncated. For long jobs, break work into subsegments with recent timestamps.
  • Lambda specifics. The daemon isn't on localhost by default; rely on the X-Ray extension and confirm the UDP endpoint. Cold-start segments can be misleading — name them distinctly.
  • Small ecosystem, zero dependents. With ~60 stars, expect to read the source rather than find Stack Overflow answers. Pin the version in composer.json, and don't expect rapid fixes; the codebase is small enough that forking a submitter or segment class is realistic.
  • Extension points are simple by design. Subclass Segment/Subsegment to set default namespace or naming conventions, or implement SegmentSubmitter to change transport. Those two hooks cover most nonstandard environments.
  • Debug by serializing. If the console doesn't show what you expect, call the segment's JSON serialization and inspect the document directly before blaming the daemon. Most issues are missing end(), wrong parent IDs, or a malformed trace header.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings