google-cloud-logging 0.23.0 → 0.23.1

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: 2658b3d8de071df00d572f934df7ba5ae0be360d
4
- data.tar.gz: 18b9dd38df8bc706d2aa821b0f6030fdda4abef3
3
+ metadata.gz: a74cfb2524ed0697d16624513a1cd6f8731525ba
4
+ data.tar.gz: f5022540c7a2ec3fecc06b730ee1c787c6920527
5
5
  SHA512:
6
- metadata.gz: 889cb152ecd3759eb2b586a18dd6043ea47f85d565e80c55b65f70a23fabbcefd140409f6c27a298ccb6992cf196e6178368c6bbea100a33112bd522ae38d58d
7
- data.tar.gz: 7ef4b2783d70281cdc5b0a4216098cf9e0a49c359a8af8f52d17e9b947d70ae4628d72ef06b56d77eaaefc1eb2d71b96e3bb329e1b50dfdff0bd9745c6d6d326
6
+ metadata.gz: 44fda14faba568efd1d87a208e3764c4aeb3101549b72ffaf8d90f37d13601fcb236e21969e0f772d0d82ece0a9e7b025326c19fd76be0395b7c08f8c8663201
7
+ data.tar.gz: 91842b1780df095396c2a0d11321d3166eb59fdf582bbf26f0aa59ab9ab618d2a8bea8dc283c032b3f786821ccfb3cfad1c61a372f242a53d2e4f3ccf5ba44a6
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require "set"
16
+
15
17
 
16
18
  module Google
17
19
  module Cloud
@@ -52,6 +54,11 @@ module Google
52
54
  #
53
55
  class AsyncWriter
54
56
  DEFAULT_MAX_QUEUE_SIZE = 10000
57
+ CLEANUP_TIMEOUT = 10.0
58
+ WAIT_INTERVAL = 1.0
59
+
60
+ @cleanup_list = nil
61
+ @exit_lock = Mutex.new
55
62
 
56
63
  ##
57
64
  # @private Item in the log entries queue.
@@ -308,7 +315,8 @@ module Google
308
315
  # Blocks until this asynchronous writer has been stopped, or the given
309
316
  # timeout (if present) has elapsed.
310
317
  #
311
- # @param [Number] timeout Timeout in seconds, or nil for no timeout.
318
+ # @param [Number, nil] timeout Timeout in seconds, or `nil` for no
319
+ # timeout.
312
320
  #
313
321
  # @return [Boolean] Returns true if the writer is stopped, or false
314
322
  # if the timeout expired.
@@ -320,12 +328,37 @@ module Google
320
328
  until state == :stopped
321
329
  cur_time = ::Time.new.to_f
322
330
  return false if deadline && cur_time >= deadline
323
- @lock_cond.wait(deadline ? deadline - cur_time : nil)
331
+ interval = deadline ? deadline - cur_time : WAIT_INTERVAL
332
+ interval = WAIT_INTERVAL if interval > WAIT_INTERVAL
333
+ @lock_cond.wait interval
324
334
  end
325
335
  end
326
336
  true
327
337
  end
328
338
 
339
+ ##
340
+ # Stop this asynchronous writer and block until it has been stopped.
341
+ #
342
+ # @param [Number] timeout Timeout in seconds.
343
+ # @param [Boolean] force If set to true, and the writer hasn't stopped
344
+ # within the given timeout, kill it forcibly by terminating the
345
+ # thread. This should be used with extreme caution, as it can
346
+ # leave RPCs unfinished. Default is false.
347
+ #
348
+ # @return [Symbol] Returns `:stopped` if the AsyncWriter was already
349
+ # stopped at the time of invocation, `:waited` if it stopped
350
+ # during the timeout period, `:timeout` if it is still running
351
+ # after the timeout, or `:forced` if it was forcibly killed.
352
+ #
353
+ def stop! timeout, force: false
354
+ return :stopped unless stop
355
+ return :waited if wait_until_stopped timeout
356
+ return :timeout unless force
357
+ @thread.kill
358
+ @thread.join
359
+ :forced
360
+ end
361
+
329
362
  protected
330
363
 
331
364
  ##
@@ -341,14 +374,18 @@ module Google
341
374
  @queue = []
342
375
  @lock = Monitor.new
343
376
  @lock_cond = @lock.new_cond
344
- @thread = Thread.new { run_backgrounder }
377
+ AsyncWriter.register_for_cleanup self
378
+ @thread = Thread.new do
379
+ run_backgrounder
380
+ AsyncWriter.unregister_for_cleanup self
381
+ end
345
382
  end
346
383
  end
347
384
  end
348
385
 
349
386
  ##
350
387
  # @private The background thread implementation, which continuously
351
- # waits for performs work, and returns only when fully stopped.
388
+ # waits for and performs work, and returns only when fully stopped.
352
389
  #
353
390
  def run_backgrounder
354
391
  loop do
@@ -367,14 +404,18 @@ module Google
367
404
  @last_exception = e
368
405
  end
369
406
  end
407
+ ensure
408
+ # If something drastic happened like the thread was killed, make
409
+ # sure the state is consistent.
410
+ @state = :stopped
370
411
  end
371
412
 
372
413
  ##
373
414
  # @private Wait for and dequeue the next set of log entries to transmit.
374
415
  #
375
- # @return [QueueItem,NilClass] Returns the next set of entries. If
416
+ # @return [QueueItem, nil] Returns the next set of entries. If
376
417
  # the writer has been stopped and no more entries are left in the
377
- # queue, returns nil.
418
+ # queue, returns `nil`.
378
419
  #
379
420
  def wait_next_item
380
421
  @lock.synchronize do
@@ -382,14 +423,55 @@ module Google
382
423
  (state == :running && @queue.empty?)
383
424
  @lock_cond.wait
384
425
  end
426
+ queue_item = nil
385
427
  if @queue.empty?
386
428
  @state = :stopped
387
- nil
388
429
  else
389
430
  queue_item = @queue.shift
390
431
  @queue_size -= queue_item.entries.size
391
- @lock_cond.broadcast
392
- queue_item
432
+ end
433
+ @lock_cond.broadcast
434
+ queue_item
435
+ end
436
+ end
437
+
438
+ ##
439
+ # Register the given AsyncWriter for cleanup on VM exit.
440
+ #
441
+ # @private
442
+ #
443
+ def self.register_for_cleanup async
444
+ @exit_lock.synchronize do
445
+ unless @cleanup_list
446
+ @cleanup_list = ::Set.new
447
+ at_exit { AsyncWriter.run_cleanup }
448
+ end
449
+ @cleanup_list.add async
450
+ end
451
+ end
452
+
453
+ ##
454
+ # Unregister the given AsyncWriter for cleanup on VM exit.
455
+ #
456
+ # @private
457
+ #
458
+ def self.unregister_for_cleanup async
459
+ @exit_lock.synchronize do
460
+ @cleanup_list.delete async if @cleanup_list
461
+ end
462
+ end
463
+
464
+ ##
465
+ # Exit hook that cleans up any running AsyncWriters.
466
+ #
467
+ # @private
468
+ #
469
+ def self.run_cleanup
470
+ @exit_lock.synchronize do
471
+ if @cleanup_list
472
+ @cleanup_list.each do |async|
473
+ async.stop! CLEANUP_TIMEOUT, force: true
474
+ end
393
475
  end
394
476
  end
395
477
  end
@@ -21,7 +21,8 @@ module Google
21
21
  ##
22
22
  # # Logger
23
23
  #
24
- # A (mostly) API-compatible logger for ruby's Logger.
24
+ # An API-compatible replacement for ruby's Logger that logs to the
25
+ # Stackdriver Logging Service.
25
26
  #
26
27
  # @example
27
28
  # require "google/cloud/logging"
@@ -36,6 +37,18 @@ module Google
36
37
  # logger.info "Job started."
37
38
  #
38
39
  class Logger
40
+ ##
41
+ # A RequestInfo represents data about the request being handled by the
42
+ # current thread. It is used to configure logs coming from that thread.
43
+ #
44
+ # The trace_id is a String that controls the trace ID sent with the log
45
+ # entry. If it is nil, no trace ID is sent.
46
+ #
47
+ # The log_name is a String that controls the name of the Stackdriver
48
+ # log to write to. If it is nil, the default log_name for this Logger
49
+ # is used.
50
+ RequestInfo = ::Struct.new :trace_id, :log_name
51
+
39
52
  ##
40
53
  # The Google Cloud writer object that calls to {#write_entries} are made
41
54
  # on. Either an AsyncWriter or Project object.
@@ -44,6 +57,7 @@ module Google
44
57
  ##
45
58
  # The Google Cloud log_name to write the log entry with.
46
59
  attr_reader :log_name
60
+ alias_method :progname, :log_name
47
61
 
48
62
  ##
49
63
  # The Google Cloud resource to write the log entry with.
@@ -53,11 +67,37 @@ module Google
53
67
  # The Google Cloud labels to write the log entry with.
54
68
  attr_reader :labels
55
69
 
70
+ ##
71
+ # The logging severity threshold (e.g. `Logger::INFO`)
72
+ attr_reader :level
73
+ alias_method :sev_threshold, :level
74
+
75
+ ##
76
+ # This logger does not use a formatter, but it provides a default
77
+ # Logger::Formatter for API compatibility with the standard Logger.
78
+ attr_accessor :formatter
79
+
80
+ ##
81
+ # This logger does not use a formatter, but it implements this
82
+ # attribute for API compatibility with the standard Logger.
83
+ attr_accessor :datetime_format
84
+
85
+ ##
86
+ # This logger treats progname as an alias for log_name.
87
+ def progname= name
88
+ @log_name = name
89
+ end
90
+
56
91
  ##
57
92
  # A OrderedHash of Thread IDs to Stackdriver request trace ID. The
58
93
  # Stackdriver trace ID is a shared request identifier across all
59
94
  # Stackdriver services.
60
- attr_reader :trace_ids
95
+ #
96
+ # @deprecated Use request_info
97
+ #
98
+ def trace_ids
99
+ @request_info.inject({}) { |r, (k, v)| r[k] = v.trace_id }
100
+ end
61
101
 
62
102
  ##
63
103
  # Create a new Logger instance.
@@ -101,7 +141,11 @@ module Google
101
141
  @resource = resource
102
142
  @labels = labels
103
143
  @level = 0 # DEBUG is the default behavior
104
- @trace_ids = OrderedHash.new
144
+ @request_info = OrderedHash.new
145
+ @closed = false
146
+ # Unused, but present for API compatibility
147
+ @formatter = ::Logger::Formatter.new
148
+ @datetime_format = ""
105
149
  end
106
150
 
107
151
  ##
@@ -240,10 +284,21 @@ module Google
240
284
  end
241
285
  end
242
286
 
243
- write_entry severity, message
287
+ write_entry severity, message unless @closed
288
+ true
244
289
  end
245
290
  alias_method :log, :add
246
291
 
292
+ ##
293
+ # Logs the given message at UNKNOWN severity.
294
+ #
295
+ # @param [String] msg The log entry payload as a string.
296
+ #
297
+ def << msg
298
+ unknown msg
299
+ self
300
+ end
301
+
247
302
  ##
248
303
  # Returns `true` if the current severity level allows for sending
249
304
  # `DEBUG` messages.
@@ -306,29 +361,88 @@ module Google
306
361
  end
307
362
  alias_method :sev_threshold=, :level=
308
363
 
364
+ ##
365
+ # Close the logging "device". This effectively disables logging from
366
+ # this logger; any further log messages will be silently ignored. The
367
+ # logger may be re-enabled by calling #reopen.
368
+ #
369
+ def close
370
+ @closed = true
371
+ self
372
+ end
373
+
374
+ ##
375
+ # Re-enable logging if the logger has been closed.
376
+ #
377
+ # Note that this method accepts a "logdev" argument for compatibility
378
+ # with the standard Ruby Logger class; however, this argument is
379
+ # ignored because this logger does not use a log device.
380
+ #
381
+ def reopen _logdev = nil
382
+ @closed = false
383
+ self
384
+ end
385
+
309
386
  ##
310
387
  # Track a given trace_id by associating it with the current
311
388
  # Thread
312
389
  #
313
- # @param [String] trace_id The HTTP_X_CLOUD_TRACE_CONTEXT HTTP request
314
- # header that's shared and tracked by all Stackdriver services
390
+ # @deprecated Use add_request_info
391
+ #
315
392
  def add_trace_id trace_id
316
- trace_ids[current_thread_id] = trace_id
393
+ add_request_id trace_id: trace_id
394
+ end
395
+
396
+ ##
397
+ # Associate request data with the current Thread. You may provide
398
+ # either the individual pieces of data (trace ID, log name) or a
399
+ # populated RequestInfo object.
400
+ #
401
+ # @param [RequestInfo] info Info about the current request. Optional.
402
+ # If not present, a new RequestInfo is created using the remaining
403
+ # parameters.
404
+ # @param [String, nil] trace_id The trace ID, or `nil` if no trace ID
405
+ # should be logged.
406
+ # @param [String, nil] log_name The log name to use, or nil to use
407
+ # this logger's default.
408
+ #
409
+ def add_request_info info: nil,
410
+ trace_id: nil,
411
+ log_name: nil
412
+ info ||= RequestInfo.new trace_id, log_name
413
+ @request_info[current_thread_id] = info
317
414
 
318
415
  # Start removing old entries if hash gets too large.
319
416
  # This should never happen, because middleware should automatically
320
417
  # remove entries when a request is finished
321
- trace_ids.shift if trace_ids.size > 10_000
418
+ @request_info.shift while @request_info.size > 10_000
419
+
420
+ info
322
421
  end
323
422
 
324
423
  ##
325
- # Untrack the trace_id that's associated with current Thread
424
+ # Get the request data for the current Thread
425
+ #
426
+ # @return [RequestInfo, nil] The request data for the current thread,
427
+ # or `nil` if there is no data set.
326
428
  #
327
- # @return [String] The trace_id that's being deleted
328
- def delete_trace_id
329
- trace_ids.delete current_thread_id
429
+ def request_info
430
+ @request_info[current_thread_id]
330
431
  end
331
432
 
433
+ ##
434
+ # Untrack the RequestInfo that's associated with current Thread
435
+ #
436
+ # @return [RequestInfo] The info that's being deleted
437
+ #
438
+ def delete_request_info
439
+ @request_info.delete current_thread_id
440
+ end
441
+
442
+ ##
443
+ # @deprecated Use delete_request_info
444
+ alias_method :delete_trace_id, :delete_request_info
445
+
332
446
  protected
333
447
 
334
448
  ##
@@ -340,12 +454,18 @@ module Google
340
454
  e.payload = message
341
455
  end
342
456
 
343
- # merge input labels and trace_id
344
- trace_id = trace_ids[current_thread_id]
345
- merged_labels = trace_id.nil? ? {} : { traceId: trace_id }
457
+ # merge input labels and request info
458
+ merged_labels = {}
459
+ actual_log_name = log_name
460
+ info = request_info
461
+ if info
462
+ actual_log_name = info.log_name || actual_log_name
463
+ merged_labels["traceId"] = info.trace_id unless info.trace_id.nil?
464
+ end
346
465
  merged_labels = labels.merge(merged_labels) unless labels.nil?
347
466
 
348
- writer.write_entries entry, log_name: log_name, resource: resource,
467
+ writer.write_entries entry, log_name: actual_log_name,
468
+ resource: resource,
349
469
  labels: merged_labels
350
470
  end
351
471
 
@@ -13,10 +13,17 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "stackdriver/core"
17
+
16
18
  module Google
17
19
  module Cloud
18
20
  module Logging
19
21
  class Middleware
22
+ ##
23
+ # A default value for the log_name_map argument. Directs health check
24
+ # logs to a separate log name so they don't spam the main log.
25
+ DEFAULT_LOG_NAME_MAP = { "/_ah/health" => "ruby_health_check_log" }
26
+
20
27
  ##
21
28
  # The Google::Cloud::Logging::Logger instance
22
29
  attr_reader :logger
@@ -29,13 +36,18 @@ module Google
29
36
  # this middleware. The middleware will be interacting with the logger
30
37
  # to track Stackdriver request trace ID. It also properly sets
31
38
  # env["rack.logger"] to this assigned logger for accessing.
39
+ # @param [Hash] log_name_map A map from URI path to log name override.
40
+ # The path may be a string or regex. If a request path matches an
41
+ # entry in this map, the log name is set accordingly, otherwise the
42
+ # logger's default log_name is used.
32
43
  #
33
44
  # @return [Google::Cloud::Logging::Middleware] A new
34
45
  # Google::Cloud::Logging::Middleware instance
35
46
  #
36
- def initialize app, logger: nil
47
+ def initialize app, logger: nil, log_name_map: DEFAULT_LOG_NAME_MAP
37
48
  @app = app
38
49
  @logger = logger
50
+ @log_name_map = log_name_map
39
51
  end
40
52
 
41
53
  ##
@@ -51,25 +63,44 @@ module Google
51
63
  #
52
64
  def call env
53
65
  env["rack.logger"] = logger
54
- trace_id = extract_trace_id(env)
55
- logger.add_trace_id trace_id
56
-
66
+ trace_id = get_trace_id env
67
+ log_name = get_log_name env
68
+ logger.add_request_info trace_id: trace_id, log_name: log_name
57
69
  begin
58
70
  @app.call env
59
71
  ensure
60
- logger.delete_trace_id
72
+ logger.delete_request_info
61
73
  end
62
74
  end
63
75
 
64
76
  ##
65
- # Extract the trace_id from HTTP request header
66
- # HTTP_X_CLOUD_TRACE_CONTEXT.
67
- #
68
- # @return [String] The trace_id or nil if trace_id is empty.
69
- def extract_trace_id env
70
- trace_context = env["HTTP_X_CLOUD_TRACE_CONTEXT"].to_s
71
- return nil if trace_context.empty?
72
- trace_context.sub(%r{/.*}, "")
77
+ # Determine the trace ID for this request.
78
+ #
79
+ # @private
80
+ # @param [Hash] env The Rack environment.
81
+ # @return [String] The trace ID.
82
+ #
83
+ def get_trace_id env
84
+ trace_context = Stackdriver::Core::TraceContext.parse_rack_env env
85
+ trace_context.trace_id
86
+ end
87
+
88
+ ##
89
+ # Determine the log name override for this request, if any.
90
+ #
91
+ # @private
92
+ # @param [Hash] env The Rack environment.
93
+ # @return [String, nil] The log name override, or `nil` if there is
94
+ # no override.
95
+ #
96
+ def get_log_name env
97
+ return nil unless @log_name_map
98
+ path = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
99
+ path = "/#{path}" unless path.start_with? "/"
100
+ @log_name_map.each do |k, v|
101
+ return v if k === path
102
+ end
103
+ nil
73
104
  end
74
105
 
75
106
  ##
@@ -136,25 +167,29 @@ module Google
136
167
  # the correct monitoring resource types and labels.
137
168
  #
138
169
  # @example If running from GAE, returns default resource:
139
- # rc = Google::Cloud::Logging::Middleware.build_monitored_resource
170
+ # rc = Google::Cloud::Logging::Middleware.send \
171
+ # :default_monitored_resource
140
172
  # rc.type #=> "gae_app"
141
173
  # rc.labels # { module_id: [GAE module name],
142
174
  # # version_id: [GAE module version] }
143
175
  #
144
176
  # @example If running from GKE, returns default resource:
145
- # rc = Google::Cloud::Logging::Middleware.build_monitored_resource
177
+ # rc = Google::Cloud::Logging::Middleware.send \
178
+ # :default_monitored_resource
146
179
  # rc.type #=> "container"
147
180
  # rc.labels # { cluster_name: [GKE cluster name],
148
181
  # # namespace_id: [GKE namespace_id] }
149
182
  #
150
183
  # @example If running from GCE, return default resource:
151
- # rc = Google::Cloud::Logging::Middleware.build_monitored_resource
184
+ # rc = Google::Cloud::Logging::Middleware.send \
185
+ # :default_monitored_resource
152
186
  # rc.type #=> "gce_instance"
153
187
  # rc.labels # { instance_id: [GCE VM instance id],
154
188
  # # zone: [GCE vm group zone] }
155
189
  #
156
190
  # @example Otherwise default to generic "global" type:
157
- # rc = Google::Cloud::Logging::Middleware.build_monitored_resource
191
+ # rc = Google::Cloud::Logging::Middleware.send \
192
+ # :default_monitored_resource
158
193
  # rc.type #=> "global"
159
194
  # rc.labels #=> {}
160
195
  #
@@ -103,23 +103,23 @@ module Google
103
103
  begin
104
104
  Google::Cloud::Logging::Credentials.credentials_with_scope keyfile
105
105
  rescue Exception => e
106
- warn "Unable to initialize Google::Cloud::Logging due " \
107
- "to authorization error: #{e.message}"
106
+ warn "Google::Cloud::Logging is not activated due to " \
107
+ "authorization error: #{e.message}\nFalling back to default " \
108
+ "logger"
108
109
  return false
109
110
  end
110
111
 
111
112
  project_id = gcp_config.logging.project_id || gcp_config.project_id ||
112
113
  Google::Cloud::Logging::Project.default_project
113
114
  if project_id.to_s.empty?
114
- warn "Unable to initialize Google::Cloud::Logging with empty " \
115
- "project_id"
115
+ warn "Google::Cloud::Logging is not activated due to empty " \
116
+ "project_id; falling back to default logger"
116
117
  return false
117
118
  end
118
119
 
119
120
  # Otherwise default to true if Rails is running in production or
120
- # config.stackdriver.use_logging is explicitly true
121
- Rails.env.production? ||
122
- (gcp_config.key?(:use_logging) && gcp_config.use_logging)
121
+ # config.stackdriver.use_logging is true
122
+ Rails.env.production? || gcp_config.use_logging
123
123
  end
124
124
  end
125
125
  end
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Logging
19
- VERSION = "0.23.0"
19
+ VERSION = "0.23.1"
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.23.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-12-09 00:00:00.000000000 Z
12
+ date: 2016-12-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google-cloud-core
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 0.21.0
20
+ version: 0.21.1
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 0.21.0
27
+ version: 0.21.1
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: grpc
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -95,6 +95,20 @@ dependencies:
95
95
  - - '='
96
96
  - !ruby/object:Gem::Version
97
97
  version: 0.0.6
98
+ - !ruby/object:Gem::Dependency
99
+ name: stackdriver-core
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.21.0
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.21.0
98
112
  - !ruby/object:Gem::Dependency
99
113
  name: minitest
100
114
  requirement: !ruby/object:Gem::Requirement
@@ -325,7 +339,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
339
  version: '0'
326
340
  requirements: []
327
341
  rubyforge_project:
328
- rubygems_version: 2.4.5.1
342
+ rubygems_version: 2.6.8
329
343
  signing_key:
330
344
  specification_version: 4
331
345
  summary: API Client library for Stackdriver Logging