File Coverage

blib/lib/Plack/Middleware/Statsd.pm
Criterion Covered Total %
statement 95 99 95.9
branch 30 46 65.2
condition 5 10 50.0
subroutine 19 19 100.0
pod 2 2 100.0
total 151 176 85.8


line stmt bran cond sub pod time code
1             package Plack::Middleware::Statsd;
2              
3             # ABSTRACT: send statistics to statsd
4              
5             # RECOMMEND PREREQ: Net::Statsd::Tiny v0.3.0
6             # RECOMMEND PREREQ: HTTP::Status 6.16
7             # RECOMMEND PREREQ: List::Util::XS
8             # RECOMMEND PREREQ: Ref::Util::XS
9              
10 5     5   257689 use v5.14;
  5         30  
11 5     5   26 use warnings;
  5         10  
  5         149  
12              
13 5     5   24 use parent qw/ Plack::Middleware /;
  5         10  
  5         29  
14              
15 5     5   26310 use List::Util qw/ first /;
  5         11  
  5         480  
16 5     5   33 use Plack::Util;
  5         10  
  5         173  
17             use Plack::Util::Accessor
18 5     5   28 qw/ client sample_rate histogram increment set_add catch_errors /;
  5         11  
  5         44  
19 5     5   3044 use Ref::Util qw/ is_coderef /;
  5         8250  
  5         365  
20 5     5   48 use Time::HiRes;
  5         13  
  5         36  
21 5     5   3018 use Try::Tiny;
  5         8519  
  5         5794  
22              
23             our $VERSION = 'v0.6.3';
24              
25             # Note: You may be able to omit the client if there is a client
26             # defined in the environment hash at C, and the
27             # L, L and L are set. But that
28             # is a strange case and unsupported.
29              
30             sub prepare_app {
31 5     5 1 6929 my ($self) = @_;
32              
33 5 50       42 if ( my $client = $self->client ) {
34 5         265 foreach my $init (
35             [qw/ histogram timing_ms timing /],
36             [qw/ increment increment /],
37             [qw/ set_add set_add /],
38             )
39             {
40 15         114 my ( $attr, @methods ) = @$init;
41 15 100       46 next if defined $self->$attr;
42 14     16   126 my $method = first { $client->can($_) } @methods;
  16         137  
43 14 100       152 warn "No $attr method found for client " . ref($client)
44             unless defined $method;
45             $self->$attr(
46             sub {
47 49 50   49   110 return unless defined $method;
48 49         127 my ($env, @args) = @_;
49             try {
50 49         2991 $client->$method( grep { defined $_ } @args );
  116         338  
51             }
52             catch {
53 3         73 my ($e) = $_;
54 3 50       21 if (my $logger = $env->{'psgix.logger'}) {
55 3         16 $logger->( { message => $e, level => 'error' } );
56             }
57             else {
58 0         0 $env->{'psgi.errors'}->print($e);
59             }
60 49         268 };
61              
62             }
63 14         111 );
64             }
65             }
66              
67 5 100   14   67 if (my $attr = first { !is_coderef($self->$_) } qw/ histogram increment set_add /) {
  14         62  
68 1         17 die "$attr is not a coderef";
69             }
70              
71 4 100       35 if ( my $catch = $self->catch_errors ) {
72              
73 1 50       8 unless ( is_coderef($catch) ) {
74              
75             $self->catch_errors(
76             sub {
77 1     1   3 my ( $env, $error ) = @_;
78 1 50       4 if ( my $logger = $env->{'psgix.logger'} ) {
79 1         5 $logger->( { level => 'error', message => $error } );
80             }
81             else {
82 0         0 $env->{'psgi.errors'}->print($error);
83             }
84 1         5 my $message = 'Internal Error';
85 1         5 return [ 500, [ 'Content-Type' => 'text/plain', 'Content-Length' => length($message) ], [$message] ];
86             }
87 1         5 );
88              
89             }
90              
91             }
92             }
93              
94             sub call {
95 6     6 1 249702 my ( $self, $env ) = @_;
96              
97 6   33     44 my $client = ( $env->{'psgix.monitor.statsd'} //= $self->client );
98              
99 6         72 my $start = [Time::HiRes::gettimeofday];
100 6         12 my $res;
101              
102 6 100       21 if (my $catch = $self->catch_errors) {
103             try {
104 1     1   73 $res = $self->app->($env);
105             }
106             catch {
107 1     1   114 $res = $catch->( $env, $_ );
108             }
109 1         10 }
110             else {
111 5         44 $res = $self->app->($env);
112             }
113              
114             return Plack::Util::response_cb(
115             $res,
116             sub {
117 6 50   6   118 return unless $client;
118              
119 6         46 my ($res) = @_;
120              
121 6         39 my $rate = $self->sample_rate;
122              
123 6 50 33     55 $rate = undef if ( defined $rate ) && ( $rate >= 1 );
124              
125 6         18 my $histogram = $self->histogram;
126 6         34 my $increment = $self->increment;
127 6         33 my $set_add = $self->set_add;
128              
129 6         37 my $elapsed = Time::HiRes::tv_interval($start);
130              
131 6         142 $histogram->( $env, 'psgi.response.time', $elapsed * 1000, $rate );
132              
133 6 50       186 if ( defined $env->{CONTENT_LENGTH} ) {
134             $histogram->( $env,
135             'psgi.request.content-length',
136 6         23 $env->{CONTENT_LENGTH}, $rate
137             );
138             }
139              
140 6 50       134 if ( my $method = $env->{REQUEST_METHOD} ) {
141 6         27 $increment->( $env, 'psgi.request.method.' . $method, $rate );
142             }
143              
144 6 100       120 if ( my $type = _mime_type_to_metric( $env->{CONTENT_TYPE} ) ) {
145 1         4 $increment->( $env, 'psgi.request.content-type.' . $type, $rate );
146             }
147              
148             $set_add->( $env, 'psgi.request.remote_addr', $env->{REMOTE_ADDR} )
149 6 50       59 if $env->{REMOTE_ADDR};
150              
151 6         121 $set_add->( $env, 'psgi.worker.pid', $$ );
152              
153 6         113 my $h = Plack::Util::headers( $res->[1] );
154              
155             my $xsendfile =
156             $env->{'plack.xsendfile.type'}
157             || $ENV{HTTP_X_SENDFILE_TYPE}
158 6   50     172 || 'X-Sendfile';
159              
160 6 50       51 if ( $h->exists($xsendfile) ) {
161 0         0 $increment->( $env, 'psgi.response.x-sendfile', $rate );
162             }
163              
164 6 50       237 if ( $h->exists('Content-Length') ) {
165 6   100     173 my $length = $h->get('Content-Length') || 0;
166 6         212 $histogram->( $env, 'psgi.response.content-length', $length, $rate );
167             }
168              
169 6 50       141 if ( my $type = _mime_type_to_metric( $h->get('Content-Type') ) ) {
170 6         21 $increment->( $env, 'psgi.response.content-type.' . $type, $rate );
171             }
172              
173 6         144 $increment->( $env, 'psgi.response.status.' . $res->[0], $rate );
174              
175 6 50       124 if (
    50          
176             $env->{'psgix.harakiri.supported'}
177             ? $env->{'psgix.harakiri'}
178             : $env->{'psgix.harakiri.commit'}
179             )
180             {
181 0         0 $increment->( $env, 'psgix.harakiri' ); # rate == 1
182             }
183              
184 6 50       54 $client->flush if $client->can('flush');
185              
186 6         59 return;
187             }
188 6         1600 );
189              
190             }
191              
192             sub _mime_type_to_metric {
193 12 100   12   233 my $type = $_[0] or return;
194 7         56 return $type =~ s#\.#-#gr =~ s#/#.#gr =~ s/;.*$//r;
195             }
196              
197              
198             1;
199              
200             __END__