File Coverage

blib/lib/CGI/Application/Plugin/OpenTracing.pm
Criterion Covered Total %
statement 273 279 97.8
branch 52 62 83.8
condition 9 12 75.0
subroutine 54 55 98.1
pod 1 6 16.6
total 389 414 93.9


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::OpenTracing;
2              
3 5     5   1521247 use strict;
  5         38  
  5         153  
4 5     5   29 use warnings;
  5         11  
  5         208  
5              
6             our $VERSION = 'v0.103.3';
7              
8 5     5   1767 use syntax 'maybe';
  5         96721  
  5         27  
9              
10 5     5   17791 use OpenTracing::Implementation;
  5         15223  
  5         37  
11 5     5   162 use OpenTracing::GlobalTracer;
  5         12  
  5         24  
12              
13 5     5   354 use Carp qw( croak carp );
  5         13  
  5         227  
14 5     5   564 use HTTP::Headers;
  5         4198  
  5         158  
15 5     5   478 use HTTP::Status;
  5         4786  
  5         1798  
16 5     5   46 use Scalar::Util qw( refaddr );
  5         21  
  5         271  
17 5     5   54 use Time::HiRes qw( gettimeofday );
  5         13  
  5         50  
18              
19 5     5   975 use constant CGI_LOAD_TMPL => 'cgi_application_load_tmpl';
  5         11  
  5         394  
20 5     5   42 use constant CGI_REQUEST => 'cgi_application_request';
  5         11  
  5         284  
21 5     5   44 use constant CGI_RUN => 'cgi_application_run';
  5         17  
  5         267  
22 5     5   41 use constant CGI_SETUP => 'cgi_application_setup';
  5         12  
  5         246  
23 5     5   33 use constant CGI_TEARDOWN => 'cgi_application_teardown';
  5         12  
  5         1218  
24              
25             our $implementation_import_name;
26             our @implementation_import_opts;
27              
28             our $TAG_JOIN_CHAR = ',';
29              
30             sub import {
31 5     5   53 my $package = shift;
32            
33 5         17 ( $implementation_import_name, @implementation_import_opts ) = @_;
34 5 100 33     27 $ENV{OPENTRACING_DEBUG} && carp "OpenTracing Implementation not defined during import\n"
35             unless defined $implementation_import_name;
36            
37 5         23 my $caller = caller;
38 5         213 $caller->add_callback( init => \&init );
39 5         97 $caller->add_callback( prerun => \&prerun );
40 5         69 $caller->add_callback( postrun => \&postrun );
41 5         66 $caller->add_callback( load_tmpl => \&load_tmpl );
42 5         64 $caller->add_callback( teardown => \&teardown );
43 5         78 $caller->add_callback( error => \&error );
44            
45              
46 5     5   46 my $run_glob = do { no strict 'refs'; \*{ $caller . '::run' } };
  5         12  
  5         422  
  5         75  
  5         10  
  5         30  
47 5 50       611 my $run_orig
48             = defined &$run_glob
49             ? \&run_glob
50             : eval "package $caller;" # SUPER works based on the package it's defined in
51             . 'sub { my $self = shift; $self->SUPER::run(@_) }';
52 5     5   35 no warnings 'redefine';
  5         10  
  5         14182  
53 5         18 *$run_glob = _wrap_run($run_orig);
54              
55 5         138 return;
56             }
57              
58             sub _wrap_run {
59 5     5   15 my ($orig) = @_;
60              
61             return sub {
62 27     27   1228 my $cgi_app = shift;
63              
64 27         64 my $res;
65 27         67 my $wantarray = wantarray; # eval has its own
66 27         60 my $ok = eval {
67 27 50       93 if ($wantarray) {
68 0         0 $res = [ $cgi_app->$orig(@_) ];
69             }
70             else {
71 27         841 $res = $cgi_app->$orig(@_);
72             }
73 24         681 1;
74             };
75 27 50       2237 return $wantarray ? @$res : $res if $ok;
    100          
76              
77 3         9 my $error = $@;
78              
79 3         12 my $request_span = _plugin_get_scope($cgi_app, CGI_REQUEST)->get_span;
80 3         19 $request_span->add_tag('http.status_code' => 500);
81              
82 3         449 _cascade_set_failed_spans($cgi_app, $error);
83              
84 3         63 die $error;
85 5         27 };
86             }
87              
88             sub _cascade_set_failed_spans {
89 5     5   19 my ($cgi_app, $error, $root_span) = @_;
90 5 100       32 my $root_addr = refaddr($root_span) if defined $root_span;
91              
92 5         14 my $tracer = _plugin_get_tracer($cgi_app);
93 5         40 while (my $scope = $tracer->get_scope_manager->get_active_scope()) {
94 10         958 my $span = $scope->get_span();
95 10 100 100     47 last if defined $root_addr and $root_addr eq refaddr($span);
96              
97 8         31 $span->add_tags(error => 1, message => $error);
98 8         948 $scope->close();
99             }
100 5         440 return;
101             }
102              
103             sub init {
104 27     27 0 592166 my $cgi_app = shift;
105            
106 27         132 _plugin_init_opentracing_implementation( $cgi_app );
107            
108 27         122 my %request_tags = _get_request_tags($cgi_app);
109 27         141 my %query_params = _get_query_params($cgi_app);
110 27         135 my %form_data = _get_form_data($cgi_app);
111 27         131 my $context = _tracer_extract_context( $cgi_app );
112            
113 27         18156 _plugin_start_active_span( $cgi_app, CGI_REQUEST, child_of => $context );
114 27         155 _plugin_add_tags( $cgi_app, CGI_REQUEST, %request_tags );
115 27         3894 _plugin_add_tags( $cgi_app, CGI_REQUEST, %query_params );
116 27         3361 _plugin_add_tags( $cgi_app, CGI_REQUEST, %form_data );
117 27         3268 _plugin_start_active_span( $cgi_app, CGI_SETUP );
118            
119             return
120 27         139 }
121              
122              
123              
124             sub prerun {
125 27     27 0 3213 my $cgi_app = shift;
126            
127 27         102 my %runmode_tags = _get_runmode_tags($cgi_app);
128 27         116 my %baggage_items = _get_baggage_items($cgi_app);
129            
130 27         133 _plugin_add_baggage_items( $cgi_app, CGI_SETUP, %baggage_items );
131 27         3329 _plugin_close_scope( $cgi_app, CGI_SETUP );
132 27         4826 _plugin_add_baggage_items( $cgi_app, CGI_REQUEST, %baggage_items );
133 27         2707 _plugin_add_tags( $cgi_app, CGI_REQUEST, %runmode_tags );
134 27         3676 _plugin_start_active_span( $cgi_app, CGI_RUN );
135            
136             return
137 27         95 }
138              
139              
140              
141             sub postrun {
142 24     24 0 5378 my $cgi_app = shift;
143            
144 24         99 _plugin_close_scope( $cgi_app, CGI_RUN );
145 24         3716 _plugin_start_active_span( $cgi_app, CGI_TEARDOWN );
146            
147             return
148 24         81 }
149              
150              
151              
152             sub load_tmpl {
153 0     0 0 0 my $cgi_app = shift;
154            
155 0         0 _plugin_close_scope( $cgi_app, CGI_LOAD_TMPL );
156            
157             return
158 0         0 }
159              
160              
161              
162             sub teardown {
163 24     24 1 7808 my $cgi_app = shift;
164            
165 24         101 my %http_status_tags = _get_http_status_tags($cgi_app);
166            
167 24         96 _plugin_close_scope( $cgi_app, CGI_TEARDOWN );
168 24         3872 _plugin_add_tags( $cgi_app, CGI_REQUEST, %http_status_tags );
169 24         3377 _plugin_close_scope( $cgi_app, CGI_REQUEST );
170            
171             return
172 24         2992 }
173              
174              
175              
176             sub error {
177 4     4 0 2678 my ($cgi_app, $error) = @_;
178 4 100       22 return if not $cgi_app->error_mode(); # we're dying
179              
180             # run span should continue
181 2         27 my $root = _plugin_get_scope($cgi_app, CGI_RUN)->get_span;
182 2         10 _cascade_set_failed_spans($cgi_app, $error, $root);
183            
184 2         5 return;
185             }
186              
187              
188              
189             sub _init_global_tracer {
190 27     27   69 my $cgi_app = shift;
191            
192 27         108 my @bootstrap_options = _get_bootstrap_options($cgi_app);
193            
194 27 50       320 my $bootstrapped_tracer =
195             $implementation_import_name ?
196             OpenTracing::Implementation->bootstrap_tracer(
197             $implementation_import_name,
198             @implementation_import_opts,
199             @bootstrap_options,
200             )
201             :
202             OpenTracing::Implementation->bootstrap_default_tracer(
203             @implementation_import_opts,
204             @bootstrap_options,
205             )
206             ;
207            
208 27         2301184 OpenTracing::GlobalTracer->set_global_tracer( $bootstrapped_tracer );
209            
210             return
211 27         13792 }
212              
213              
214              
215             sub _cgi_get_run_mode {
216 27     27   58 my $cgi_app = shift;
217            
218 27         115 my $run_mode = $cgi_app->get_current_runmode();
219            
220 27         205 return $run_mode
221             }
222              
223              
224              
225             sub _cgi_get_run_method {
226 27     27   55 my $cgi_app = shift;
227            
228 27         70 my $run_mode = $cgi_app->get_current_runmode();
229 27         191 my $run_methode = { $cgi_app->run_modes }->{ $run_mode };
230            
231 27         342 return $run_methode
232             }
233              
234              
235              
236             sub _cgi_get_http_method {
237 27     27   66 my $cgi_app = shift;
238            
239 27         173 my $query = $cgi_app->query();
240            
241 27         146106 return $query->request_method();
242             }
243              
244              
245             sub _cgi_get_http_headers { # TODO: extract headers from CGI request
246 27     27   62 my $cgi_app = shift;
247 27         217 return HTTP::Headers->new();
248             }
249              
250              
251             sub _cgi_get_http_url {
252 27     27   205 my $cgi_app = shift;
253            
254 27         97 my $query = $cgi_app->query();
255            
256 27         340 return $query->url(-path => 1);
257             }
258              
259              
260              
261             =for not_implemented
262             sub get_opentracing_global_tracer {
263             OpenTracing::GlobalTracer->get_global_tracer()
264             }
265             =cut
266              
267              
268              
269             sub _get_request_tags {
270 27     27   74 my $cgi_app = shift;
271            
272 27         113 my %tags = (
273             'component' => 'CGI::Application',
274             maybe 'http.method' => _cgi_get_http_method($cgi_app),
275             maybe 'http.url' => _cgi_get_http_url($cgi_app),
276             );
277            
278              
279 27         12619 return %tags
280             }
281              
282             sub _gen_tag_processor {
283 38     38   112 my $cgi_app = shift;
284            
285 38     32   198 my $joiner = sub { join $TAG_JOIN_CHAR, @_ };
  32         102  
286            
287 38         103 my (@specs, $fallback);
288 38         126 foreach my $spec_gen (@_) {
289 76 100       244 next if not defined $spec_gen;
290            
291 28         106 my ($spec, $spec_fallback) = _gen_spec($spec_gen->());
292 28   100     174 $fallback ||= $spec_fallback;
293 28         78 push @specs, $spec;
294             }
295 38   66     193 $fallback ||= $joiner;
296            
297             return sub {
298 73     73   191 my ($cgi_app, $name, $values) = @_;
299            
300 73         124 my $processor = $fallback;
301 73         155 foreach my $spec (@specs) {
302 62         129 my ($matched, $spec_processor) = $spec->($name);
303 62 100       171 $processor = $spec_processor if $matched;
304             }
305            
306 73 100       181 return if not defined $processor;
307 64 100       179 return $processor if not ref $processor;
308              
309 54 50       154 if (ref $processor eq 'CODE') {
310 54         142 my $processed = $processor->(@$values);
311 54 100       248 $processed = $joiner->(@$processed) if ref $processed eq 'ARRAY';
312 54         148 return $processed;
313             }
314            
315 0         0 croak "Invalid processor for param `$name`: ", ref $processor;
316 38         206 };
317             }
318              
319             sub _gen_spec {
320 28     28   236 my @def = @_;
321            
322 28         57 my $fallback;
323 28 100       101 $fallback = pop @def if @def % 2 != 0;
324            
325 28         66 my (%direct_match, @regex);
326 28         148 while (my ($cond, $processor) = splice @def, 0, 2) {
327 33 100       99 if (ref $cond eq 'Regexp') {
328 1         6 push @regex, [ $cond => $processor ];
329             }
330             else {
331 32 100       96 foreach my $name (ref $cond eq 'ARRAY' ? @$cond : $cond) {
332 33         155 $direct_match{$name} = $processor;
333             }
334             }
335             }
336             my $spec = sub {
337 62     62   126 my ($name) = @_;
338            
339             # return match state separately to differentiate from undef processors
340 62 100       182 return (1, $direct_match{$name}) if exists $direct_match{$name};
341            
342 34         88 foreach (@regex) {
343 3         7 my ($re, $processor) = @$_;
344 3 50       26 return (1, $processor) if $name =~ $re;
345             }
346 31         62 return;
347 28         118 };
348            
349 28         97 return ($spec, $fallback);
350             }
351              
352             sub _get_query_params {
353 27     27   72 my $cgi_app = shift;
354            
355 27         287 my $processor = _gen_tag_processor($cgi_app,
356             $cgi_app->can('opentracing_process_tags_query_params'),
357             $cgi_app->can('opentracing_process_tags'),
358             );
359            
360 27         99 my %processed_params;
361            
362 27         114 my $query = $cgi_app->query();
363 27         351 foreach my $param ($query->url_param()) {
364 48 50       2845 next unless defined $param; # huh ???
365 48         144 my @values = $query->url_param($param);
366 48         953 my $processed_value = $cgi_app->$processor($param, \@values);
367 48 100       132 next unless defined $processed_value;
368            
369 41         163 $processed_params{"http.query.$param"} = $processed_value;
370             }
371 27         682 return %processed_params;
372             }
373              
374             sub _get_form_data {
375 27     27   66 my $cgi_app = shift;
376 27         88 my $query = $cgi_app->query();
377 27 100       285 return unless _has_form_data($query);
378            
379 11         87 my $processor = _gen_tag_processor($cgi_app,
380             $cgi_app->can('opentracing_process_tags_form_fields'),
381             $cgi_app->can('opentracing_process_tags'),
382             );
383            
384 11         31 my %processed_params = ();
385            
386 11         46 my %params = $cgi_app->query->Vars();
387 11         2206 while (my ($param_name, $param_value) = each %params) {
388 25         102 my $processed_value = $cgi_app->$processor(
389             $param_name, [ split /\0/, $param_value ]
390             );
391 25 100       82 next unless defined $processed_value;
392 21         97 $processed_params{"http.form.$param_name"} = $processed_value
393             }
394            
395 11         140 return %processed_params;
396             }
397              
398             sub _has_form_data {
399 27     27   78 my ($query) = @_;
400 27         96 my $content_type = $query->content_type();
401 27 100       232 return if not defined $content_type;
402 11 50       47 return 1 if $content_type =~ m{\Amultipart/form-data};
403 11 50       84 return 1 if $content_type =~ m{\Aapplication/x-www-form-urlencoded};
404 0         0 return;
405             }
406              
407             sub _get_runmode_tags {
408 27     27   61 my $cgi_app = shift;
409            
410 27         108 my %tags = (
411             maybe 'run_mode' => _cgi_get_run_mode($cgi_app),
412             maybe 'run_method' => _cgi_get_run_method($cgi_app),
413             );
414 27         118 return %tags
415             }
416              
417             sub _get_http_status_tags {
418 24     24   60 my $cgi_app = shift;
419            
420 24         95 my %headers = $cgi_app->header_props();
421             my $status = $headers{-status} or return (
422 24 100       555 'http.status_code' => '200',
423             );
424 3         27 my $status_code = [ $status =~ /^\s*(\d{3})/ ]->[0];
425 3         13 my $status_mess = [ $status =~ /^\s*\d{3}\s*(.+)\s*$/ ]->[0];
426            
427 3 50       26 $status_mess = HTTP::Status::status_message($status_code)
428             unless defined $status_mess;
429            
430 3         36 my %tags = (
431             maybe 'http.status_code' => $status_code,
432             maybe 'http.status_message' => $status_mess,
433             );
434 3         16 return %tags
435             }
436              
437              
438             sub _get_bootstrap_options {
439 27     27   69 my $cgi_app = shift;
440            
441 27 100       228 return unless $cgi_app->can('opentracing_bootstrap_options');
442            
443 1         7 my @bootstrap_options = $cgi_app->opentracing_bootstrap_options( );
444            
445             return @bootstrap_options
446 1         7 }
447              
448              
449              
450             sub _get_baggage_items {
451 27     27   59 my $cgi_app = shift;
452            
453 27 100       162 return unless $cgi_app->can('opentracing_baggage_items');
454            
455 1         7 my %baggage_items = $cgi_app->opentracing_baggage_items( );
456            
457            
458 1         9 return %baggage_items
459             }
460              
461              
462              
463             sub _tracer_extract_context {
464 27     27   60 my $cgi_app = shift;
465            
466 27         115 my $http_headers = _cgi_get_http_headers($cgi_app);
467 27         303 my $tracer = _plugin_get_tracer( $cgi_app );
468            
469 27         143 return $tracer->extract_context($http_headers)
470             }
471              
472             sub _plugin_get_tracer {
473 137     137   235 my $cgi_app = shift;
474             return $cgi_app->{__PLUGINS}{OPENTRACING}{TRACER}
475 137         355 }
476              
477             sub _plugin_init_opentracing_implementation {
478 27     27   64 my $cgi_app = shift;
479            
480 27         260 _init_global_tracer($cgi_app);
481             # unless OpenTracing::GlobalTracer->is_registered;
482 27         127 my $tracer = OpenTracing::GlobalTracer->get_global_tracer;
483            
484 27         249 $cgi_app->{__PLUGINS}{OPENTRACING}{TRACER} = $tracer;
485             }
486              
487             sub _plugin_start_active_span {
488 105     105   217 my $cgi_app = shift;
489 105         237 my $operation_name = shift;
490 105         274 my %params = @_;
491 105         252 my $scope_name = uc $operation_name;
492            
493 105         322 my $scope =
494             _tracer_start_active_span( $cgi_app, $operation_name, %params );
495            
496 105         306349414 $cgi_app->{__PLUGINS}{OPENTRACING}{SCOPE}{$scope_name} = $scope;
497             }
498              
499             sub _tracer_start_active_span {
500 105     105   221 my $cgi_app = shift;
501 105         184 my $operation_name = shift;
502 105         217 my %params = @_;
503            
504 105         222 my $tracer = _plugin_get_tracer($cgi_app);
505 105         448 $tracer->start_active_span( $operation_name, %params );
506             }
507              
508             sub _plugin_add_tags {
509 132     132   276 my $cgi_app = shift;
510 132         285 my $operation_name = shift;
511 132         380 my %tags = @_;
512 132         321 my $scope_name = uc $operation_name;
513            
514 132         679 $cgi_app->{__PLUGINS}{OPENTRACING}{SCOPE}{$scope_name}
515             ->get_span->add_tags(%tags);
516             }
517              
518             sub _plugin_add_baggage_items {
519 54     54   115 my $cgi_app = shift;
520 54         104 my $operation_name = shift;
521 54         119 my %baggage_items = @_;
522 54         125 my $scope_name = uc $operation_name;
523            
524 54         299 $cgi_app->{__PLUGINS}{OPENTRACING}{SCOPE}{$scope_name}
525             ->get_span->add_baggage_items( %baggage_items );
526             }
527              
528             sub _plugin_close_scope {
529 99     99   199 my $cgi_app = shift;
530 99         185 my $operation_name = shift;
531 99         242 my $scope_name = uc $operation_name;
532            
533 99         465 $cgi_app->{__PLUGINS}{OPENTRACING}{SCOPE}{$scope_name}->close
534             }
535              
536             sub _plugin_get_scope {
537 5     5   12 my $cgi_app = shift;
538 5         10 my $scope_name = shift;
539 5         30 return $cgi_app->{__PLUGINS}{OPENTRACING}{SCOPE}{uc $scope_name};
540             }
541              
542              
543             1;