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 14     14   532384 use 5.012000;
  14         121  
4 14     14   60 use strict;
  14         24  
  14         262  
5 14     14   65 use warnings;
  14         33  
  14         439  
6              
7 14     14   5408 use Crypt::URandom ();
  14         67959  
  14         297  
8 14     14   5277 use IO::Socket::INET;
  14         210767  
  14         78  
9 14     14   11741 use Module::Load;
  14         14099  
  14         103  
10 14     14   5615 use Time::HiRes ();
  14         13871  
  14         332  
11 14     14   5770 use Types::Serialiser;
  14         39954  
  14         421  
12 14     14   5198 use AWS::XRay::Segment;
  14         35  
  14         398  
13 14     14   4919 use AWS::XRay::Buffer;
  14         28  
  14         384  
14 14     14   77 use Carp;
  14         33  
  14         691  
15              
16 14     14   239 use Exporter 'import';
  14         22  
  14         2704  
17             our @EXPORT_OK = qw/ new_trace_id capture capture_from trace /;
18              
19             our $VERSION = "0.12";
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 14     14   7490 my $VALID_NAME_REGEXP = qr/\A[\p{L}\p{N}\p{Z}_.:\/%&#=+\\\-@]{1,200}\z/;
  14         178  
  14         177  
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 19387 my $class = shift;
45 14 50       58 if (@_) {
46 14         27 $SAMPLING_RATE = shift;
47             }
48 14         26 $SAMPLING_RATE;
49             }
50              
51             sub sampler {
52 4     4 1 11053 my $class = shift;
53 4 50       12 if (@_) {
54 4         30 $SAMPLER = shift;
55             }
56 4         10 $SAMPLER;
57             }
58              
59             sub plugins {
60 2     2 0 7450 my $class = shift;
61 2 50       123 if (@_) {
62 2         82 @PLUGINS = @_;
63 2         125 Module::Load::load $_ for @PLUGINS;
64             }
65 2         633 @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 5   50 5 0 44 $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 3356 sprintf(
94             "1-%x-%s",
95             CORE::time(),
96             unpack("H*", Crypt::URandom::urandom(12)),
97             );
98             }
99              
100             sub new_id {
101 2226     2226 0 6839 unpack("H*", Crypt::URandom::urandom(8));
102             }
103              
104             sub is_valid_name {
105 4456     4456 0 28270 $_[0] =~ $VALID_NAME_REGEXP;
106             }
107              
108             # alias for backward compatibility
109             *trace = \&capture;
110              
111             sub capture {
112 4437     4437 1 424049 my ($name, $code) = @_;
113 4437 100       5642 if (!is_valid_name($name)) {
114 2         6 my $msg = "invalid segment name: $name";
115 2 100       270 $CROAK_INVALID_NAME ? croak($msg) : carp($msg);
116             }
117 4436         6391 my $wantarray = wantarray;
118              
119 4436         4197 my $enabled;
120 4436         4557 my $sampled = $SAMPLED;
121 4436 100       6295 if (defined $ENABLED) {
    50          
122 2413 100       3077 $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       2853 $sampled = $SAMPLER->() ? 1 : 0;
130 2023 100       5195 $enabled = $sampled ? 1 : 0;
131             }
132 4436         4718 local $ENABLED = $enabled;
133 4436         4669 local $SAMPLED = $sampled;
134              
135 4436 100       9555 return $code->(AWS::XRay::Segment->new) if !$enabled;
136              
137 2224   66     4285 local $TRACE_ID = $TRACE_ID // new_trace_id();
138              
139 2224         72079 my $segment = AWS::XRay::Segment->new({ name => $name });
140 2224 100 66     6872 unless (defined $segment->{type} && $segment->{type} eq "subsegment") {
141 1512         2671 $_->apply_plugin($segment) for @PLUGINS;
142             }
143              
144 2224         3237 local $SEGMENT_ID = $segment->{id};
145              
146 2224         2451 my @ret;
147 2224         2382 eval {
148 2224 100       3556 if ($wantarray) {
    100          
149 1         6 @ret = $code->($segment);
150             }
151             elsif (defined $wantarray) {
152 4         13 $ret[0] = $code->($segment);
153             }
154             else {
155 2219         3721 $code->($segment);
156             }
157             };
158 2224         406181 my $error = $@;
159 2224 50       3229 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         2174 eval {
172 2224         3997 $segment->close();
173             };
174 2224 50       42061 if ($@) {
175 0         0 warn $@;
176             }
177 2224 50       3129 die $error if $error;
178 2224 100       9230 return $wantarray ? @ret : $ret[0];
179             }
180              
181             sub capture_from {
182 1004     1004 1 7524 my ($header, $name, $code) = @_;
183 1004         1284 my ($trace_id, $segment_id, $sampled) = parse_trace_header($header);
184              
185 1004   100     1897 local $AWS::XRay::SAMPLED = $sampled // $SAMPLER->();
186 1004         3960 local $AWS::XRay::ENABLED = $AWS::XRay::SAMPLED;
187 1004         1295 local ($AWS::XRay::TRACE_ID, $AWS::XRay::SEGMENT_ID) = ($trace_id, $segment_id);
188 1004         1400 capture($name, $code);
189             }
190              
191             sub parse_trace_header {
192 1004 100   1004 1 2035 my $header = shift or return;
193              
194 3         7 my ($trace_id, $segment_id, $sampled);
195 3 50       20 if ($header =~ /Root=([0-9a-fA-F-]+)/) {
196 3         9 $trace_id = $1;
197             }
198 3 50       14 if ($header =~ /Parent=([0-9a-fA-F]+)/) {
199 3         7 $segment_id = $1;
200             }
201 3 50       16 if ($header =~ /Sampled=([^;]+)/) {
202 3         8 $sampled = $1;
203             }
204 3         9 return ($trace_id, $segment_id, $sampled);
205             }
206              
207             sub add_capture {
208 3     3 1 157 my ($class, $package, @methods) = @_;
209 14     14   286959 no warnings 'redefine';
  14         28  
  14         588  
210 14     14   69 no strict 'refs';
  14         26  
  14         2666  
211 3         38 for my $method (@methods) {
212 3 50       168 my $orig = $package->can($method) or next;
213 3         53 *{"${package}::${method}"} = sub {
214 3     3   27 my @args = @_;
215             capture(
216             $package,
217             sub {
218 3     3   7 my $segment = shift;
219 3         26 $segment->{metadata}->{method} = $method;
220 3         25 $segment->{metadata}->{package} = $package;
221 3         15 $orig->(@args);
222             },
223 3         129 );
224 3         82 };
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 14     14   82 no warnings 'redefine';
  14         26  
  14         391  
244 14     14   72 no strict 'refs';
  14         28  
  14         2044  
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__