File Coverage

blib/lib/AWS/XRay.pm
Criterion Covered Total %
statement 125 137 91.2
branch 35 52 67.3
condition 11 19 57.8
subroutine 30 31 96.7
pod 8 12 66.6
total 209 251 83.2


line stmt bran cond sub pod time code
1             package AWS::XRay;
2              
3 13     13   497884 use 5.012000;
  13         103  
4 13     13   57 use strict;
  13         21  
  13         275  
5 13     13   55 use warnings;
  13         19  
  13         285  
6              
7 13     13   5156 use Crypt::URandom ();
  13         57203  
  13         265  
8 13     13   6372 use IO::Socket::INET;
  13         187082  
  13         70  
9 13     13   10321 use Module::Load;
  13         12399  
  13         87  
10 13     13   5250 use Time::HiRes ();
  13         12170  
  13         317  
11 13     13   5127 use Types::Serialiser;
  13         35307  
  13         386  
12 13     13   4692 use AWS::XRay::Segment;
  13         29  
  13         374  
13 13     13   4434 use AWS::XRay::Buffer;
  13         25  
  13         317  
14 13     13   65 use Carp;
  13         29  
  13         657  
15              
16 13     13   131 use Exporter 'import';
  13         21  
  13         2352  
17             our @EXPORT_OK = qw/ new_trace_id capture capture_from trace /;
18              
19             our $VERSION = "0.11";
20              
21             our $TRACE_ID;
22             our $SEGMENT_ID;
23             our $ENABLED;
24             our $SAMPLED;
25             our $SAMPLING_RATE = 1;
26             our $SAMPLER = sub { rand() < $SAMPLING_RATE };
27             our $AUTO_FLUSH = 1;
28              
29             our @PLUGINS;
30              
31             our $DAEMON_HOST = "127.0.0.1";
32             our $DAEMON_PORT = 2000;
33              
34             our $CROAK_INVALID_NAME = 0;
35 13     13   6553 my $VALID_NAME_REGEXP = qr/\A[\p{L}\p{N}\p{Z}_.:\/%&#=+\\\-@]{1,200}\z/;
  13         152  
  13         149  
36              
37             if ($ENV{"AWS_XRAY_DAEMON_ADDRESS"}) {
38             ($DAEMON_HOST, $DAEMON_PORT) = split /:/, $ENV{"AWS_XRAY_DAEMON_ADDRESS"};
39             }
40              
41             my $Sock;
42              
43             sub sampling_rate {
44 14     14 1 19762 my $class = shift;
45 14 50       39 if (@_) {
46 14         25 $SAMPLING_RATE = shift;
47             }
48 14         32 $SAMPLING_RATE;
49             }
50              
51             sub sampler {
52 4     4 1 11418 my $class = shift;
53 4 50       12 if (@_) {
54 4         24 $SAMPLER = shift;
55             }
56 4         8 $SAMPLER;
57             }
58              
59             sub plugins {
60 2     2 0 5278 my $class = shift;
61 2 50       111 if (@_) {
62 2         68 @PLUGINS = @_;
63 2         110 Module::Load::load $_ for @PLUGINS;
64             }
65 2         573 @PLUGINS;
66             }
67              
68             sub auto_flush {
69 0     0 1 0 my $class = shift;
70 0 0       0 if (@_) {
71 0         0 my $auto_flush = shift;
72 0 0       0 if ($auto_flush != $AUTO_FLUSH) {
73 0 0 0     0 $Sock->close if $Sock && $Sock->can("close");
74 0         0 undef $Sock; # regenerate
75             }
76 0         0 $AUTO_FLUSH = $auto_flush;
77             }
78 0         0 $AUTO_FLUSH;
79             }
80              
81             sub sock {
82 4   50 4 0 30 $Sock //= AWS::XRay::Buffer->new(
      50        
      66        
83             IO::Socket::INET->new(
84             PeerAddr => $DAEMON_HOST || "127.0.0.1",
85             PeerPort => $DAEMON_PORT || 2000,
86             Proto => "udp",
87             ),
88             $AUTO_FLUSH,
89             );
90             }
91              
92             sub new_trace_id {
93 1513     1513 1 3084 sprintf(
94             "1-%x-%s",
95             CORE::time(),
96             unpack("H*", Crypt::URandom::urandom(12)),
97             );
98             }
99              
100             sub new_id {
101 2225     2225 0 7188 unpack("H*", Crypt::URandom::urandom(8));
102             }
103              
104             sub is_valid_name {
105 4456     4456 0 28624 $_[0] =~ $VALID_NAME_REGEXP;
106             }
107              
108             # alias for backward compatibility
109             *trace = \&capture;
110              
111             sub capture {
112 4437     4437 1 428619 my ($name, $code) = @_;
113 4437 100       5567 if (!is_valid_name($name)) {
114 2         6 my $msg = "invalid segment name: $name";
115 2 100       255 $CROAK_INVALID_NAME ? croak($msg) : carp($msg);
116             }
117 4436         6565 my $wantarray = wantarray;
118              
119 4436         4303 my $enabled;
120 4436         4499 my $sampled = $SAMPLED;
121 4436 100       6348 if (defined $ENABLED) {
    50          
122 2413 100       2999 $enabled = $ENABLED ? 1 : 0; # fix true or false (not undef)
123             }
124             elsif ($TRACE_ID) {
125 0         0 $enabled = 0; # called from parent capture
126             }
127             else {
128             # root capture try sampling
129 2023 100       2700 $sampled = $SAMPLER->() ? 1 : 0;
130 2023 100       5296 $enabled = $sampled ? 1 : 0;
131             }
132 4436         4956 local $ENABLED = $enabled;
133 4436         4622 local $SAMPLED = $sampled;
134              
135 4436 100       9729 return $code->(AWS::XRay::Segment->new) if !$enabled;
136              
137 2224   66     4263 local $TRACE_ID = $TRACE_ID // new_trace_id();
138              
139 2224         75943 my $segment = AWS::XRay::Segment->new({ name => $name });
140 2224 100 66     6813 unless (defined $segment->{type} && $segment->{type} eq "subsegment") {
141 1512         2805 $_->apply_plugin($segment) for @PLUGINS;
142             }
143              
144 2224         3064 local $SEGMENT_ID = $segment->{id};
145              
146 2224         2441 my @ret;
147 2224         2377 eval {
148 2224 100       3876 if ($wantarray) {
    100          
149 1         4 @ret = $code->($segment);
150             }
151             elsif (defined $wantarray) {
152 4         14 $ret[0] = $code->($segment);
153             }
154             else {
155 2219         3573 $code->($segment);
156             }
157             };
158 2224         405985 my $error = $@;
159 2224 50       3067 if ($error) {
160 0         0 $segment->{error} = Types::Serialiser::true;
161             $segment->{cause} = {
162 0         0 exceptions => [
163             {
164             id => new_id(),
165             message => "$error",
166             remote => Types::Serialiser::true,
167             },
168             ],
169             };
170             }
171 2224         2222 eval {
172 2224         3590 $segment->close();
173             };
174 2224 50       361113 if ($@) {
175 0         0 warn $@;
176             }
177 2224 50       3238 die $error if $error;
178 2224 100       9187 return $wantarray ? @ret : $ret[0];
179             }
180              
181             sub capture_from {
182 1004     1004 1 7084 my ($header, $name, $code) = @_;
183 1004         1325 my ($trace_id, $segment_id, $sampled) = parse_trace_header($header);
184              
185 1004   100     1927 local $AWS::XRay::SAMPLED = $sampled // $SAMPLER->();
186 1004         4020 local $AWS::XRay::ENABLED = $AWS::XRay::SAMPLED;
187 1004         1281 local ($AWS::XRay::TRACE_ID, $AWS::XRay::SEGMENT_ID) = ($trace_id, $segment_id);
188 1004         1349 capture($name, $code);
189             }
190              
191             sub parse_trace_header {
192 1004 100   1004 1 2014 my $header = shift or return;
193              
194 3         6 my ($trace_id, $segment_id, $sampled);
195 3 50       18 if ($header =~ /Root=([0-9a-fA-F-]+)/) {
196 3         9 $trace_id = $1;
197             }
198 3 50       12 if ($header =~ /Parent=([0-9a-fA-F]+)/) {
199 3         6 $segment_id = $1;
200             }
201 3 50       14 if ($header =~ /Sampled=([^;]+)/) {
202 3         7 $sampled = $1;
203             }
204 3         10 return ($trace_id, $segment_id, $sampled);
205             }
206              
207             sub add_capture {
208 3     3 1 123 my ($class, $package, @methods) = @_;
209 13     13   254071 no warnings 'redefine';
  13         27  
  13         562  
210 13     13   61 no strict 'refs';
  13         22  
  13         2385  
211 3         50 for my $method (@methods) {
212 3 50       167 my $orig = $package->can($method) or next;
213 3         57 *{"${package}::${method}"} = sub {
214 3     3   21 my @args = @_;
215             capture(
216             $package,
217             sub {
218 3     3   4 my $segment = shift;
219 3         15 $segment->{metadata}->{method} = $method;
220 3         24 $segment->{metadata}->{package} = $package;
221 3         36 $orig->(@args);
222             },
223 3         68 );
224 3         83 };
225             }
226             }
227              
228             if ($ENV{LAMBDA_TASK_ROOT}) {
229             # AWS::XRay is loaded in AWS Lambda worker.
230             # notify the Lambda Runtime that initialization is complete.
231             unless (mkdir '/tmp/.aws-xray') {
232             # ignore the error if the directory is already exits or other process created it.
233             my $err = $!;
234             unless (-d '/tmp/.aws-xray') {
235             warn "failed to make directory: $err";
236             }
237             }
238             open my $fh, '>', '/tmp/.aws-xray/initialized' or warn "failed to create file: $!";
239             close $fh;
240             utime undef, undef, '/tmp/.aws-xray/initialized' or warn "failed to touch file: $!";
241              
242             # patch the capture
243 13     13   73 no warnings 'redefine';
  13         22  
  13         338  
244 13     13   76 no strict 'refs';
  13         19  
  13         1759  
245             my $org = \&capture;
246             *capture = sub {
247             my ($trace_id, $segment_id, $sampled) = parse_trace_header($ENV{_X_AMZN_TRACE_ID});
248             local $AWS::XRay::SAMPLED = $sampled // $SAMPLER->();
249             local $AWS::XRay::ENABLED = $AWS::XRay::SAMPLED;
250             local ($AWS::XRay::TRACE_ID, $AWS::XRay::SEGMENT_ID) = ($trace_id, $segment_id);
251             local *capture = $org;
252             local *trace = $org;
253             capture(@_);
254             };
255             *trace = \&capture;
256             }
257              
258             1;
259             __END__