File Coverage

blib/lib/Plack/Middleware/Statsd.pm
Criterion Covered Total %
statement 133 137 97.0
branch 37 54 68.5
condition 8 15 53.3
subroutine 23 23 100.0
pod 2 2 100.0
total 203 231 87.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             # RECOMMEND PREREQ: Crypt::Mac::HMAC 0.089
10              
11 5     5   2081132 use v5.20;
  5         19  
12 5     5   29 use warnings;
  5         10  
  5         320  
13              
14 5     5   32 use parent qw/ Plack::Middleware /;
  5         11  
  5         47  
15              
16 5     5   28132 use List::Util qw/ first /;
  5         11  
  5         462  
17 5     5   3048 use Module::Load;
  5         8077  
  5         41  
18 5     5   432 use Plack::Util;
  5         9  
  5         200  
19             use Plack::Util::Accessor
20 5     5   31 qw/ client sample_rate histogram increment set_add secure_set_add secure_set_key secure_set_hash catch_errors /;
  5         10  
  5         60  
21 5     5   3166 use Ref::Util qw/ is_coderef /;
  5         13944  
  5         526  
22 5     5   39 use Time::HiRes;
  5         9  
  5         43  
23 5     5   3018 use Try::Tiny;
  5         9982  
  5         417  
24              
25 5     5   2777 use experimental qw/ postderef signatures /;
  5         14675  
  5         59  
26              
27             our $VERSION = 'v0.9.1';
28              
29             # Note: You may be able to omit the client if there is a client
30             # defined in the environment hash at C, and the
31             # L, L and L are set. But that
32             # is a strange case and unsupported.
33              
34 5     5 1 447037 sub prepare_app($self) {
  5         13  
  5         8  
35              
36 5 50       28 if ( my $client = $self->client ) {
37 5         329 foreach my $init (
38             [qw/ histogram timing_ms timing /],
39             [qw/ increment increment /],
40             [qw/ set_add set_add /],
41             )
42             {
43 15         107 my ( $attr, @methods ) = $init->@*;
44 15 100       48 next if defined $self->$attr;
45 14     16   138 my $method = first { $client->can($_) } @methods;
  16         122  
46 14 100       111 warn "No $attr method found for client " . ref($client)
47             unless defined $method;
48 56         108 $self->$attr(
49 56     56   180 sub($env, @args) {
  56         174  
  56         86  
50 56 50       152 return unless defined $method;
51             try {
52 56         4708 $client->$method( grep { defined $_ } @args );
  133         464  
53             }
54             catch {
55 2         47 my ($e) = $_;
56 2 50       8 if (my $logger = $env->{'psgix.logger'}) {
57 2         12 $logger->( { message => $e, level => 'error' } );
58             }
59             else {
60 0         0 $env->{'psgi.errors'}->print($e);
61             }
62 56         459 };
63              
64             }
65 14         70 );
66             }
67              
68 5   50     45 my $hash = $self->secure_set_hash || "SHA1";
69 5 100       70 if ( my $key = $self->secure_set_key ) {
70 1         9 load Crypt::Mac::HMAC;
71 6         10 $self->secure_set_add(
72 6     6   12 sub( $env, $metric, $string ) {
  6         12  
  6         12  
  6         10  
73 6         194 my $obscure = Crypt::Mac::HMAC::hmac_b64u( $hash, $key, $string );
74 6         33 $self->set_add->( $env, $metric, $obscure );
75             }
76 1         7431 );
77             }
78              
79             }
80              
81 5 100   14   48 if (my $attr = first { !is_coderef($self->$_) } qw/ histogram increment set_add /) {
  14         98  
82 1         13 die "$attr is not a coderef";
83             }
84              
85 4 100       50 if ( my $catch = $self->catch_errors ) {
86              
87 1 50       9 unless ( is_coderef($catch) ) {
88              
89 1         2 $self->catch_errors(
90 1     1   2 sub( $env, $error ) {
  1         3  
  1         2  
91 1 50       5 if ( my $logger = $env->{'psgix.logger'} ) {
92 1         7 $logger->( { level => 'error', message => $error } );
93             }
94             else {
95 0         0 $env->{'psgi.errors'}->print($error);
96             }
97 1         6 my $message = 'Internal Error';
98 1         25 return [ 500, [ 'Content-Type' => 'text/plain', 'Content-Length' => length($message) ], [$message] ];
99             }
100 1         17 );
101              
102             }
103              
104             }
105             }
106              
107 7     7 1 404937 sub call ( $self, $env ) {
  7         25  
  7         16  
  7         14  
108              
109 7   33     112 my $client = ( $env->{'psgix.monitor.statsd'} //= $self->client );
110 7         89 my $secure = $self->secure_set_add;
111              
112 7 100   1   72 $env->{'psgix.monitor.statsd_secure_set_add'} = sub { $secure->( $env, @_ ) } if defined $secure;
  1         101  
113              
114 7         60 my $start = [Time::HiRes::gettimeofday];
115 7         17 my $res;
116              
117 7 100       28 if (my $catch = $self->catch_errors) {
118             try {
119 1     1   100 $res = $self->app->($env);
120             }
121             catch {
122 1     1   156 $res = $catch->( $env, $_ );
123             }
124 1         16 }
125             else {
126 6         68 $res = $self->app->($env);
127             }
128              
129 7         16 return Plack::Util::response_cb(
130             $res,
131 7     7   96 sub($res) {
  7         13  
132 7 50       26 return unless $client;
133              
134 7         29 my $rate = $self->sample_rate;
135              
136 7 50 33     73 $rate = undef if ( defined $rate ) && ( $rate >= 1 );
137              
138 7         23 my $histogram = $self->histogram;
139 7         47 my $increment = $self->increment;
140 7         42 my $set_add = $self->set_add;
141              
142 7         53 my $elapsed = Time::HiRes::tv_interval($start);
143              
144 7         170 $histogram->( $env, 'psgi.response.time', $elapsed * 1000, $rate );
145              
146 7 50       253 if ( defined $env->{CONTENT_LENGTH} ) {
147             $histogram->( $env,
148             'psgi.request.content-length',
149 7         28 $env->{CONTENT_LENGTH}, $rate
150             );
151             }
152              
153 7 50       247 if ( my $method = $env->{REQUEST_METHOD} ) {
154 7 100       51 $method = "other" unless $method =~ /^\w+$/a;
155 7         56 $increment->( $env, 'psgi.request.method.' . $method, $rate );
156             }
157              
158 7 100       196 if ( my $type = _mime_type_to_metric( $env->{CONTENT_TYPE} ) ) {
159 1         7 $increment->( $env, 'psgi.request.content-type.' . $type, $rate );
160             }
161              
162             $secure->( $env, 'psgi.request.remote_addr', $env->{REMOTE_ADDR} )
163 7 50 66     78 if defined($secure) && $env->{REMOTE_ADDR};
164              
165 7         167 $set_add->( $env, 'psgi.worker.pid', $$ );
166              
167 7         180 my $h = Plack::Util::headers( $res->[1] );
168              
169             my $xsendfile =
170             $env->{'plack.xsendfile.type'}
171             || $ENV{HTTP_X_SENDFILE_TYPE}
172 7   50     265 || 'X-Sendfile';
173              
174 7 50       53 if ( $h->exists($xsendfile) ) {
175 0         0 $increment->( $env, 'psgi.response.x-sendfile', $rate );
176             }
177              
178 7 50       277 if ( $h->exists('Content-Length') ) {
179 7   100     204 my $length = $h->get('Content-Length') || 0;
180 7         239 $histogram->( $env, 'psgi.response.content-length', $length, $rate );
181             }
182              
183 7 50       216 if ( my $type = _mime_type_to_metric( $h->get('Content-Type') ) ) {
184 7         26 $increment->( $env, 'psgi.response.content-type.' . $type, $rate );
185             }
186              
187 7         254 $increment->( $env, 'psgi.response.status.' . $res->[0], $rate );
188              
189 7 50       227 if (
    50          
190             $env->{'psgix.harakiri.supported'}
191             ? $env->{'psgix.harakiri'}
192             : $env->{'psgix.harakiri.commit'}
193             )
194             {
195 0         0 $increment->( $env, 'psgix.harakiri' ); # rate == 1
196             }
197              
198 7 50       46 $client->flush if $client->can('flush');
199              
200 7         101 return;
201             }
202 7         2066 );
203              
204             }
205              
206 14     14   257 sub _mime_type_to_metric( $type = undef ) {
  14         63  
  14         35  
207 14 100       54 return unless $type;
208 8 50       83 return unless $type =~ m#^\w+/(?:\w+[\-\+])*\w+(?: *;\w+=\w+)?#a;
209 8         104 return $type =~ s#\.#-#gr =~ s#/#.#gr =~ s/;.*$//r;
210             }
211              
212              
213             1;
214              
215             __END__