google-ads-common 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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