google-ads-common 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f9fd925b1012604aaedfaba55ac60cd16fed3f3
4
- data.tar.gz: 4a27eb4e56b52c99148be41fdcfc98946a9fd947
3
+ metadata.gz: 3267da5cdfdc5eef0f5653e99c6bd8329addb03f
4
+ data.tar.gz: 06334b9e716c913d333d49089f30fd2fc2158823
5
5
  SHA512:
6
- metadata.gz: 1664c58b1bae120d33c4d890d4978c0702cd9caa65dffa39d146a985a42985df7bef92b93447d0c25f9a7158991f5371dce495943f2bc5f61bae5276081bb979
7
- data.tar.gz: 71d90e5d480d8f17457261c13f8e61e2e573a72e27d42b1101fa8ba518fbcc920c75fa4fbed9fd3b285845305666e802f8721d6b9889a84de9def2a6e18b3b4d
6
+ metadata.gz: c1a6a5e280a2897d4443224111473de8f75a7db7550c9372364038418321c475473b50149e4d4f829e7be5f8b32445ed719fbdf39db8fa2b73df8ee57f4a9758
7
+ data.tar.gz: 9b1612f9cc4b39666296a48be14960801773d401763dceaaee115193b21531bb6462d3a788c43dad93b724cd4c4ca0c57929e3dc0154583309fc10d3a3b1c93a
data/ChangeLog CHANGED
@@ -1,3 +1,6 @@
1
+ 0.14.0:
2
+ - Standardizing logging format for requests, response, and summary.
3
+
1
4
  0.13.0:
2
5
  - Removed support for p12 keyfiles for service accounts.
3
6
  - Fixed issue #27. You can now force a refresh when fetching the access token
@@ -98,6 +98,12 @@ module AdsCommon
98
98
  @auth_handler = auth_handler
99
99
  end
100
100
 
101
+ # Get the unique identifier for the current account/network context. This
102
+ # is meant to be overriden by each client library.
103
+ def identifier()
104
+ return nil
105
+ end
106
+
101
107
  private
102
108
 
103
109
  # Loads the credentials from the config data.
@@ -73,6 +73,11 @@ module AdsCommon
73
73
  generate_headers(request, soap)
74
74
  end
75
75
 
76
+ # Returns the account/network identifier based on the current context.
77
+ def identifier()
78
+ return @credential_handler.identifier()
79
+ end
80
+
76
81
  private
77
82
 
78
83
  # Returns element name for SOAP header.
@@ -32,6 +32,7 @@ module AdsCommon
32
32
  attr_reader :namespace
33
33
 
34
34
  FALLBACK_API_ERROR_EXCEPTION = "ApiException"
35
+ MAX_FAULT_LOG_LENGTH = 16000
35
36
 
36
37
  # Creates a new service.
37
38
  def initialize(config, endpoint, namespace, version)
@@ -68,7 +69,7 @@ module AdsCommon
68
69
  AdsCommon::Http.configure_httpi(@config, httpi)
69
70
  end
70
71
  client.config.raise_errors = false
71
- client.config.logger.subject = get_logger()
72
+ client.config.logger.subject = NoopLogger.new
72
73
  return client
73
74
  end
74
75
 
@@ -86,9 +87,9 @@ module AdsCommon
86
87
  registry = get_service_registry()
87
88
  validator = ParametersValidator.new(registry)
88
89
  args = validator.validate_args(action_name, args)
89
- response = handle_soap_request(
90
+ request_info, response = handle_soap_request(
90
91
  action_name.to_sym, false, args, validator.extra_namespaces)
91
- log_headers(response.http.headers)
92
+ do_logging(action_name, request_info, response)
92
93
  handle_errors(response)
93
94
  extractor = ResultsExtractor.new(registry)
94
95
  result = extractor.extract_result(response, action_name, &block)
@@ -96,24 +97,20 @@ module AdsCommon
96
97
  return result
97
98
  end
98
99
 
99
- # Logs response headers.
100
- # TODO: this needs to go on http or httpi level.
101
- def log_headers(headers)
102
- get_logger().debug(headers.map {|k, v| [k, v].join(': ')}.join(', '))
103
- end
104
-
105
100
  # Executes the SOAP request with original SOAP name.
106
101
  def handle_soap_request(action, xml_only, args, extra_namespaces)
107
102
  original_action_name =
108
103
  get_service_registry.get_method_signature(action)[:original_name]
109
104
  original_action_name = action if original_action_name.nil?
105
+ request_info = nil
110
106
  response = @client.request(original_action_name) do |soap, wsdl, http|
111
107
  soap.body = args
112
108
  @header_handler.prepare_request(http, soap)
113
109
  soap.namespaces.merge!(extra_namespaces) unless extra_namespaces.nil?
114
110
  return soap.to_xml if xml_only
111
+ request_info = RequestInfo.new(soap.to_xml, http.headers, http.url)
115
112
  end
116
- return response
113
+ return request_info, response
117
114
  end
118
115
 
119
116
  # Checks for errors in response and raises appropriate exception.
@@ -166,5 +163,103 @@ module AdsCommon
166
163
  end
167
164
  return nil
168
165
  end
166
+
167
+ # Log the request, response, and summary lines.
168
+ def do_logging(action, request, response)
169
+ logger = get_logger()
170
+ return unless should_log_summary(logger.level, response.soap_fault?)
171
+
172
+ response_hash = response.hash
173
+
174
+ soap_headers = {}
175
+ begin
176
+ soap_headers = response_hash[:envelope][:header][:response_header]
177
+ rescue NoMethodError
178
+ # If there are no headers, just ignore. We'll log what we know.
179
+ end
180
+
181
+ summary_message = ('ID: %s, URL: %s, Service: %s, Action: %s, Response ' +
182
+ 'time: %sms, Request ID: %s') % [@header_handler.identifier,
183
+ request.url, self.class.to_s.split("::").last, action,
184
+ soap_headers[:response_time], soap_headers[:request_id]]
185
+ if soap_headers[:operations]
186
+ summary_message += ', Operations: %s' % soap_headers[:operations]
187
+ end
188
+ summary_message += ', Is fault: %s' % response.soap_fault?
189
+
190
+ request_message = nil
191
+ response_message = nil
192
+
193
+ if should_log_payloads(logger.level, response.soap_fault?)
194
+ request_message = 'Outgoing request: %s %s' %
195
+ [format_headers(request.headers), sanitize_request(request.body)]
196
+ response_message = 'Incoming response: %s %s' %
197
+ [format_headers(response.http.headers), response.http.body]
198
+ end
199
+
200
+ if response.soap_fault?
201
+ summary_message += ', Fault message: %s' % format_fault(
202
+ response_hash[:envelope][:body][:fault][:faultstring])
203
+ logger.warn(summary_message)
204
+ logger.info(request_message) if request_message
205
+ logger.info(response_message) if response_message
206
+ else
207
+ logger.info(summary_message)
208
+ logger.debug(request_message) if request_message
209
+ logger.debug(response_message) if response_message
210
+ end
211
+ end
212
+
213
+ # Format headers, redacting sensitive information.
214
+ def format_headers(headers)
215
+ return headers.map do |k, v|
216
+ v = 'REDACTED' if k == 'Authorization'
217
+ [k, v].join(': ')
218
+ end.join(', ')
219
+ end
220
+
221
+ # Sanitize the request body, redacting sensitive information.
222
+ def sanitize_request(body)
223
+ body.gsub(/developerToken>[a-zA-Z0-9_\-]+<\//,
224
+ 'developerToken>REDACTED</')
225
+ end
226
+
227
+ # Format the fault message by capping length and removing newlines.
228
+ def format_fault(message)
229
+ message = message[0...MAX_FAULT_LOG_LENGTH] if
230
+ message.length > MAX_FAULT_LOG_LENGTH
231
+ message = message.gsub("\n", " ")
232
+ return message
233
+ end
234
+
235
+ # Check whether or not to log request summaries based on log level.
236
+ def should_log_summary(level, is_fault)
237
+ # Fault summaries log at WARN.
238
+ return level <= Logger::WARN if is_fault
239
+ # Success summaries log at INFO.
240
+ return level <= Logger::INFO
241
+ end
242
+
243
+ # Check whether or not to log payloads based on log level.
244
+ def should_log_payloads(level, is_fault)
245
+ # Fault payloads log at INFO.
246
+ return level <= Logger::INFO if is_fault
247
+ # Success payloads log at DEBUG.
248
+ return level <= Logger::DEBUG
249
+ end
250
+
251
+ class RequestInfo
252
+ attr_accessor :body, :headers, :url
253
+
254
+ def initialize(body, headers, url)
255
+ @body, @headers, @url = body, headers, url
256
+ end
257
+ end
258
+
259
+ class NoopLogger
260
+ def method_missing(m, *args, &block)
261
+ nil
262
+ end
263
+ end
169
264
  end
170
265
  end
@@ -19,6 +19,6 @@
19
19
 
20
20
  module AdsCommon
21
21
  module ApiConfig
22
- CLIENT_LIB_VERSION = '0.13.0'
22
+ CLIENT_LIB_VERSION = '0.14.0'
23
23
  end
24
24
  end
@@ -19,6 +19,7 @@
19
19
  # Tests the array replies from services.
20
20
 
21
21
  require 'test/unit'
22
+ require 'logger'
22
23
 
23
24
  require 'ads_common/config'
24
25
  require 'ads_common/savon_service'
@@ -27,6 +28,8 @@ require 'ads_common/savon_service'
27
28
  class StubService < AdsCommon::SavonService
28
29
 
29
30
  public :get_service_registry, :get_module
31
+ public :format_headers, :sanitize_request, :format_fault
32
+ public :should_log_summary, :should_log_payloads
30
33
 
31
34
  def initialize(namespace, endpoint, version)
32
35
  @logger = Logger.new(STDERR)
@@ -64,4 +67,59 @@ class TestSavonService < Test::Unit::TestCase
64
67
  def test_get_module_abstract()
65
68
  assert_raises(NoMethodError) { @stub_service.get_module() }
66
69
  end
70
+
71
+ def test_format_headers()
72
+ test1 = {
73
+ 'header1' => 'value1',
74
+ 'header2' => 'value2'
75
+ }
76
+ expected1 = "header1: value1, header2: value2"
77
+ test2 = {
78
+ 'Authorization' => 'Bearer ABCD',
79
+ 'header3' => 'value3'
80
+ }
81
+ expected2 = "Authorization: REDACTED, header3: value3"
82
+ assert_equal(expected1, @stub_service.format_headers(test1))
83
+ assert_equal(expected2, @stub_service.format_headers(test2))
84
+ end
85
+
86
+ def test_sanitize_request()
87
+ test1 = "<some_xml><developerToken>ab1cdEF2GH-IJ3KL_mn4OP" +
88
+ "</developerToken></some_xml>"
89
+ expected1 = "<some_xml><developerToken>REDACTED</developerToken></some_xml>"
90
+ test2 = "<xml><element1></element1><element2></element2></xml>"
91
+ test3 = "<xml><ns1:developerToken>w-x_Y-Z_</ns1:developerToken></xml>"
92
+ expected3 = "<xml><ns1:developerToken>REDACTED</ns1:developerToken></xml>"
93
+ assert_equal(expected1, @stub_service.sanitize_request(test1))
94
+ assert_equal(test2, @stub_service.sanitize_request(test2))
95
+ assert_equal(expected3, @stub_service.sanitize_request(test3))
96
+ end
97
+
98
+ def test_format_fault()
99
+ test1 = "fault\nwith\nnewlines\nand\nunicode\n\u2713\n"+
100
+ "\u3084\u3063\u305F".encode('utf-8')
101
+ expected1 = "fault with newlines and unicode \u2713 \u3084\u3063\u305F"
102
+ test2 = "This needs to be more than 16000 characters......." * 1000
103
+ expected2 = "This needs to be more than 16000 characters......." * 320
104
+ assert(test2.length > AdsCommon::SavonService::MAX_FAULT_LOG_LENGTH)
105
+ assert(expected2.length == AdsCommon::SavonService::MAX_FAULT_LOG_LENGTH)
106
+ assert_equal(expected1, @stub_service.format_fault(test1))
107
+ assert_equal(expected2, @stub_service.format_fault(test2))
108
+ end
109
+
110
+ def test_log_levels()
111
+ assert_equal(true, @stub_service.should_log_payloads(Logger::DEBUG, true))
112
+ assert_equal(true, @stub_service.should_log_payloads(Logger::DEBUG, false))
113
+ assert_equal(true, @stub_service.should_log_payloads(Logger::INFO, true))
114
+ assert_equal(false, @stub_service.should_log_payloads(Logger::INFO, false))
115
+ assert_equal(false, @stub_service.should_log_payloads(Logger::WARN, true))
116
+ assert_equal(false, @stub_service.should_log_payloads(Logger::WARN, false))
117
+
118
+ assert_equal(true, @stub_service.should_log_summary(Logger::INFO, true))
119
+ assert_equal(true, @stub_service.should_log_summary(Logger::INFO, false))
120
+ assert_equal(true, @stub_service.should_log_summary(Logger::WARN, true))
121
+ assert_equal(false, @stub_service.should_log_summary(Logger::WARN, false))
122
+ assert_equal(false, @stub_service.should_log_summary(Logger::ERROR, true))
123
+ assert_equal(false, @stub_service.should_log_summary(Logger::ERROR, false))
124
+ end
67
125
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-ads-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergio Gomes
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-03-16 00:00:00.000000000 Z
13
+ date: 2017-05-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: google-ads-savon