Flare by Spatie
    • Error Tracking
    • Performance Monitoring
    • Logs Coming soon
  • Pricing
  • Docs
  • Insights
  • Changelog
  • Back to Flare ⌘↵ Shortcut: Command or Control Enter
  • Sign in
  • Try Flare for free
  • Error Tracking
  • Performance Monitoring
  • Logs Coming soon
  • Pricing
  • Docs
  • Insights
  • Changelog
    • Back to Flare ⌘↵ Shortcut: Command or Control Enter
    • Try Flare for free
    • Sign in
Flare Flare Laravel Laravel PHP PHP JavaScript JavaScript React React Vue Vue Protocol Protocol
  • General
  • Installation
  • Censoring collected data
  • Ignoring collected data
  • Flare daemon
  • Laravel Octane
  • Laravel Vapor
  • Errors
  • Introduction
  • Customise error report
  • Customising error grouping
  • Linking to errors
  • Logs
  • Introduction
  • Levels
  • With errors
  • Performance
  • Introduction
  • Sampling
  • Limits
  • Modify spans and span events
  • Data Collection
  • Application info
  • Cache events
  • Console commands
  • Custom context
  • Database transactions
  • Dumps
  • Errors when tracing
  • Exception context
  • External http requests
  • Filesystem operations
  • Git information
  • Glows
  • Identifying users
  • Jobs and queues
  • Laravel context
  • Livewire
  • Queries
  • Redis commands
  • Requests
  • Server info
  • Spans
  • Stacktrace arguments
  • Views
  • Older Packages
  • Laravel Flare V2
  • Laravel Flare V1
  • Ignition

Spans

Flare automatically collects a lot of information about everything happening within your application. But sometimes you want to add your own custom events to Flare.

Internally, Flare is following the OpenTelemetry specification. This means you can add two types of events to Flare:

  • Spans
  • SpanEvents

A span can be seen as an event that takes some time to complete, such as handling a request or running a query. A span always has a start and end time and can be nested.

A span event is a sub-event of a span. It always requires a parent's span to be attached to it. A span event typically has no start or end time but happens at an exact point in time. Some examples of span events are logs being written, errors happening, etc.

Each span in the end belongs to a trace, which is a collection of related spans. A trace can be distributed; for example, a request handled by your server triggers a job that runs on another server, both generating spans in the same trace.

Starting a trace

Flare's Laravel application will default to start a sampled trace based upon a sampling decision. No manual work is required to start a trace.

Creating spans

A span always requires the following properties:

  • A trace ID
  • A span ID
  • A name
  • A start and end time

The following properties are optional:

  • A parent span ID
  • Attributes adding context to the span

The easiest way to get started with adding spans using the tracer:

use Spatie\LaravelFlare\Facades\Flare;

Flare::tracer()->span('My custom span', function (){
    // Do an operation
});

It is possible to nest spans:

Flare::tracer()->span('My custom span A', function () use ($flare){
    // Do an operation
    
    Flare::tracer()->span('My custom span B', function (){
        // Do another operation
    });
});

You can add additional attributes to a span to provide additional context:

Flare::tracer()->span('My custom span', function (){
    // Do an operation
}, attributes: [
    'key' => 'value'
]);

If you want to add attributes after the operation has run, based on the result of the operation, you can add an endAttributes Closure:

Flare::tracer()->span('My custom span', function () {
    // Do an operation
    
    return $result;
}, endAttributes: function ($result) {
    return ['result' => $result];
});

When you don't want to use a closure to run operations, you can also start and end a span manually:

$span = Flare::tracer()->startSpan('My custom span');

// Do an operation

Flare::tracer()->endSpan($span);

It is possible to pass additional attributes to the span as such:

$span = Flare::tracer()->startSpan('My custom span', attributes: [
    'key' => 'value'
]);

// Do an operation

Flare::tracer()->endSpan($span, additionalAttributes: function ($result) {
    return ['result' => $result];
});

Calling endSpan without a span will end the current span in the trace:

Flare::tracer()->startSpan('My custom span');

// Do an operation

Flare::tracer()->endSpan();

It is possible to define the times of the span manually:

use Spatie\FlareClient\Time\TimeHelper;

$span = Flare::tracer()->startSpan('My custom span', time: TimeHelper::now());

// Do an operation

Flare::tracer()->endSpan($span, time: TimeHelper::now());

Flare works with nano timings internally, so you can also use the TimeHelper to convert your timings to nanoseconds:

TimeHelper::now(); // Current time
TimeHelper::phpMicroTime(microtime(true)); // Parses PHP's microtime
TimeHelper::dateTimeToNano(new DateTime()); // Parses a DateTime object

TimeHelper::microseconds(1000); // 1000 microseconds
TimeHelper::milliseconds(200); // 200 milliseconds
TimeHelper::seconds(30); // 30 seconds
TimeHelper::minutes(4); // 4 minutes

Nested spans will automatically set their parent span ID, so you don't need to worry about that.

Creating span events

A span always requires the following properties:

  • A name
  • A timestamp

The following properties are optional:

  • Attributes adding context to the span event

Please remember that span events are always attached to a parent span, so you need to start a span first.

The easiest way to get started with adding span events using the tracer:

Flare::tracer()->spanEvent('My custom span event');

It is possible to add additional attributes to a span event to provide additional context:

Flare::tracer()->spanEvent('My custom span event', attributes: [
    'key' => 'value'
]);

You can set the timestamp of the span event manually:

use Spatie\FlareClient\Time\TimeHelper;

Flare::tracer()->spanEvent('My custom span event', time: TimeHelper::now());

Ending a trace

In a Laravel application, traces are automatically ended at the end of a request, job, or command.

Propagation

Traces can sometimes span multiple entry points, such as commands, jobs or web requests. For example, you could have a web request that triggers a job, which triggers another job. In this case, you want to propagate the trace ID and span ID to the next entry point so that you'll have the complete picture of the trace in Flare.

By default, the Flare Laravel client automatically propagates the trace ID and span ID when queuing a job. This means that once the job runs, the trace will be continued automatically.

To propagate, we'll need to pass on the trace ID and span ID to the next entry point; the traceparent is a single string containing both.

use Illuminate\Support\Facades\Http;

Http::post($url, [
    'traceparent' => Flare::tracer()->traceparent(),
]);

On the receiving side, you generally don't need to do anything. The Laravel client picks up incoming traceparents automatically through the application lifecycle: HTTP requests are read from the traceparent header, queued jobs from the propagated payload, and so on. The continued trace then keeps the same sampling decision as the parent.

If you're working in an entry point the lifecycle doesn't cover yet (a custom long-running process, a non-standard transport), see the application lifecycle docs in the PHP section for the lower-level wiring.

Using and creating recorders

We've seen how to trace spans and span events in a performance context until now. This context is also useful until an error is thrown in your application.

That's why Flare provides another way to save spans and span events, using recorders.

A recorder is a class that boots before Flare boots and listens for events within your application. It can record spans and span events, which will be used for performance monitoring. Additionally, when an error occurs, all spans and span events within the recorder will be sent alongside the error to Flare.

Flare provides many default recorders, such as the QueryRecorder, ViewRecorder, RequestRecorder, and so on. For example, when recording a query in Flare, you'll call $flare->query()->record(...); internally, Flare will call the QueryRecorder to record the query.

It is possible to add your own recorders, let's dive into it.

Creating a Span recorder

A span recorder can be created by extending the SpansRecorder class:

use Spatie\FlareClient\Recorders\SpansRecorder;
use Spatie\FlareClient\Enums\RecorderType;

class MyQueryRecorder extends SpansRecorder
{
    public static function type(): string|RecorderType
    {
        return 'my_query';
    }

    public function recordQueryStart(string $sql): void
    {
        $this->startSpan("Query - {$sql}");
    }

    public function recordQueryEnd(): void
    {
         $this->endSpan();
    }
}

Now we'll need to register the recorder with Flare in the config/flare.php file:

use Spatie\FlareClient\Enums\CollectType;

'collects' => FlareConfig::defaultCollects(
    extra: [
        CollectType::Recorders->value => [
            'recorders' => [MyQueryRecorder::class => []],
        ]
    ]
),

You'll always need to provide the recorder's class as the key, and you can provide additional configuration as the value.

Now we can call our recorder:

Flare::recorder('my_query')->recordQueryStart("SELECT * FROM users");

// Run your application

Flare::recorder('my_query')->recordQueryEnd();

When a trace is started, the span is added to the trace. If an error occurs, the span is sent to Flare alongside the error.

When no trace has started, the span will still be recorded for sending alongside a potential future error to Flare, but it won't be used for performance monitoring.

It is also possible to inject dependencies into the recorder, for example, we could inject Laravel's event dispatcher and listen for specific events:

use Spatie\FlareClient\Time\TimeHelper;

class MyQueryRecorder extends SpansRecorder
{
    public static function type(): string|RecorderType
    {
        return 'my_query';
    }

    public function __construct(
        Tracer $tracer,
        BackTracer $backTracer,
        array $config,
        protected Dispatcher $dispatcher,
    ) {
        parent::__construct($tracer, $backTracer, $config);
    }

    public function boot(): void
    {
        $this->dispatcher->listen(QueryExecuted::class, [$this, 'recordEvent']);
    }

    public function recordEvent(QueryExecuted $event): ?Span
    {
        return $this->span("Query - {$event->sql}", duration: TimeHelper::milliseconds($event->duration));
    }
}

You can configure a recorder even further by passing a configuration array alongside the recorder class:

use Spatie\FlareClient\Time\TimeHelper;

'collects' => FlareConfig::defaultCollects(
    extra: [
        CollectType::Recorders->value => [
            'recorders' => [
                MyRecorder::class => [
                    'with_traces' => true,
                    'with_errors' => true,
                    'max_items_with_errors' => 10,
                    'find_origin' => true,
                    'find_origin_threshold' => TimeHelper::milliseconds(300),
                ],
            ],
        ],
    ],
),

The following options can be configured:

  • with_traces: Whether the recorder should add spans to the trace
  • with_errors: Whether the recorder should add spans to the error
  • max_items_with_errors: The maximum number of items to be recorded when an error happens
  • find_origin: Whether the recorder should find the origin of the span (where it was started in the code)
  • find_origin_threshold: The threshold in milliseconds to find the origin of the span in nanoseconds, when null, the origin will be added for all spans

Note: Be careful with the find_origin option. It will add a lot of overhead to the span creation. It is recommended that you only use this option when you really need it.

You're able to add custom configuration options by overwriting the configure method in the recorder:

class MyQueryRecorder extends SpansRecorder
{
    protected bool $advancedMode = false;

    protected function configure(array $config): void
    {
        $this->advancedMode = $config['advanced_mode'] ?? false;
    }
}

You can now define the extra config option as such:

'collects' => FlareConfig::defaultCollects(
    extra: [
        CollectType::Recorders->value => [
            'recorders' => [
                MyRecorder::class => [
                    'advances_mode' => true,
                ],
            ],
        ],
    ],
),

If you'll need external dependencies, you can use the constructor to inject them:

public function __construct(
    Tracer $tracer,
    BackTracer $backTracer,
    array $config,
    protected MyDependency $myDependency,
) {
    parent::__construct($tracer, $backTracer, $config);
}

Creating a span events recorder

A span events recorder can be created by extending the SpanEventsRecorder class:

use Spatie\FlareClient\Recorders\SpanEventsRecorder;
use Spatie\FlareClient\Enums\RecorderType;

class MyLogRecorder extends SpanEventsRecorder
{
    public static function type(): string|RecorderType
    {
        return 'my_log';
    }

    public function record(string $message): void
    {
        $this->spanEvent($message);
    }
}

Now we'll need to register the recorder with Flare:

'collects' => FlareConfig::defaultCollects(
    extra: [
        CollectType::Recorders->value => [
            'recorders' => [
                MyLogRecorder::class => [],
            ],
        ],
    ],
),

You'll always need to provide the recorder's class as the key, and you can provide additional configuration as the value.

Now we can call our recorder:

Flare::recorder('my_log')->record("My custom log message");

While it is not possible to start traces with a spans event recorder, it is possible to configure and add external dependencies to the recorder. The same way as with the span recorder.

Creating a Span recorder by implementing the interface

Instead of extending the SpansRecorder class, you can implement the SpansRecorder contract directly:

use Spatie\FlareClient\Contracts\Recorders\SpansRecorder;
use Spatie\FlareClient\Enums\RecorderType;

class MyRecorder implements SpansRecorder
{
    public static function type(): string|RecorderType
    {
        // This is the type of recorder; it can be used to identify the recorder.
        // You can select one of the default types or create your own.
    }

    public function boot(): void
    {
        // When booting up Flare, this method will be called.
        // It is an excellent place to start the recorder and listen for events.
    }

    public function reset(): void
    {
        // Every time an error is sent, a new request is handled or a job started, all recorders will be reset
        // In this method, you can clear all the spans that have been recorded
    }

    /** @return array<Span> */
    public function getSpans(): array
    {
        // When an error happens, all spans that need to be sent to Flare should be returned here.
    }
}

The recorder defines four methods to implement, while the getSpans method provides the recorded spans when an error happens. When you also want to record spans for performance monitoring, you'll also need to add the span to the tracer as such:

public function boot(): void
{
    $this->tracer->span('My custom span', function () {
        // Do an operation
    });
}

Or you can use the startSpan and endSpan methods to manually start and end a span:

$span = $this->tracer->startSpan('My custom span');

// Do an operation

$this->tracer->endSpan($span);

If you've created a span manually, it can be added as such:

use Spatie\FlareClient\Spans\Span;
use Spatie\FlareClient\Time\TimeHelper;

$span = new Span(
    traceId: $tracer->currentTraceId(),
    spanId: $tracer->ids()->span(),
    parentSpanId: $tracer->currentSpanId(),
    name: 'My custom span',
    start: TimeHelper::dateTimeToNano($time),
    duration: TimeHelper::milliseconds(300),
);

$this->tracer->addSpan($span);

Creating a SpanEvents recorder by implementing the interface

Instead of extending the SpanEventsRecorder class, you can implement the SpanEventsRecorder contract directly:

use Spatie\FlareClient\Contracts\Recorders\SpanEventsRecorder;
use Spatie\FlareClient\Enums\RecorderType;

class MySpanEventsRecorder implements SpanEventsRecorder
{
    public static function type(): string|RecorderType
    {
        // This is the type of recorder; it can be used to identify the recorder.
        // You can select one of the default types or create your own.
    }

    public function boot(): void
    {
        // When booting up Flare, this method will be called.
        // It is an excellent place to start the recorder and listen for events.
    }

    public function reset(): void
    {
        // Every time an error is sent, a new request is handled or a job started, all recorders will be reset
        // In this method, you can clear all the span events that have been recorded
    }

    public function getSpanEvents(): array
    {
        // When an error happens, all span events that need to be sent to Flare should be returned here.
    }
}

The recorder defines four methods to implement, while the getSpanEvents method provides the recorded span events when an error happens. When you also want to record span events for performance monitoring, you'll also need to add the span events to the current span as such:


use Spatie\FlareClient\Spans\SpanEvent;

public function boot(): void
{
    $span = $this->tracer->currentSpan();
    
    if ($span === null) {
        return;
    }
    
    $span->addEvent(new SpanEvent(
        name: 'My custom span event',
        time: TimeHelper::now(),
        attributes: [
            'key' => 'value',
        ],
    ));
}
Server info Stacktrace arguments
  • On this page
  • Starting a trace
  • Creating spans
  • Creating span events
  • Ending a trace
  • Propagation
  • Using and creating recorders
  • Creating a Span recorder
  • Creating a span events recorder
  • Creating a Span recorder by implementing the interface
  • Creating a SpanEvents recorder by implementing the interface

Catch errors and fix slowdowns with Flare, the full-stack application monitoring platform for Laravel, PHP & JavaScript.

  • Platform
  • Error Tracking
  • Performance Monitoring
  • Pricing
  • Support
  • Resources
  • Insights
  • Newsletter
  • Changelog
  • Documentation
  • Affiliate program
  • uptime status badge Service status
  • Terms of use
  • DPA
  • Privacy & cookie Policy
Made in by
Flare