google-cloud-logging 2.0.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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +407 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/INSTRUMENTATION.md +71 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +321 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/lib/google-cloud-logging.rb +161 -0
  13. data/lib/google/cloud/logging.rb +188 -0
  14. data/lib/google/cloud/logging/async_writer.rb +513 -0
  15. data/lib/google/cloud/logging/convert.rb +70 -0
  16. data/lib/google/cloud/logging/credentials.rb +44 -0
  17. data/lib/google/cloud/logging/entry.rb +528 -0
  18. data/lib/google/cloud/logging/entry/http_request.rb +167 -0
  19. data/lib/google/cloud/logging/entry/list.rb +178 -0
  20. data/lib/google/cloud/logging/entry/operation.rb +91 -0
  21. data/lib/google/cloud/logging/entry/source_location.rb +85 -0
  22. data/lib/google/cloud/logging/errors.rb +101 -0
  23. data/lib/google/cloud/logging/log/list.rb +156 -0
  24. data/lib/google/cloud/logging/logger.rb +633 -0
  25. data/lib/google/cloud/logging/metric.rb +168 -0
  26. data/lib/google/cloud/logging/metric/list.rb +170 -0
  27. data/lib/google/cloud/logging/middleware.rb +307 -0
  28. data/lib/google/cloud/logging/project.rb +838 -0
  29. data/lib/google/cloud/logging/rails.rb +232 -0
  30. data/lib/google/cloud/logging/resource.rb +85 -0
  31. data/lib/google/cloud/logging/resource_descriptor.rb +137 -0
  32. data/lib/google/cloud/logging/resource_descriptor/list.rb +175 -0
  33. data/lib/google/cloud/logging/service.rb +239 -0
  34. data/lib/google/cloud/logging/sink.rb +315 -0
  35. data/lib/google/cloud/logging/sink/list.rb +168 -0
  36. data/lib/google/cloud/logging/version.rb +22 -0
  37. metadata +304 -0
@@ -0,0 +1,188 @@
1
+ # Copyright 2016 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
+ # https://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
+ require "google-cloud-logging"
17
+ require "google/cloud/logging/project"
18
+ require "google/cloud/config"
19
+ require "google/cloud/env"
20
+ require "stackdriver/core"
21
+
22
+ module Google
23
+ module Cloud
24
+ ##
25
+ # # Stackdriver Logging
26
+ #
27
+ # The Stackdriver Logging service collects and stores logs from applications
28
+ # and services on the Google Cloud Platform, giving you fine-grained,
29
+ # programmatic control over your projects' logs. You can use the Stackdriver
30
+ # Logging API to:
31
+ #
32
+ # * Read and filter log entries
33
+ # * Export your log entries to Cloud Storage, BigQuery, or Cloud Pub/Sub
34
+ # * Create logs-based metrics for use in Cloud Monitoring
35
+ # * Write log entries
36
+ #
37
+ # For general information about Stackdriver Logging, read [Stackdriver
38
+ # Logging Documentation](https://cloud.google.com/logging/docs/).
39
+ #
40
+ # See {file:OVERVIEW.md Stackdriver Logging Overview}.
41
+ #
42
+ module Logging
43
+ ##
44
+ # Creates a new object for connecting to the Stackdriver Logging service.
45
+ # Each call creates a new connection.
46
+ #
47
+ # For more information on connecting to Google Cloud see the
48
+ # {file:AUTHENTICATION.md Authentication Guide}.
49
+ #
50
+ # @param [String] project_id Project identifier for the Stackdriver
51
+ # Logging service you are connecting to. If not present, the default
52
+ # project for the credentials is used.
53
+ # @param [String, Hash, Google::Auth::Credentials] credentials The path to
54
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
55
+ # Google::Auth::Credentials object. (See {Logging::Credentials})
56
+ # @param [String, Array<String>] scope The OAuth 2.0 scopes controlling
57
+ # the set of resources and operations that the connection can access.
58
+ # See [Using OAuth 2.0 to Access Google
59
+ # APIs](https://developers.google.com/identity/protocols/OAuth2).
60
+ #
61
+ # The default scope is:
62
+ #
63
+ # * `https://www.googleapis.com/auth/logging.admin`
64
+ #
65
+ # @param [Integer] timeout Default timeout to use in requests. Optional.
66
+ # @param [String] endpoint Override of the endpoint host name. Optional.
67
+ # If the param is nil, uses the default endpoint.
68
+ # @param [String] project Alias for the `project_id` argument. Deprecated.
69
+ # @param [String] keyfile Alias for the `credentials` argument.
70
+ # Deprecated.
71
+ #
72
+ # @return [Google::Cloud::Logging::Project]
73
+ #
74
+ # @example
75
+ # require "google/cloud/logging"
76
+ #
77
+ # logging = Google::Cloud::Logging.new
78
+ #
79
+ # entries = logging.entries
80
+ # entries.each do |e|
81
+ # puts "[#{e.timestamp}] #{e.log_name} #{e.payload.inspect}"
82
+ # end
83
+ #
84
+ def self.new project_id: nil,
85
+ credentials: nil,
86
+ scope: nil,
87
+ timeout: nil,
88
+ endpoint: nil,
89
+ project: nil,
90
+ keyfile: nil
91
+ project_id ||= (project || default_project_id)
92
+ scope ||= configure.scope
93
+ timeout ||= configure.timeout
94
+ endpoint ||= configure.endpoint
95
+ credentials ||= (keyfile || default_credentials(scope: scope))
96
+
97
+ unless credentials.is_a? Google::Auth::Credentials
98
+ credentials = Logging::Credentials.new credentials, scope: scope
99
+ end
100
+
101
+ if credentials.respond_to? :project_id
102
+ project_id ||= credentials.project_id
103
+ end
104
+ project_id = project_id.to_s # Always cast to a string
105
+ raise ArgumentError, "project_id is missing" if project_id.empty?
106
+
107
+ service = Logging::Service.new project_id, credentials, host: endpoint, timeout: timeout
108
+ Logging::Project.new service
109
+ end
110
+
111
+ ##
112
+ # Configure the Google::Cloud::Logging::Middleware when used in a
113
+ # Rack-based application.
114
+ #
115
+ # The following Stackdriver Logging configuration parameters are
116
+ # supported:
117
+ #
118
+ # * `project_id` - (String) Project identifier for the Stackdriver
119
+ # Logging service you are connecting to. (The parameter `project` is
120
+ # considered deprecated, but may also be used.)
121
+ # * `credentials` - (String, Hash, Google::Auth::Credentials) The path to
122
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
123
+ # Google::Auth::Credentials object. (See {Logging::Credentials}) (The
124
+ # parameter `keyfile` is considered deprecated, but may also be used.)
125
+ # * `scope` - (String, Array<String>) The OAuth 2.0 scopes controlling
126
+ # the set of resources and operations that the connection can access.
127
+ # * `timeout` - (Integer) Default timeout to use in requests.
128
+ # * `endpoint` - (String) Override of the endpoint host name, or `nil`
129
+ # to use the default endpoint.
130
+ # * `log_name` - (String) Name of the application log file. Default:
131
+ # `"ruby_app_log"`
132
+ # * `log_name_map` - (Hash) Map specific request routes to other log.
133
+ # Default: `{ "/_ah/health" => "ruby_health_check_log" }`
134
+ # * `monitored_resource.type` (String) Resource type name. See [full
135
+ # list](https://cloud.google.com/logging/docs/api/v2/resource-list).
136
+ # Self discovered on GCP.
137
+ # * `monitored_resource.labels` -(Hash) Resource labels. See [full
138
+ # list](https://cloud.google.com/logging/docs/api/v2/resource-list).
139
+ # Self discovered on GCP.
140
+ # * `labels` - (Hash) User defined labels. A `Hash` of label names to
141
+ # string label values or callables/`Proc` which are functions of the
142
+ # Rack environment.
143
+ # * `set_default_logger_on_rails_init` - (Boolean) Whether Google Cloud
144
+ # Logging Logger should be allowed to start background threads and open
145
+ # gRPC connections during Rails initialization. This should only be used
146
+ # with a non-forking web server. Web servers such as Puma and Unicorn
147
+ # should not set this, and instead set the Rails logger to a Google
148
+ # Cloud Logging Logger object on the worker process by calling
149
+ # {Railtie.set_default_logger} at the appropriate time, such as a
150
+ # post-fork hook.
151
+ # * `on_error` - (Proc) A Proc to be run when an error is encountered
152
+ # on a background thread. The Proc must take the error object as the
153
+ # single argument. (See {AsyncWriter.on_error}.)
154
+ #
155
+ # See the [Configuration
156
+ # Guide](https://googleapis.dev/ruby/stackdriver/latest/file.INSTRUMENTATION_CONFIGURATION.html)
157
+ # for full configuration parameters.
158
+ #
159
+ # @return [Google::Cloud::Config] The configuration object
160
+ # the Google::Cloud::Logging module uses.
161
+ #
162
+ def self.configure
163
+ yield Google::Cloud.configure.logging if block_given?
164
+
165
+ Google::Cloud.configure.logging
166
+ end
167
+
168
+ ##
169
+ # @private Default project.
170
+ def self.default_project_id
171
+ Google::Cloud.configure.logging.project_id ||
172
+ Google::Cloud.configure.project_id ||
173
+ Google::Cloud.env.project_id
174
+ end
175
+
176
+ ##
177
+ # @private Default credentials.
178
+ def self.default_credentials scope: nil
179
+ Google::Cloud.configure.logging.credentials ||
180
+ Google::Cloud.configure.credentials ||
181
+ Logging::Credentials.default(scope: scope)
182
+ end
183
+ end
184
+ end
185
+
186
+ # @private
187
+ Logging = Cloud::Logging unless const_defined? :Logging
188
+ end
@@ -0,0 +1,513 @@
1
+ # Copyright 2016 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
+ # https://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
+ require "monitor"
17
+ require "concurrent"
18
+ require "google/cloud/logging/errors"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Logging
23
+ ##
24
+ # # AsyncWriter
25
+ #
26
+ # AsyncWriter buffers, batches, and transmits log entries efficiently.
27
+ # Writing log entries is asynchronous and will not block.
28
+ #
29
+ # Batches that cannot be delivered immediately are queued. When the queue
30
+ # is full new batch requests will raise errors that can be consumed using
31
+ # the {#on_error} callback. This provides back pressure in case the writer
32
+ # cannot keep up with requests.
33
+ #
34
+ # This object is thread-safe; it may accept write requests from
35
+ # multiple threads simultaneously, and will serialize them when
36
+ # executing in the background thread.
37
+ #
38
+ # @example
39
+ # require "google/cloud/logging"
40
+ #
41
+ # logging = Google::Cloud::Logging.new
42
+ #
43
+ # async = logging.async_writer
44
+ #
45
+ # entry1 = logging.entry payload: "Job started."
46
+ # entry2 = logging.entry payload: "Job completed."
47
+ #
48
+ # labels = { job_size: "large", job_code: "red" }
49
+ # resource = logging.resource "gae_app",
50
+ # "module_id" => "1",
51
+ # "version_id" => "20150925t173233"
52
+ #
53
+ # async.write_entries [entry1, entry2],
54
+ # log_name: "my_app_log",
55
+ # resource: resource,
56
+ # labels: labels
57
+ #
58
+ class AsyncWriter
59
+ include MonitorMixin
60
+
61
+ ##
62
+ # @private Implementation accessors
63
+ attr_reader :logging, :max_bytes, :max_count, :interval,
64
+ :threads, :max_queue, :partial_success
65
+
66
+ ##
67
+ # @private Creates a new AsyncWriter instance.
68
+ def initialize logging, max_count: 10000, max_bytes: 10000000,
69
+ max_queue: 100, interval: 5, threads: 10,
70
+ partial_success: false
71
+ # init MonitorMixin
72
+ super()
73
+
74
+ @logging = logging
75
+
76
+ @max_count = max_count
77
+ @max_bytes = max_bytes
78
+ @max_queue = max_queue
79
+ @interval = interval
80
+ @threads = threads
81
+
82
+ @partial_success = partial_success
83
+
84
+ @error_callbacks = []
85
+
86
+ @cond = new_cond
87
+
88
+ # Make sure all buffered messages are sent when process exits.
89
+ at_exit { stop }
90
+ end
91
+
92
+ ##
93
+ # Asynchronously write one or more log entries to the Stackdriver
94
+ # Logging service.
95
+ #
96
+ # Unlike the main write_entries method, this method usually does not
97
+ # block. The actual write RPCs will happen in the background, and may
98
+ # be batched with related calls. However, if the queue is full, this
99
+ # method will block until enough space has cleared out.
100
+ #
101
+ # @param [Google::Cloud::Logging::Entry,
102
+ # Array<Google::Cloud::Logging::Entry>] entries One or more entry
103
+ # objects to write. The log entries must have values for all required
104
+ # fields.
105
+ # @param [String] log_name A default log ID for those log entries in
106
+ # `entries` that do not specify their own `log_name`. See also
107
+ # {Entry#log_name=}.
108
+ # @param [Resource] resource A default monitored resource for those log
109
+ # entries in entries that do not specify their own resource. See also
110
+ # {Entry#resource}.
111
+ # @param [Hash{Symbol,String => String}] labels User-defined `key:value`
112
+ # items that are added to the `labels` field of each log entry in
113
+ # `entries`, except when a log entry specifies its own `key:value`
114
+ # item with the same key. See also {Entry#labels=}.
115
+ #
116
+ # @return [Google::Cloud::Logging::AsyncWriter] Returns self.
117
+ #
118
+ # @example
119
+ # require "google/cloud/logging"
120
+ #
121
+ # logging = Google::Cloud::Logging.new
122
+ # async = logging.async_writer
123
+ #
124
+ # entry = logging.entry payload: "Job started.",
125
+ # log_name: "my_app_log"
126
+ # entry.resource.type = "gae_app"
127
+ # entry.resource.labels[:module_id] = "1"
128
+ # entry.resource.labels[:version_id] = "20150925t173233"
129
+ #
130
+ # async.write_entries entry
131
+ #
132
+ def write_entries entries, log_name: nil, resource: nil, labels: nil
133
+ synchronize do
134
+ raise "AsyncWriter has been stopped" if @stopped
135
+
136
+ Array(entries).each do |entry|
137
+ # Update the entry to have all the data directly on it
138
+ entry.log_name ||= log_name
139
+ if entry.resource.nil? || entry.resource.empty?
140
+ entry.resource = resource
141
+ end
142
+ entry.labels = labels if entry.labels.nil? || entry.labels.empty?
143
+
144
+ # Add the entry to the batch
145
+ @batch ||= Batch.new self
146
+ next if @batch.try_add entry
147
+
148
+ # If we can't add to the batch, publish and create a new batch
149
+ publish_batch!
150
+ @batch = Batch.new self
151
+ @batch.add entry
152
+ end
153
+
154
+ @thread_pool ||= Concurrent::ThreadPoolExecutor.new \
155
+ max_threads: @threads, max_queue: @max_queue
156
+ @thread ||= Thread.new { run_background }
157
+
158
+ publish_batch! if @batch&.ready?
159
+
160
+ @cond.broadcast
161
+ end
162
+ self
163
+ end
164
+
165
+ ##
166
+ # Creates a logger instance that is API-compatible with Ruby's standard
167
+ # library [Logger](http://ruby-doc.org/stdlib/libdoc/logger/rdoc).
168
+ #
169
+ # The logger will use AsyncWriter to transmit log entries on a
170
+ # background thread.
171
+ #
172
+ # @param [String] log_name A log resource name to be associated with the
173
+ # written log entries.
174
+ # @param [Google::Cloud::Logging::Resource] resource The monitored
175
+ # resource to be associated with written log entries.
176
+ # @param [Hash] labels A set of user-defined data to be associated with
177
+ # written log entries.
178
+ #
179
+ # @return [Google::Cloud::Logging::Logger] a Logger object that can be
180
+ # used in place of a ruby standard library logger object.
181
+ #
182
+ # @example
183
+ # require "google/cloud/logging"
184
+ #
185
+ # logging = Google::Cloud::Logging.new
186
+ #
187
+ # resource = logging.resource "gae_app",
188
+ # module_id: "1",
189
+ # version_id: "20150925t173233"
190
+ #
191
+ # async = logging.async_writer
192
+ # logger = async.logger "my_app_log", resource, env: :production
193
+ # logger.info "Job started."
194
+ #
195
+ def logger log_name, resource, labels = {}
196
+ Logger.new self, log_name, resource, labels
197
+ end
198
+
199
+ ##
200
+ # Begins the process of stopping the writer. Entries already in the
201
+ # queue will be published, but no new entries can be added. Use {#wait!}
202
+ # to block until the writer is fully stopped and all pending entries
203
+ # have been published.
204
+ #
205
+ # @return [AsyncWriter] returns self so calls can be chained.
206
+ def stop
207
+ synchronize do
208
+ break if @stopped
209
+
210
+ @stopped = true
211
+ publish_batch!
212
+ @cond.broadcast
213
+ @thread_pool.shutdown if @thread_pool
214
+ end
215
+
216
+ self
217
+ end
218
+
219
+ ##
220
+ # Blocks until the writer is fully stopped, all pending entries have
221
+ # been published, and all callbacks have completed. Does not stop the
222
+ # writer. To stop the writer, first call {#stop} and then call {#wait!}
223
+ # to block until the writer is stopped.
224
+ #
225
+ # @param [Number, nil] timeout The maximum number of seconds to wait for
226
+ # shutdown to complete. Will wait forever when the value is `nil`. The
227
+ # default value is `nil`.
228
+ #
229
+ # @return [AsyncWriter] returns self so calls can be chained.
230
+ def wait! timeout = nil
231
+ synchronize do
232
+ if @thread_pool
233
+ @thread_pool.shutdown
234
+ @thread_pool.wait_for_termination timeout
235
+ end
236
+ end
237
+
238
+ self
239
+ end
240
+
241
+ ##
242
+ # Stop this asynchronous writer and block until it has been stopped.
243
+ #
244
+ # @param [Number, nil] timeout The maximum number of seconds to wait for
245
+ # shutdown to complete. Will wait forever when the value is `nil`. The
246
+ # default value is `nil`.
247
+ # @param [Boolean] force If set to true, and the writer hasn't stopped
248
+ # within the given timeout, kill it forcibly by terminating the
249
+ # thread. This should be used with extreme caution, as it can
250
+ # leave RPCs unfinished. Default is false.
251
+ #
252
+ # @return [Symbol] Returns `:new` if {#write_entries} has never been
253
+ # called on the AsyncWriter, `:stopped` if it was already stopped
254
+ # at the time of invocation, `:waited` if it stopped during the
255
+ # timeout period, `:timeout` if it is still running after the
256
+ # timeout, or `:forced` if it was forcibly killed.
257
+ #
258
+ def stop! timeout = nil, force: nil
259
+ return :new unless @thread_pool
260
+ return :stopped if stopped?
261
+
262
+ stop
263
+ wait! timeout
264
+
265
+ if synchronize { @thread_pool.shutdown? }
266
+ return :waited if timeout
267
+ elsif force
268
+ @thread_pool.kill
269
+ return :forced
270
+ end
271
+ :timeout
272
+ end
273
+ alias async_stop! stop!
274
+
275
+ ##
276
+ # Forces all entries in the current batch to be published
277
+ # immediately.
278
+ #
279
+ # @return [AsyncWriter] returns self so calls can be chained.
280
+ def flush
281
+ synchronize do
282
+ publish_batch!
283
+ @cond.broadcast
284
+ end
285
+
286
+ self
287
+ end
288
+
289
+ ##
290
+ # Whether the writer has been started.
291
+ #
292
+ # @return [boolean] `true` when started, `false` otherwise.
293
+ def started?
294
+ !stopped?
295
+ end
296
+
297
+ ##
298
+ # Whether the writer has been stopped.
299
+ #
300
+ # @return [boolean] `true` when stopped, `false` otherwise.
301
+ def stopped?
302
+ synchronize { @stopped }
303
+ end
304
+
305
+ ##
306
+ # Register to be notified of errors when raised.
307
+ #
308
+ # If an unhandled error has occurred the writer will attempt to
309
+ # recover from the error and resume buffering, batching, and
310
+ # transmitting log entries
311
+ #
312
+ # Multiple error handlers can be added.
313
+ #
314
+ # @yield [callback] The block to be called when an error is raised.
315
+ # @yieldparam [Exception] error The error raised.
316
+ #
317
+ # @example
318
+ # require "google/cloud/logging"
319
+ # require "google/cloud/error_reporting"
320
+ #
321
+ # logging = Google::Cloud::Logging.new
322
+ #
323
+ # resource = logging.resource "gae_app",
324
+ # module_id: "1",
325
+ # version_id: "20150925t173233"
326
+ #
327
+ # async = logging.async_writer
328
+ #
329
+ # # Register to be notified when unhandled errors occur.
330
+ # async.on_error do |error|
331
+ # # error can be a AsyncWriterError or AsyncWriteEntriesError
332
+ # Google::Cloud::ErrorReporting.report error
333
+ # end
334
+ #
335
+ # logger = async.logger "my_app_log", resource, env: :production
336
+ # logger.info "Job started."
337
+ #
338
+ def on_error &block
339
+ synchronize do
340
+ @error_callbacks << block
341
+ end
342
+ end
343
+
344
+ ##
345
+ # The most recent unhandled error to occur while transmitting log
346
+ # entries.
347
+ #
348
+ # If an unhandled error has occurred the subscriber will attempt to
349
+ # recover from the error and resume buffering, batching, and
350
+ # transmitting log entries.
351
+ #
352
+ # @return [Exception, nil] error The most recent error raised.
353
+ #
354
+ # @example
355
+ # require "google/cloud/logging"
356
+ #
357
+ # logging = Google::Cloud::Logging.new
358
+ #
359
+ # resource = logging.resource "gae_app",
360
+ # module_id: "1",
361
+ # version_id: "20150925t173233"
362
+ #
363
+ # async = logging.async_writer
364
+ #
365
+ # logger = async.logger "my_app_log", resource, env: :production
366
+ # logger.info "Job started."
367
+ #
368
+ # # If an error was raised, it can be retrieved here:
369
+ # async.last_error #=> nil
370
+ #
371
+ def last_error
372
+ synchronize { @last_error }
373
+ end
374
+ alias last_exception last_error
375
+
376
+ protected
377
+
378
+ def run_background
379
+ synchronize do
380
+ until @stopped
381
+ if @batch.nil?
382
+ @cond.wait
383
+ next
384
+ end
385
+
386
+ if @batch.ready?
387
+ # interval met, publish the batch...
388
+ publish_batch!
389
+ @cond.wait
390
+ else
391
+ # still waiting for the interval to publish the batch...
392
+ @cond.wait @batch.publish_wait
393
+ end
394
+ end
395
+ end
396
+ end
397
+
398
+ def publish_batch!
399
+ return unless @batch
400
+
401
+ batch_to_be_published = @batch
402
+ @batch = nil
403
+ publish_batch_async batch_to_be_published
404
+ end
405
+
406
+ # Sets the last_error and calls all error callbacks.
407
+ def error! error
408
+ error_callbacks = synchronize do
409
+ @last_error = error
410
+ @error_callbacks
411
+ end
412
+ error_callbacks = default_error_callbacks if error_callbacks.empty?
413
+ error_callbacks.each { |error_callback| error_callback.call error }
414
+ end
415
+
416
+ def default_error_callbacks
417
+ # This is memoized to reduce calls to the configuration.
418
+ @default_error_callbacks ||= begin
419
+ error_callback = Google::Cloud::Logging.configure.on_error
420
+ error_callback ||= Google::Cloud.configure.on_error
421
+ if error_callback
422
+ [error_callback]
423
+ else
424
+ []
425
+ end
426
+ end
427
+ end
428
+
429
+ def publish_batch_async batch
430
+ Concurrent::Promises.future_on(
431
+ @thread_pool, batch.entries
432
+ ) do |entries|
433
+ write_entries_with entries
434
+ end
435
+ rescue Concurrent::RejectedExecutionError => e
436
+ async_error = AsyncWriterError.new(
437
+ "Error writing entries: #{e.message}",
438
+ batch.entries
439
+ )
440
+ # Manually set backtrace so we don't have to raise
441
+ async_error.set_backtrace caller
442
+ error! async_error
443
+ end
444
+
445
+ def write_entries_with entries
446
+ logging.write_entries entries, partial_success: partial_success
447
+ rescue StandardError => e
448
+ write_error = AsyncWriteEntriesError.new(
449
+ "Error writing entries: #{e.message}",
450
+ entries
451
+ )
452
+ # Manually set backtrace so we don't have to raise
453
+ write_error.set_backtrace caller
454
+ error! write_error
455
+ end
456
+
457
+ ##
458
+ # @private
459
+ class Batch
460
+ attr_reader :created_at, :entries
461
+
462
+ def initialize writer
463
+ @writer = writer
464
+ @entries = []
465
+ @entries_bytes = 2 # initial size w/ partial_success
466
+ @created_at = nil
467
+ end
468
+
469
+ def add entry, addl_bytes: nil
470
+ addl_bytes ||= addl_bytes_for entry
471
+ @entries << entry
472
+ @entries_bytes += addl_bytes
473
+ @created_at ||= Time.now
474
+ nil
475
+ end
476
+
477
+ def try_add entry
478
+ addl_bytes = addl_bytes_for entry
479
+ new_message_count = @entries.count + 1
480
+ new_message_bytes = @entries_bytes + addl_bytes
481
+ if new_message_count > @writer.max_count ||
482
+ new_message_bytes >= @writer.max_bytes
483
+ return false
484
+ end
485
+ add entry, addl_bytes: addl_bytes
486
+ true
487
+ end
488
+
489
+ def ready?
490
+ @entries.count >= @writer.max_count ||
491
+ @entries_bytes >= @writer.max_bytes ||
492
+ (@created_at.nil? || (publish_at < Time.now))
493
+ end
494
+
495
+ def publish_at
496
+ return nil if @created_at.nil?
497
+ @created_at + @writer.interval
498
+ end
499
+
500
+ def publish_wait
501
+ publish_wait = publish_at - Time.now
502
+ return 0 if publish_wait < 0
503
+ publish_wait
504
+ end
505
+
506
+ def addl_bytes_for entry
507
+ entry.to_grpc.to_proto.bytesize + 2
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end
513
+ end