ForgeOps-Tracker

 view release on metacpan or  search on metacpan

lib/ForgeOps/Tracker/Integrations/PSGIPerformance.pm  view on Meta::CPAN

package ForgeOps::Tracker::Integrations::PSGIPerformance;

use strict;
use warnings;
use parent 'Plack::Middleware';
use Time::HiRes ();
use ForgeOps::Tracker;

# PSGI/Plack middleware that times every request end to end and reports it, bucketed by
# transaction name, for a dashboard widget on a project's Performance page (so it can show which
# parts of your app are actually slow, not just which ones raise). A separate, independent
# middleware from Integrations::PSGI (error reporting), not layered onto it, since performance
# tracking runs regardless of whether error reporting is even configured: the same
# several-genuinely-independent-mechanisms split every other client's own integrations already
# use.
#
#   use Plack::Builder;
#   builder {
#       enable '+ForgeOps::Tracker::Integrations::PSGI';            # error reporting
#       enable '+ForgeOps::Tracker::Integrations::PSGIPerformance'; # performance monitoring
#       $app;
#   };
#
# Wraps the downstream app call in an eval, records the duration either way, then re-raises the
# original exception unchanged, the same shape Integrations::PSGI's own call already uses to keep
# Plack's own error handling exactly as it would be without this middleware installed.
#
# Also starts a trace for the request and finishes it here (root span named like the
# transaction), so a slow request's own breakdown reaches /spans; see ForgeOps::Tracker::span.
# There is no automatic database or HTTP span (no DBI or HTTP::Tiny hook here), so add those by
# hand (ForgeOps::Tracker::http_span for an outgoing HTTP call, which also hands it the
# traceparent header to send).
#
# The trace continues the caller's when the request arrived with a usable W3C `traceparent` header
# (see ForgeOps::Tracker::TraceParent), and its trace id exists even with track_tracing off, since
# every error reported during the request carries it. An error that escapes the app is remembered
# with the request's context (ForgeOps::Tracker::snapshot_onto) before the trace is finished, so
# Integrations::PSGI, enabled outside this middleware, still reports it with the trace id.
#
# The transaction name is "<HTTP method> <path>", the raw PATH_INFO, not a matched route pattern:
# plain PSGI has no route-matching concept of its own (a bare $env hash) to read one from
# generically, the same reasoning Go's own net/http integration documents for itself. A Dancer2
# app should use Integrations::Dancer2Performance instead, which reads the matched route's own
# pattern and so avoids a distinct id exploding into its own separate transaction. For the same
# reason an error reported during a plain PSGI request carries the trace id but no transaction
# name or endpoint (a raw path is neither), unless the app names the request itself with
# ForgeOps::Tracker::set_request_route.
sub call {
    my ($self, $env) = @_;

    ForgeOps::Tracker::clear_breadcrumbs();
    ForgeOps::Tracker::start_trace($env->{HTTP_TRACEPARENT});

    my $started_at = Time::HiRes::time;
    my @response = eval { @{ $self->app->($env) } };
    my $error = $@;
    my $duration_ms = (Time::HiRes::time - $started_at) * 1000;

    my $transaction_name = "$env->{REQUEST_METHOD} $env->{PATH_INFO}";
    ForgeOps::Tracker::record_performance($transaction_name, $duration_ms);

    # Also an automatic "controller" breadcrumb, recorded on the way out (before an exception is
    # re-raised below), so it is already in the trail by the time Integrations::PSGI, enabled
    # outside this middleware as in the synopsis above, reports that exception. Recorded whether or
    # not the request raised, since eval above catches both.
    ForgeOps::Tracker::add_breadcrumb(
        $transaction_name,
        category => 'controller',
        level    => $error ? 'error' : 'info',
        data     => { $error ? () : (status => $response[0]), path => $env->{PATH_INFO} },
    );

    ForgeOps::Tracker::snapshot_onto($error) if $error;
    ForgeOps::Tracker::finish_trace($transaction_name, $started_at, $duration_ms);

    die $error if $error;
    return \@response;
}

1;



( run in 1.352 second using v1.01-cache-2.11-cpan-16aabcbfbcb )