File Coverage

blib/lib/Plack/Middleware/Statsd.pm
Criterion Covered Total %
statement 137 141 97.1
branch 38 56 67.8
condition 9 16 56.2
subroutine 24 24 100.0
pod 2 2 100.0
total 210 239 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              
10 5     5   1217326 use v5.20;
  5         19  
11 5     5   24 use warnings;
  5         9  
  5         301  
12              
13 5     5   23 use parent qw/ Plack::Middleware /;
  5         8  
  5         33  
14              
15 5     5   29723 use Digest::SHA 5.96 qw/ hmac_sha256_base64 /;
  5         12240  
  5         539  
16 5     5   35 use List::Util qw/ first /;
  5         9  
  5         310  
17 5     5   24 use Plack::Util;
  5         7  
  5         169  
18             use Plack::Util::Accessor
19 5     5   19 qw/ client sample_rate histogram increment set_add secure_set_add secure_set_key catch_errors /;
  5         7  
  5         30  
20 5     5   2898 use Ref::Util qw/ is_coderef /;
  5         10374  
  5         364  
21 5     5   30 use Scalar::Util qw/ weaken /;
  5         8  
  5         216  
22 5     5   20 use Time::HiRes;
  5         8  
  5         38  
23 5     5   2521 use Try::Tiny;
  5         7405  
  5         297  
24              
25 5     5   1831 use experimental qw/ postderef signatures /;
  5         9469  
  5         23  
26              
27             our $VERSION = 'v0.9.3';
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 323656 sub prepare_app($self) {
  5         23  
  5         7  
35              
36 5 50       28 if ( my $client = $self->client ) {
37 5         214 foreach my $init (
38             [qw/ histogram timing_ms timing /],
39             [qw/ increment increment /],
40             [qw/ set_add set_add /],
41             [qw/ secure_set_add secure_set_add /],
42             )
43             {
44 20         87 my ( $attr, @methods ) = $init->@*;
45 20 100       49 next if defined $self->$attr;
46 19     21   126 my $method = first { $client->can($_) } @methods;
  21         113  
47 19 100 100     141 warn "No $attr method found for client " . ref($client)
48             unless defined $method || $attr eq "secure_set_add";
49 58         60 $self->$attr(
50 58     58   80 sub($env, @args) {
  58         107  
  58         57  
51 58 100       81 return unless defined $method;
52             try {
53 56         2407 $client->$method( grep { defined $_ } @args );
  133         307  
54             }
55             catch {
56 2         37 my ($e) = $_;
57 2 50       6 if (my $logger = $env->{'psgix.logger'}) {
58 2         7 $logger->( { message => $e, level => 'error' } );
59             }
60             else {
61 0         0 $env->{'psgi.errors'}->print($e);
62             }
63 56         295 };
64              
65             }
66 19         62 );
67             }
68              
69 5 50       54 unless ( $client->can("secure_set_add") ) {
70 5 100       34 if ( my $key = $self->secure_set_key ) {
71 6         9 $self->secure_set_add(
72 6     6   10 sub( $env, $metric, $string ) {
  6         7  
  6         9  
  6         7  
73 6         74 my $obscure = hmac_sha256_base64( $string, $key );
74 6         17 $self->set_add->( $env, $metric, $obscure );
75             }
76 1         6 );
77             }
78             }
79              
80             }
81              
82 5 100   14   32 if (my $attr = first { !is_coderef($self->$_) } qw/ histogram increment set_add /) {
  14         43  
83 1         14 die "$attr is not a coderef";
84             }
85              
86 4 100       39 if ( my $catch = $self->catch_errors ) {
87              
88 1 50       6 unless ( is_coderef($catch) ) {
89              
90 1         1 $self->catch_errors(
91 1     1   2 sub( $env, $error ) {
  1         2  
  1         1  
92 1 50       4 if ( my $logger = $env->{'psgix.logger'} ) {
93 1         4 $logger->( { level => 'error', message => $error } );
94             }
95             else {
96 0         0 $env->{'psgi.errors'}->print($error);
97             }
98 1         3 my $message = 'Internal Error';
99 1         6 return [ 500, [ 'Content-Type' => 'text/plain', 'Content-Length' => length($message) ], [$message] ];
100             }
101 1         4 );
102              
103             }
104              
105             }
106             }
107              
108 7     7 1 220792 sub call ( $self, $env ) {
  7         16  
  7         13  
  7         10  
109              
110 7   33     117 my $client = ( $env->{'psgix.monitor.statsd'} //= $self->client );
111 7         67 my $secure = $self->secure_set_add;
112              
113 7 50       51 if ( defined $secure ) {
114 7         18 weaken( my $ref = $env );
115 7     1   35 $env->{'psgix.monitor.statsd_secure_set_add'} = sub { $secure->( $ref, @_ ) };
  1         70  
116             }
117              
118              
119 7         41 my $start = [Time::HiRes::gettimeofday];
120 7         15 my $res;
121              
122 7 100       24 if (my $catch = $self->catch_errors) {
123             try {
124 1     1   73 $res = $self->app->($env);
125             }
126             catch {
127 1     1   90 $res = $catch->( $env, $_ );
128             }
129 1         9 }
130             else {
131 6         60 $res = $self->app->($env);
132             }
133              
134 7         10 return Plack::Util::response_cb(
135             $res,
136 7     7   83 sub($res) {
  7         19  
137 7 50       29 return unless $client;
138              
139 7         19 my $rate = $self->sample_rate;
140              
141 7 50 33     75 $rate = undef if ( defined $rate ) && ( $rate >= 1 );
142              
143 7         20 my $histogram = $self->histogram;
144 7         32 my $increment = $self->increment;
145 7         28 my $set_add = $self->set_add;
146              
147 7         37 my $elapsed = Time::HiRes::tv_interval($start);
148              
149 7         132 $histogram->( $env, 'psgi.response.time', $elapsed * 1000, $rate );
150              
151 7 50       172 if ( defined $env->{CONTENT_LENGTH} ) {
152             $histogram->( $env,
153             'psgi.request.content-length',
154 7         18 $env->{CONTENT_LENGTH}, $rate
155             );
156             }
157              
158 7 50       128 if ( my $method = $env->{REQUEST_METHOD} ) {
159 7 100       41 $method = "other" unless $method =~ /^\w+$/a;
160 7         19 $increment->( $env, 'psgi.request.method.' . $method, $rate );
161             }
162              
163 7 100       113 if ( my $type = _mime_type_to_metric( $env->{CONTENT_TYPE} ) ) {
164 1         3 $increment->( $env, 'psgi.request.content-type.' . $type, $rate );
165             }
166              
167             $secure->( $env, 'psgi.request.remote_addr', $env->{REMOTE_ADDR} )
168 7 50 33     70 if defined($secure) && $env->{REMOTE_ADDR};
169              
170 7         76 $set_add->( $env, 'psgi.worker.pid', $$ );
171              
172 7         96 my $h = Plack::Util::headers( $res->[1] );
173              
174             my $xsendfile =
175             $env->{'plack.xsendfile.type'}
176             || $ENV{HTTP_X_SENDFILE_TYPE}
177 7   50     175 || 'X-Sendfile';
178              
179 7 50       32 if ( $h->exists($xsendfile) ) {
180 0         0 $increment->( $env, 'psgi.response.x-sendfile', $rate );
181             }
182              
183 7 50       170 if ( $h->exists('Content-Length') ) {
184 7   100     124 my $length = $h->get('Content-Length') || 0;
185 7         161 $histogram->( $env, 'psgi.response.content-length', $length, $rate );
186             }
187              
188 7 50       120 if ( my $type = _mime_type_to_metric( $h->get('Content-Type') ) ) {
189 7         16 $increment->( $env, 'psgi.response.content-type.' . $type, $rate );
190             }
191              
192 7         107 $increment->( $env, 'psgi.response.status.' . $res->[0], $rate );
193              
194 7 50       110 if (
    50          
195             $env->{'psgix.harakiri.supported'}
196             ? $env->{'psgix.harakiri'}
197             : $env->{'psgix.harakiri.commit'}
198             )
199             {
200 0         0 $increment->( $env, 'psgix.harakiri' ); # rate == 1
201             }
202              
203 7 50       31 $client->flush if $client->can('flush');
204              
205 7         59 return;
206             }
207 7         1617 );
208              
209             }
210              
211 14     14   168 sub _mime_type_to_metric( $type = undef ) {
  14         27  
  14         14  
212 14 100       37 return unless $type;
213 8 50       56 return unless $type =~ m#^\w+/(?:\w+[\-\+])*\w+(?: *;\w+=\w+)?#a;
214 8         56 return $type =~ s#\.#-#gr =~ s#/#.#gr =~ s/;.*$//r;
215             }
216              
217              
218             1;
219              
220             __END__