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   67 use warnings;
  11         19  
  11         279  
18 11     11   53 use version;
  11         18  
  11         238  
19 11     11   46  
  11         14  
  11         80  
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   614  
  11         20  
  11         371  
24             use Class::Std::Fast;
25 11     11   107 use JSON::XS;
  11         20  
  11         65  
26 11     11   1333 use Encode qw( encode_utf8 decode_utf8 );
  11         15  
  11         626  
27 11     11   59  
  11         18  
  11         559  
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   61 use constant SCRUBBED_HEADERS => qw(developer-token Authorization);
  11         45  
  11         680  
32 11     11   70 # Below fields will be scrubbed in the HTTP request and response content.
  11         18  
  11         753  
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         615 qw(emailAddress inviterUserEmailAddress userEmail);
41 11     11   64 # Below fields will be scrubbed in the GAQL statement of SearchGoogleAdsRequest
  11         21  
42             # and SearchGoogleAdsStreamRequest.
43             use constant SCRUBBED_GAQL_FIELDS => qw(customer_user_access\.email_address
44 11         4195 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   63  
  11         20  
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 158 my $method = $self->get_method() || "";
60 2   50     7 my $request_headers = $self->get_request_headers() || {};
61 2   50     16 my $request_content = $self->get_request_content() || "";
62 2   50     11 my $response_headers = $self->get_response_headers() || {};
63 2   50     14 my $response_content = $self->get_response_content();
64 2   50     12 my $fault = $self->get_fault();
65 2         11  
66 2         10 # Scrub the sensitive HTTP headers.
67             foreach my $header (SCRUBBED_HEADERS) {
68             $request_headers->header($header => REDACTED_STRING);
69 2         11 }
70 4         86  
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         68  
75 2         4 # 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         6 $response_content = _scrub_content($response_content) if $response_content;
79 2         5  
80 2 100       6 my $json_coder = JSON::XS->new->utf8->pretty;
81             my $detail_message = sprintf(
82 2         12 "Request\n" .
83 2         35 "-------\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       7 return $detail_message;
92             }
93 2         16  
94 11     11   69 # Scrubs the sensitive fields in HTTP content.
  11         23  
  11         57  
95             my $content = shift;
96             foreach my $field (SCRUBBED_CONTENT_FIELDS) {
97             $content =~ s/("$field"\s?:\s?)".+?"/$1"${\REDACTED_STRING}"/g;
98 3     3   5 }
99 3         6  
100 9         118 return $content;
  4         18  
101             }
102              
103 3         7 # 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       10 }
111 2         4  
112 10         173 return $content;
113 1         8 }
114              
115             return 1;
116 2         6  
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