File Coverage

blib/lib/OpenTelemetry/Instrumentation/DBI.pm
Criterion Covered Total %
statement 51 109 46.7
branch 2 16 12.5
condition 0 3 0.0
subroutine 16 21 76.1
pod 2 3 66.6
total 71 152 46.7


line stmt bran cond sub pod time code
1             package OpenTelemetry::Instrumentation::DBI;
2             # ABSTRACT: OpenTelemetry instrumentation for DBI
3              
4             our $VERSION = '0.033';
5              
6 1     1   731 use strict;
  1         3  
  1         47  
7 1     1   6 use warnings;
  1         3  
  1         71  
8 1     1   47 use experimental 'signatures';
  1         3  
  1         28  
9 1     1   182 use feature 'state';
  1         2  
  1         40  
10              
11 1     1   6 use Class::Inspector;
  1         2  
  1         47  
12 1     1   6 use Class::Method::Modifiers 'install_modifier';
  1         2  
  1         60  
13 1     1   7 use Feature::Compat::Try;
  1         2  
  1         8  
14 1     1   93 use OpenTelemetry::Constants qw( SPAN_KIND_CLIENT SPAN_STATUS_ERROR SPAN_STATUS_OK );
  1         3  
  1         7  
15 1     1   1262 use OpenTelemetry::Context;
  1         3  
  1         36  
16 1     1   7 use OpenTelemetry::Trace;
  1         2  
  1         28  
17 1     1   5 use OpenTelemetry;
  1         2  
  1         5  
18 1     1   315 use Syntax::Keyword::Dynamically;
  1         4  
  1         8  
19              
20 1     1   70 use parent 'OpenTelemetry::Instrumentation';
  1         2  
  1         6  
21              
22 0     0 1 0 sub dependencies { 'DBI' }
23              
24             my ( $EXECUTE, $DO, $loaded );
25 0     0 0 0 sub uninstall ( $class ) {
  0         0  
  0         0  
26 0 0       0 return unless $loaded;
27 1     1   165 no strict 'refs';
  1         3  
  1         40  
28 1     1   4 no warnings 'redefine';
  1         3  
  1         1952  
29 0         0 delete $Class::Method::Modifiers::MODIFIER_CACHE{'DBI::st'}{execute};
30 0         0 *{'DBI::st::execute'} = $EXECUTE;
  0         0  
31 0         0 *{'DBI::db::do'} = $DO;
  0         0  
32 0         0 undef $loaded;
33 0         0 return;
34             }
35              
36 1     1 1 4 sub install ( $class, %options ) {
  1         3  
  1         2  
  1         3  
37 1 50       140 return if $loaded;
38 1 50       7 return unless Class::Inspector->loaded('DBI');
39              
40 0     0     my $wrapper = sub ( $dbh, $statement, $orig, $handle, @args ) {
  0            
  0            
  0            
  0            
  0            
  0            
41 0           state %meta;
42              
43 0           my $name = $dbh->{Name};
44              
45 0   0       my $info = $meta{$name} //= do {
46             my %meta = (
47             'db.system' => lc $dbh->{Driver}{Name},
48 0           );
49              
50 0 0         $meta{'db.user'} = $dbh->{Username} if $dbh->{Username};
51 0 0         $meta{'server.address'} = $1 if $name =~ /host=([^;]+)/;
52 0 0         $meta{'server.port'} = $1 if $name =~ /port=([0-9]+)/;
53              
54             # Driver-specific metadata available before call
55 0 0         if ( $meta{'db.system'} eq 'mysql' ) {
56 0           $meta{'network.transport'} = 'IP.TCP';
57             }
58              
59 0           \%meta;
60             };
61              
62 0           $statement = $statement =~ s/^\s+|\s+$//gr =~ s/\s+/ /gr;
63              
64 0           my $span = OpenTelemetry->tracer_provider->tracer->create_span(
65             name => substr($statement, 0, 100) =~ s/\s+$//r,
66             kind => SPAN_KIND_CLIENT,
67             attributes => {
68             'db.connection_string' => $name,
69             'db.statement' => $statement,
70             %$info,
71             },
72             );
73              
74 0           dynamically OpenTelemetry::Context->current
75             = OpenTelemetry::Trace->context_with_span($span);
76              
77 0           try {
78 0           return $handle->$orig(@args);
79             }
80             catch ( $error ) {
81 0           my ($description) = split /\n/, $error =~ s/^\s+|\s+$//gr, 2;
82 0           $description =~ s/ at \S+ line \d+\.$//a;
83              
84 0           $span->record_exception($error);
85 0           $span->set_status( SPAN_STATUS_ERROR, $description );
86              
87 0           die $error;
88             }
89             finally {
90 0 0         if ( $handle->err ) {
91 0           my $error = $handle->errstr =~ s/^\s+|\s+$//gr;
92              
93 0           my ($description) = split /\n/, $error, 2;
94 0           $description =~ s/ at \S+ line \d+\.$//a;
95              
96 0           $span->set_status( SPAN_STATUS_ERROR, $description );
97             }
98             else {
99 0           $span->set_status( SPAN_STATUS_OK );
100             }
101              
102 0           $span->end;
103             }
104 0           };
105              
106 0           $EXECUTE = \&DBI::st::execute;
107             install_modifier 'DBI::st' => around => execute => sub {
108 0     0     my ( undef, $sth ) = @_;
109 0           unshift @_, $sth->{Database}, $sth->{Statement};
110 0           goto $wrapper;
111 0           };
112              
113 0           $DO = \&DBI::st::do;
114             install_modifier 'DBI::db' => around => do => sub {
115 0     0     my ( undef, $dbh, $sql ) = @_;
116 0           unshift @_, $dbh, $sql;
117 0           goto $wrapper;
118 0           };
119              
120 0           return $loaded = 1;
121             }
122              
123             1;