Catalyst-Plugin-PrometheusTiny

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


    * refresh README, fixup META resources

0.005 - 2021-06-08

    * add META resources
    * add optional controller and action labels to the metrics

0.004 - 2021-06-06

    * allow simple configuration of endpoint path
    * bump deps on Prometheus::Tiny and ::Shared
    * more tests

0.003 - 2021-06-05

    * bump min Perl version to 5.10.1 due to upstream deps

0.002 - 2021-06-05

    * remove a regexp modifier added in Perl 5.22

MANIFEST  view on Meta::CPAN

Changes
lib/Catalyst/Plugin/PrometheusTiny.pm
maint/Makefile.PL.include
Makefile.PL
MANIFEST			This list of files
t/default_config.t
t/endpoint.t
t/include_action_labels.t
t/lib/TestApp.pm
t/lib/TestApp/Controller/Root.pm
t/lib/TestApp/Helper.pm
t/prometheus.t
META.yml                                 Module YAML meta-data (added by MakeMaker)
META.json                                Module JSON meta-data (added by MakeMaker)
README                                   README file (added by Distar)
LICENSE                                  LICENSE file (added by Distar)

README  view on Meta::CPAN

    Once your app has served from requests you can fetch request/response
    metrics:

        curl http://$myappaddress/metrics

DESCRIPTION
    This plugin integrates Prometheus::Tiny::Shared with your Catalyst app,
    providing some default metrics for requests and responses, with the
    ability to easily add further metrics to your app. A default controller
    is included which makes the metrics available via the configured
    "endpoint", though this can be disabled if you prefer to add your own
    controller action.

    See Prometheus::Tiny for more details of the kind of metrics supported.

    The following metrics are included by default:

        http_request_duration_seconds => {
            help => 'Request durations in seconds',
            type => 'histogram',
        },

README  view on Meta::CPAN

  prometheus
        sub my_action {
            my ( $self, $c ) = @_;

            $c->prometheus->inc(...);
        }

    Returns the "Prometheus::Tiny::Shared" instance.

CONFIGURATION
  endpoint
    The endpoint from which metrics are served. Defaults to "/metrics".

  filename
    It is recommended that this is set to a directory on a memory-backed
    filesystem. See "filename" in Prometheus::Tiny::Shared for details and
    default value.

  ignore_path_regex
        ignore_path_regex => '^(healthcheck|foobar)'

    A regular expression against which "$c->request->path" is checked, and

README  view on Meta::CPAN

            },
            # more...
        }

    See "declare" in Prometheus::Tiny. Declare extra metrics to be added to
    those included with the plugin.

  no_default_controller
        no_default_controller => 0      # default

    If set to a true value then the default "endpoint" will not be added,
    and you will need to add your own controller action for exporting the
    metrics. Something like:

        package MyApp::Controller::Stats;

        sub begin : Private { }
        sub end  : Private  { }

        sub index : Path Args(0) {
            my ( $self, $c ) = @_;

lib/Catalyst/Plugin/PrometheusTiny.pm  view on Meta::CPAN

            help    => 'Response sizes in bytes',
            type    => 'histogram',
            buckets => [ 1, 50, 100, 1_000, 50_000, 500_000, 1_000_000 ],
        }
    },
};

my ($prometheus,               # instance
    $ignore_path_regexp,       # set from config
    $include_action_labels,    # set from config
    $metrics_endpoint,         # set from config with default
    $no_default_controller,    # set from config
    $request_path              # derived from $metrics_endpoint
);

# for testing
sub _clear_prometheus {
    undef $prometheus;
}

sub prometheus {
    my $c = shift;
    $prometheus //= do {
        my $config = Catalyst::Utils::merge_hashes(
            $defaults,
            $c->config->{'Plugin::PrometheusTiny'} // {}
        );

        $include_action_labels = $config->{include_action_labels};

        $metrics_endpoint = $config->{endpoint};
        if ($metrics_endpoint) {
            if ( $metrics_endpoint !~ m|^/| ) {
                Carp::croak
                  "Plugin::PrometheusTiny endpoint '$metrics_endpoint' does not begin with '/'";
            }
        }
        else {
            $metrics_endpoint = '/metrics';
        }

        $request_path = $metrics_endpoint;
        $request_path =~ s|^/||;

        $ignore_path_regexp = $config->{ignore_path_regexp};
        if ($ignore_path_regexp) {
            $ignore_path_regexp = qr/$ignore_path_regexp/
              unless 'Regexp' eq ref $ignore_path_regexp;
        }

        $no_default_controller = $config->{no_default_controller};

lib/Catalyst/Plugin/PrometheusTiny.pm  view on Meta::CPAN


before setup_components => sub {
    my $class = shift;

    # initialise prometheus instance pre-fork and setup lexicals
    $class->prometheus;

    return
      if $class->config->{'Plugin::PrometheusTiny'}{no_default_controller};

    # Paranoia, as we're going to eval $metrics_endpoint
    if ( $metrics_endpoint =~ s|[^-A-Za-z0-9\._~/]||g ) {
        $class->log->warn(
            "Plugin::PrometheusTiny unsafe characters removed from endpoint");
    }

    $class->log->info(
        "Plugin::PrometheusTiny metrics endpoint installed at $metrics_endpoint"
    );

    eval qq|

        package Catalyst::Plugin::PrometheusTiny::Controller;
        use base 'Catalyst::Controller';

        sub begin : Private { }
        sub end : Private   { }

        sub metrics : Path($metrics_endpoint) Args(0) {
            my ( \$self, \$c ) = \@_;
            my \$res = \$c->res;
            \$res->content_type("text/plain");
            \$res->output( \$c->prometheus->format );
        }
        1;

    | or do {
        Carp::croak("Plugin::PrometheusTiny controller eval failed: $@");
    };

lib/Catalyst/Plugin/PrometheusTiny.pm  view on Meta::CPAN


Once your app has served from requests you can fetch request/response metrics:

    curl http://$myappaddress/metrics

=head1 DESCRIPTION

This plugin integrates L<Prometheus::Tiny::Shared> with your L<Catalyst> app,
providing some default metrics for requests and responses, with the ability
to easily add further metrics to your app. A default controller is included
which makes the metrics available via the configured L</endpoint>, though this
can be disabled if you prefer to add your own controller action.

See L<Prometheus::Tiny> for more details of the kind of metrics supported.

The following metrics are included by default:

    http_request_duration_seconds => {
        help => 'Request durations in seconds',
        type => 'histogram',
    },

lib/Catalyst/Plugin/PrometheusTiny.pm  view on Meta::CPAN

    sub my_action {
        my ( $self, $c ) = @_;

        $c->prometheus->inc(...);
    }

Returns the C<Prometheus::Tiny::Shared> instance.

=head1 CONFIGURATION

=head2 endpoint

The endpoint from which metrics are served. Defaults to C</metrics>.

=head2 filename

It is recommended that this is set to a directory on a memory-backed
filesystem. See L<Prometheus::Tiny::Shared/filename> for details and default
value.

=head2 ignore_path_regex

    ignore_path_regex => '^(healthcheck|foobar)'

lib/Catalyst/Plugin/PrometheusTiny.pm  view on Meta::CPAN

        # more...
    }

See L<Prometheus::Tiny/declare>. Declare extra metrics to be added to those
included with the plugin.

=head2 no_default_controller

    no_default_controller => 0      # default

If set to a true value then the default L</endpoint> will not be
added, and you will need to add your own controller action for exporting the
metrics. Something like:

    package MyApp::Controller::Stats;

    sub begin : Private { }
    sub end  : Private  { }

    sub index : Path Args(0) {
        my ( $self, $c ) = @_;

t/endpoint.t  view on Meta::CPAN

use warnings;
use strict;
use lib 'lib', 't/lib';

use Test::More;
use Test::Deep;
use TestApp::Helper;

TestApp::Helper::run(
    { endpoint => '/testme' },
    '/testme',
    superbagof('http_requests_total{code="200",method="GET"} 1')
);

done_testing;

t/lib/TestApp/Helper.pm  view on Meta::CPAN

use strict;

use Test::More;
use Test::Deep;

use HTTP::Request::Common;
use Plack::Test;
use TestApp;

sub get_metrics {
    my ( $test, $endpoint ) = @_;

    my $res = $test->request( GET $endpoint );
    return [ grep { $_ !~ /^#/ } split /\n/, $res->content ];
}

sub run {
    my ( $config, $endpoint, $expect ) = @_;

    TestApp->config( 'Plugin::PrometheusTiny' => $config );
    TestApp->setup;
    my $app  = TestApp->psgi_app;
    my $test = Plack::Test->create($app);

    my $got = get_metrics( $test, $endpoint );
    cmp_deeply $got, [], "We start with no metrics"
      or diag explain $got;

    ok my $res = $test->request( GET "/" ), "GET /";
    is $res->content, "Hello World", "... and content is as expected";

    $got = get_metrics( $test, $endpoint );
    cmp_deeply $got,
      $expect,
      "... and metrics are as expected"
      or diag explain $got;
}

1;



( run in 0.346 second using v1.01-cache-2.11-cpan-b61123c0432 )