File Coverage

blib/lib/Plack/Middleware/Statsd.pm
Criterion Covered Total %
statement 95 98 96.9
branch 27 42 64.2
condition 5 10 50.0
subroutine 17 17 100.0
pod 2 2 100.0
total 146 169 86.3


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