google-cloud-logging 0.23.0 → 0.23.1

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: 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