File Coverage

blib/lib/Google/Ads/GoogleAds/Logging/DetailStats.pm
Criterion Covered Total %
statement 67 67 100.0
branch 7 8 87.5
condition 5 10 50.0
subroutine 15 15 100.0
pod 0 1 0.0
total 94 101 93.0


line stmt bran cond sub pod time code
1             # Copyright 2019, Google LLC
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14              
15              
16             use strict;
17 11     11   66 use warnings;
  11         15  
  11         272  
18 11     11   49 use version;
  11         19  
  11         220  
19 11     11   46  
  11         18  
  11         63  
20             # The following needs to be on one line because CPAN uses a particularly hacky
21             # eval() to determine module versions.
22             use Google::Ads::GoogleAds::Constants; our $VERSION = ${Google::Ads::GoogleAds::Constants::VERSION};
23 11     11   581  
  11         20  
  11         368  
24             use Class::Std::Fast;
25 11     11   49 use JSON::XS;
  11         13  
  11         89  
26 11     11   1220 use Encode qw( encode_utf8 decode_utf8 );
  11         50  
  11         628  
27 11     11   77  
  11         24  
  11         560  
28             # A list of fields in HTTP headers, content and GAQL that need to be scrubbed
29             # before logging for privacy reasons.
30             use constant REDACTED_STRING => "REDACTED";
31 11     11   73 use constant SCRUBBED_HEADERS => qw(developer-token Authorization);
  11         15  
  11         656  
32 11     11   65 # Below fields will be scrubbed in the HTTP request and response content.
  11         16  
  11         738  
33             # CustomerUserAccess.emailAddress
34             # CustomerUserAccess.inviterUserEmailAddress
35             # CustomerUserAccessInvitation.emailAddress
36             # ChangeEvent.userEmail
37             # PlacesLocationFeedData.emailAddress
38             # CreateCustomerClientRequest.emailAddress
39             use constant SCRUBBED_CONTENT_FIELDS =>
40 11         563 qw(emailAddress inviterUserEmailAddress userEmail);
41 11     11   62 # Below fields will be scrubbed in the GAQL statement of SearchGoogleAdsRequest
  11         20  
42             # and SearchGoogleAdsStreamRequest.
43             use constant SCRUBBED_GAQL_FIELDS => qw(customer_user_access\.email_address
44 11         4119 customer_user_access\.inviter_user_email_address
45             customer_user_access_invitation\.email_address
46             change_event\.user_email feed\.places_location_feed_data\.email_address
47             );
48 11     11   61  
  11         16  
49             my %host_of : ATTR(:name<host> :default<>);
50             my %method_of : ATTR(:name<method> :default<>);
51             my %request_headers_of : ATTR(:name<request_headers> :default<>);
52             my %request_content_of : ATTR(:name<request_content> :default<>);
53             my %response_headers_of : ATTR(:name<response_headers> :default<>);
54             my %response_content_of : ATTR(:name<response_content> :default<>);
55             my %fault_of : ATTR(:name<fault> :default<>);
56              
57             my $self = shift;
58             my $host = $self->get_host() || "";
59 2     2 0 154 my $method = $self->get_method() || "";
60 2   50     8 my $request_headers = $self->get_request_headers() || {};
61 2   50     12 my $request_content = $self->get_request_content() || "";
62 2   50     10 my $response_headers = $self->get_response_headers() || {};
63 2   50     13 my $response_content = $self->get_response_content();
64 2   50     10 my $fault = $self->get_fault();
65 2         10  
66 2         9 # Scrub the sensitive HTTP headers.
67             foreach my $header (SCRUBBED_HEADERS) {
68             $request_headers->header($header => REDACTED_STRING);
69 2         9 }
70 4         81  
71             # Delete the unuseful "::std_case" header from request headers and response headers.
72             delete $request_headers->{"::std_case"};
73             delete $response_headers->{"::std_case"};
74 2         64  
75 2         3 # Scrub the sensitive fields in the HTTP request and response.
76             $request_content = _scrub_content($request_content);
77             $request_content = _scrub_gaql($request_content);
78 2         5 $response_content = _scrub_content($response_content) if $response_content;
79 2         5  
80 2 100       5 my $json_coder = JSON::XS->new->utf8->pretty;
81             my $detail_message = sprintf(
82 2         8 "Request\n" .
83 2         32 "-------\n" . "MethodName: %s\n" . "Host: %s\n" . "Headers: %s\n" .
84             "Request: %s\n" . "\nResponse\n" . "-------\n" . "Headers: %s\n",
85             $method, $host, $json_coder->encode({%$request_headers}),
86             $request_content, $json_coder->encode({%$response_headers}));
87              
88             $detail_message .= "Response: ${response_content}\n" if $response_content;
89             $detail_message .= "Fault: ${fault}\n" if $fault;
90 2 100       12  
91 2 100       5 return $detail_message;
92             }
93 2         15  
94 11     11   74 # Scrubs the sensitive fields in HTTP content.
  11         17  
  11         54  
95             my $content = shift;
96             foreach my $field (SCRUBBED_CONTENT_FIELDS) {
97             $content =~ s/("$field"\s?:\s?)".+?"/$1"${\REDACTED_STRING}"/g;
98 3     3   4 }
99 3         7  
100 9         116 return $content;
  4         20  
101             }
102              
103 3         6 # Scrubs the sensitive fields in GAQL statement.
104             my $content = shift;
105              
106             return $content if $content !~ /"query"/;
107             foreach my $field (SCRUBBED_GAQL_FIELDS) {
108 2     2   3 $content =~
109             s/(SELECT.+WHERE.+$field.+?['"])\S+?(['"])/$1${\REDACTED_STRING}$2/i;
110 2 50       8 }
111 2         5  
112 10         170 return $content;
113 1         7 }
114              
115             return 1;
116 2         5  
117             =pod
118              
119             =head1 NAME
120              
121             Google::Ads::GoogleAds::Logging::DetailStats
122              
123             =head1 DESCRIPTION
124              
125             Class that wraps the detailed HTTP request and response like host, method,
126             headers, payload.
127              
128             =head1 ATTRIBUTES
129              
130             =head2 host
131              
132             The Google Ads API server endpoint.
133              
134             =head2 method
135              
136             The name of the service method that was called.
137              
138             =head2 request_headers
139              
140             The REST HTTP request headers.
141              
142             =head2 request_content
143              
144             The REST HTTP request payload.
145              
146             =head2 response_headers
147              
148             The REST HTTP response headers.
149              
150             =head2 response_content
151              
152             The REST HTTP response payload.
153              
154             =head2 fault
155              
156             The stack trace of up to 16K characters if a fault occurs.
157              
158             =head1 LICENSE AND COPYRIGHT
159              
160             Copyright 2019 Google LLC
161              
162             Licensed under the Apache License, Version 2.0 (the "License");
163             you may not use this file except in compliance with the License.
164             You may obtain a copy of the License at
165              
166             http://www.apache.org/licenses/LICENSE-2.0
167              
168             Unless required by applicable law or agreed to in writing, software
169             distributed under the License is distributed on an "AS IS" BASIS,
170             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
171             See the License for the specific language governing permissions and
172             limitations under the License.
173              
174             =head1 REPOSITORY INFORMATION
175              
176             $Rev: $
177             $LastChangedBy: $
178             $Id: $
179              
180             =cut