vmik-fluent-plugin-google-cloud 0.5.5 → 0.6.4.pre.alpha
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 +7 -0
- data/Gemfile.lock +159 -0
- data/fluent-plugin-google-cloud.gemspec +9 -7
- data/lib/fluent/plugin/monitoring.rb +67 -0
- data/lib/fluent/plugin/out_google_cloud.rb +481 -224
- data/test/plugin/base_test.rb +263 -447
- data/test/plugin/constants.rb +452 -0
- data/test/plugin/test_out_google_cloud.rb +62 -46
- data/test/plugin/test_out_google_cloud_grpc.rb +66 -61
- metadata +83 -109
- data/lib/google/logging/type/http_request_pb.rb +0 -30
- data/lib/google/logging/type/log_severity_pb.rb +0 -26
- data/lib/google/logging/v1/log_entry_pb.rb +0 -52
- data/lib/google/logging/v1/logging_pb.rb +0 -84
- data/lib/google/logging/v1/logging_services_pb.rb +0 -150
@@ -18,12 +18,14 @@ require 'socket'
|
|
18
18
|
require 'time'
|
19
19
|
require 'yaml'
|
20
20
|
require 'google/apis'
|
21
|
-
require 'google/apis/
|
22
|
-
require 'google/logging/
|
23
|
-
require 'google/logging/
|
24
|
-
require 'google/logging/
|
21
|
+
require 'google/apis/logging_v2beta1'
|
22
|
+
require 'google/logging/v2/logging_pb'
|
23
|
+
require 'google/logging/v2/logging_services_pb'
|
24
|
+
require 'google/logging/v2/log_entry_pb'
|
25
25
|
require 'googleauth'
|
26
26
|
|
27
|
+
require_relative 'monitoring'
|
28
|
+
|
27
29
|
module Google
|
28
30
|
module Protobuf
|
29
31
|
# Alias the has_key? method to have the same interface as a regular map.
|
@@ -36,17 +38,48 @@ end
|
|
36
38
|
module Fluent
|
37
39
|
# fluentd output plugin for the Stackdriver Logging API
|
38
40
|
class GoogleCloudOutput < BufferedOutput
|
41
|
+
# Constants for service names and resource types.
|
42
|
+
module Constants
|
43
|
+
APPENGINE_CONSTANTS = {
|
44
|
+
service: 'appengine.googleapis.com',
|
45
|
+
resource_type: 'gae_app'
|
46
|
+
}
|
47
|
+
CLOUDFUNCTIONS_CONSTANTS = {
|
48
|
+
service: 'cloudfunctions.googleapis.com',
|
49
|
+
resource_type: 'cloud_function'
|
50
|
+
}
|
51
|
+
COMPUTE_CONSTANTS = {
|
52
|
+
service: 'compute.googleapis.com',
|
53
|
+
resource_type: 'gce_instance'
|
54
|
+
}
|
55
|
+
CONTAINER_CONSTANTS = {
|
56
|
+
service: 'container.googleapis.com',
|
57
|
+
resource_type: 'container'
|
58
|
+
}
|
59
|
+
DATAFLOW_CONSTANTS = {
|
60
|
+
service: 'dataflow.googleapis.com',
|
61
|
+
resource_type: 'dataflow_step'
|
62
|
+
}
|
63
|
+
DATAPROC_CONSTANTS = {
|
64
|
+
service: 'cluster.dataproc.googleapis.com',
|
65
|
+
resource_type: 'cloud_dataproc_cluster'
|
66
|
+
}
|
67
|
+
EC2_CONSTANTS = {
|
68
|
+
service: 'ec2.amazonaws.com',
|
69
|
+
resource_type: 'aws_ec2_instance'
|
70
|
+
}
|
71
|
+
ML_CONSTANTS = {
|
72
|
+
service: 'ml.googleapis.com',
|
73
|
+
resource_type: 'ml_job'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
include self::Constants
|
78
|
+
|
39
79
|
Fluent::Plugin.register_output('google_cloud', self)
|
40
80
|
|
41
81
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
42
|
-
PLUGIN_VERSION = '0.
|
43
|
-
|
44
|
-
# Constants for service names.
|
45
|
-
APPENGINE_SERVICE = 'appengine.googleapis.com'
|
46
|
-
CLOUDFUNCTIONS_SERVICE = 'cloudfunctions.googleapis.com'
|
47
|
-
COMPUTE_SERVICE = 'compute.googleapis.com'
|
48
|
-
CONTAINER_SERVICE = 'container.googleapis.com'
|
49
|
-
EC2_SERVICE = 'ec2.amazonaws.com'
|
82
|
+
PLUGIN_VERSION = '0.6.3'
|
50
83
|
|
51
84
|
# Name of the the Google cloud logging write scope.
|
52
85
|
LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
|
@@ -155,6 +188,18 @@ module Fluent
|
|
155
188
|
:default => nil,
|
156
189
|
:secret => true
|
157
190
|
|
191
|
+
# Whether to collect metrics about the plugin usage. Use configuration
|
192
|
+
# parameter monitoring_type to select a monitoring system you want to use.
|
193
|
+
config_param :monitoring_enabled, :bool, :default => false
|
194
|
+
|
195
|
+
# What system to use when collecting metrics. Possible values are:
|
196
|
+
# - 'prometheus', in this case default registry in the Prometheus
|
197
|
+
# client library is used, without actually exposing the endpoint
|
198
|
+
# to serve metrics in the Prometheus format.
|
199
|
+
# - any other value will result in the absence of metrics.
|
200
|
+
config_param :monitoring_type, :string,
|
201
|
+
:default => Monitoring::PrometheusMonitoringRegistry.name
|
202
|
+
|
158
203
|
# rubocop:enable Style/HashSyntax
|
159
204
|
|
160
205
|
# TODO: Add a log_name config option rather than just using the tag?
|
@@ -167,7 +212,7 @@ module Fluent
|
|
167
212
|
attr_reader :running_on_managed_vm
|
168
213
|
attr_reader :gae_backend_name
|
169
214
|
attr_reader :gae_backend_version
|
170
|
-
attr_reader :
|
215
|
+
attr_reader :resource
|
171
216
|
attr_reader :common_labels
|
172
217
|
|
173
218
|
def initialize
|
@@ -179,6 +224,25 @@ module Fluent
|
|
179
224
|
def configure(conf)
|
180
225
|
super
|
181
226
|
|
227
|
+
# If monitoring is enabled, register metrics in the default registry
|
228
|
+
# and store metric objects for future use.
|
229
|
+
if @monitoring_enabled
|
230
|
+
registry = Monitoring::MonitoringRegistryFactory.create @monitoring_type
|
231
|
+
@successful_requests_count = registry.counter(
|
232
|
+
:stackdriver_successful_requests_count,
|
233
|
+
'A number of successful requests to the Stackdriver Logging API')
|
234
|
+
@failed_requests_count = registry.counter(
|
235
|
+
:stackdriver_failed_requests_count,
|
236
|
+
'A number of failed requests to the Stackdriver Logging API,'\
|
237
|
+
' broken down by the error code')
|
238
|
+
@ingested_entries_count = registry.counter(
|
239
|
+
:stackdriver_ingested_entries_count,
|
240
|
+
'A number of log entries ingested by Stackdriver Logging')
|
241
|
+
@dropped_entries_count = registry.counter(
|
242
|
+
:stackdriver_dropped_entries_count,
|
243
|
+
'A number of log entries dropped by the Stackdriver output plugin')
|
244
|
+
end
|
245
|
+
|
182
246
|
# Alert on old authentication configuration.
|
183
247
|
unless @auth_method.nil? && @private_key_email.nil? &&
|
184
248
|
@private_key_path.nil? && @private_key_passphrase.nil?
|
@@ -198,6 +262,11 @@ module Fluent
|
|
198
262
|
@common_labels = {}
|
199
263
|
@common_labels.merge!(@labels) if @labels
|
200
264
|
|
265
|
+
# TODO: Construct Google::Api::MonitoredResource when @use_grpc is
|
266
|
+
# true after the protobuf map corruption issue is fixed.
|
267
|
+
@resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
268
|
+
labels: {})
|
269
|
+
|
201
270
|
@compiled_kubernetes_tag_regexp = nil
|
202
271
|
if @kubernetes_tag_regexp
|
203
272
|
@compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
|
@@ -211,6 +280,8 @@ module Fluent
|
|
211
280
|
(?:\[(?<execution_id>[^\]]+)\])?
|
212
281
|
[ ](?<text>.*)$/x
|
213
282
|
|
283
|
+
@http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
|
284
|
+
|
214
285
|
# set attributes from metadata (unless overriden by static config)
|
215
286
|
@vm_name = Socket.gethostname if @vm_name.nil?
|
216
287
|
@platform = detect_platform
|
@@ -235,7 +306,7 @@ module Fluent
|
|
235
306
|
@vm_id = metadata['instanceId']
|
236
307
|
end
|
237
308
|
if metadata.key?('accountId')
|
238
|
-
|
309
|
+
@resource.labels['aws_account'] = metadata['accountId']
|
239
310
|
end
|
240
311
|
when Platform::OTHER
|
241
312
|
# do nothing
|
@@ -268,12 +339,19 @@ module Fluent
|
|
268
339
|
# Functions.
|
269
340
|
@running_cloudfunctions = false
|
270
341
|
|
271
|
-
# Set labels, etc. based on the config
|
342
|
+
# Set up the MonitoredResource, labels, etc. based on the config.
|
272
343
|
case @platform
|
273
344
|
when Platform::GCE
|
274
|
-
@
|
345
|
+
@resource.type = COMPUTE_CONSTANTS[:resource_type]
|
346
|
+
# TODO: introduce a new MonitoredResource-centric configuration and
|
347
|
+
# deprecate subservice-name; for now, translate known uses.
|
275
348
|
if @subservice_name
|
276
|
-
|
349
|
+
# TODO: what should we do if we encounter an unknown value?
|
350
|
+
if @subservice_name == DATAFLOW_CONSTANTS[:service]
|
351
|
+
@resource.type = DATAFLOW_CONSTANTS[:resource_type]
|
352
|
+
elsif @subservice_name == ML_CONSTANTS[:service]
|
353
|
+
@resource.type = ML_CONSTANTS[:resource_type]
|
354
|
+
end
|
277
355
|
elsif @detect_subservice
|
278
356
|
# Check for specialized GCE environments.
|
279
357
|
# TODO: Add config options for these to allow for running outside GCE?
|
@@ -287,41 +365,66 @@ module Fluent
|
|
287
365
|
fetch_gce_metadata('instance/attributes/gae_backend_name')
|
288
366
|
@gae_backend_version =
|
289
367
|
fetch_gce_metadata('instance/attributes/gae_backend_version')
|
290
|
-
@
|
291
|
-
|
292
|
-
|
293
|
-
@gae_backend_version
|
368
|
+
@resource.type = APPENGINE_CONSTANTS[:resource_type]
|
369
|
+
@resource.labels['module_id'] = @gae_backend_name
|
370
|
+
@resource.labels['version_id'] = @gae_backend_version
|
294
371
|
elsif attributes.include?('kube-env')
|
295
372
|
# Kubernetes/Container Engine
|
296
|
-
@
|
297
|
-
common_labels["#{CONTAINER_SERVICE}/instance_id"] = @vm_id
|
373
|
+
@resource.type = CONTAINER_CONSTANTS[:resource_type]
|
298
374
|
@raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
|
299
375
|
@kube_env = YAML.load(@raw_kube_env)
|
300
|
-
|
376
|
+
@resource.labels['cluster_name'] =
|
301
377
|
cluster_name_from_kube_env(@kube_env)
|
302
378
|
detect_cloudfunctions(attributes)
|
379
|
+
elsif attributes.include?('dataproc-cluster-uuid') &&
|
380
|
+
attributes.include?('dataproc-cluster-name')
|
381
|
+
# Dataproc
|
382
|
+
@resource.type = DATAPROC_CONSTANTS[:resource_type]
|
383
|
+
@resource.labels['cluster_uuid'] =
|
384
|
+
fetch_gce_metadata('instance/attributes/dataproc-cluster-uuid')
|
385
|
+
@resource.labels['cluster_name'] =
|
386
|
+
fetch_gce_metadata('instance/attributes/dataproc-cluster-name')
|
387
|
+
@resource.labels['region'] =
|
388
|
+
fetch_gce_metadata('instance/attributes/dataproc-region')
|
303
389
|
end
|
304
390
|
end
|
305
|
-
|
306
|
-
|
307
|
-
|
391
|
+
# Some services have the GCE instance_id and zone as MonitoredResource
|
392
|
+
# labels; for other services we send them as entry labels.
|
393
|
+
if @resource.type == COMPUTE_CONSTANTS[:resource_type] ||
|
394
|
+
@resource.type == CONTAINER_CONSTANTS[:resource_type]
|
395
|
+
@resource.labels['instance_id'] = @vm_id
|
396
|
+
@resource.labels['zone'] = @zone
|
397
|
+
else
|
398
|
+
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
|
399
|
+
common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
|
400
|
+
end
|
401
|
+
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
308
402
|
when Platform::EC2
|
309
|
-
@
|
310
|
-
|
311
|
-
|
312
|
-
|
403
|
+
@resource.type = EC2_CONSTANTS[:resource_type]
|
404
|
+
@resource.labels['instance_id'] = @vm_id
|
405
|
+
@resource.labels['region'] = @zone
|
406
|
+
# the aws_account label is populated above.
|
407
|
+
common_labels["#{EC2_CONSTANTS[:service]}/resource_name"] = @vm_name
|
313
408
|
when Platform::OTHER
|
314
|
-
# Use
|
315
|
-
@
|
316
|
-
|
317
|
-
|
318
|
-
common_labels["#{
|
409
|
+
# Use GCE as the default environment.
|
410
|
+
@resource.type = COMPUTE_CONSTANTS[:resource_type]
|
411
|
+
@resource.labels['instance_id'] = @vm_id
|
412
|
+
@resource.labels['zone'] = @zone
|
413
|
+
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
319
414
|
end
|
415
|
+
@resource.labels.merge!(
|
416
|
+
extract_resource_labels(@resource.type, common_labels))
|
417
|
+
|
418
|
+
# The resource and labels are now set up; ensure they can't be modified
|
419
|
+
# without first duping them.
|
420
|
+
@resource.freeze
|
421
|
+
@resource.labels.freeze
|
422
|
+
@common_labels.freeze
|
320
423
|
|
321
424
|
# Log an informational message containing the Logs viewer URL
|
322
|
-
@log.info 'Logs viewer address: ',
|
323
|
-
|
324
|
-
|
425
|
+
@log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
|
426
|
+
"viewer?project=#{@project_id}&resource=#{@resource_type}/",
|
427
|
+
"instance_id/#{@vm_id}"
|
325
428
|
end
|
326
429
|
|
327
430
|
def start
|
@@ -353,6 +456,112 @@ module Fluent
|
|
353
456
|
tag
|
354
457
|
end
|
355
458
|
|
459
|
+
# Compute the monitored resource and common labels shared by a collection of
|
460
|
+
# entries.
|
461
|
+
def compute_group_resource_and_labels(tag)
|
462
|
+
# Note that we assume that labels added to group_common_labels below are
|
463
|
+
# not 'service' labels (i.e. we do not call extract_resource_labels
|
464
|
+
# again).
|
465
|
+
group_resource = @resource.dup
|
466
|
+
group_common_labels = @common_labels.dup
|
467
|
+
|
468
|
+
if @running_cloudfunctions
|
469
|
+
# If the current group of entries is coming from a Cloud Functions
|
470
|
+
# function, the function name can be extracted from the tag.
|
471
|
+
match_data = @cloudfunctions_tag_regexp.match(tag)
|
472
|
+
if match_data
|
473
|
+
# Resource type is set to Cloud Functions only for logs actually
|
474
|
+
# coming from a function, otherwise we leave it as Container.
|
475
|
+
group_resource.type = CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
476
|
+
group_resource.labels['region'] = @gcf_region
|
477
|
+
group_resource.labels['function_name'] =
|
478
|
+
decode_cloudfunctions_function_name(
|
479
|
+
match_data['encoded_function_name'])
|
480
|
+
# Move GKE container labels from the MonitoredResource to the
|
481
|
+
# LogEntry.
|
482
|
+
instance_id = group_resource.labels.delete('instance_id')
|
483
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/cluster_name"] =
|
484
|
+
group_resource.labels.delete('cluster_name')
|
485
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/instance_id"] =
|
486
|
+
instance_id
|
487
|
+
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
|
488
|
+
instance_id
|
489
|
+
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] =
|
490
|
+
group_resource.labels.delete('zone')
|
491
|
+
end
|
492
|
+
end
|
493
|
+
if group_resource.type == CONTAINER_CONSTANTS[:resource_type] &&
|
494
|
+
@compiled_kubernetes_tag_regexp
|
495
|
+
# Container logs in Kubernetes are tagged based on where they came
|
496
|
+
# from, so we can extract useful metadata from the tag.
|
497
|
+
# Do this here to avoid having to repeat it for each record.
|
498
|
+
match_data = @compiled_kubernetes_tag_regexp.match(tag)
|
499
|
+
if match_data
|
500
|
+
group_resource.labels['container_name'] = match_data['container_name']
|
501
|
+
group_resource.labels['namespace_id'] = match_data['namespace_name']
|
502
|
+
group_resource.labels['pod_id'] = match_data['pod_name']
|
503
|
+
%w(namespace_name pod_name).each do |field|
|
504
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/#{field}"] =
|
505
|
+
match_data[field]
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# Freeze the per-request state. Any further changes must be made on a
|
511
|
+
# per-entry basis.
|
512
|
+
group_resource.freeze
|
513
|
+
group_resource.labels.freeze
|
514
|
+
group_common_labels.freeze
|
515
|
+
|
516
|
+
[group_resource, group_common_labels]
|
517
|
+
end
|
518
|
+
|
519
|
+
# Extract entry resource and common labels that should be applied to
|
520
|
+
# individual entries from the group resource.
|
521
|
+
def extract_entry_labels(group_resource, record)
|
522
|
+
resource_labels = {}
|
523
|
+
common_labels = {}
|
524
|
+
|
525
|
+
if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
526
|
+
record.key?('log')
|
527
|
+
@cloudfunctions_log_match =
|
528
|
+
@cloudfunctions_log_regexp.match(record['log'])
|
529
|
+
end
|
530
|
+
|
531
|
+
if group_resource.type == CONTAINER_CONSTANTS[:resource_type]
|
532
|
+
# Move the stdout/stderr annotation from the record into a label
|
533
|
+
common_labels.merge!(
|
534
|
+
fields_to_labels(
|
535
|
+
record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
|
536
|
+
|
537
|
+
# If the record has been annotated by the kubernetes_metadata_filter
|
538
|
+
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
539
|
+
# populated at the grouped_entries level from the group's tag.
|
540
|
+
if record.key?('kubernetes')
|
541
|
+
extracted_resource_labels, extracted_common_labels = \
|
542
|
+
extract_container_metadata(record)
|
543
|
+
resource_labels.merge!(extracted_resource_labels)
|
544
|
+
common_labels.merge!(extracted_common_labels)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# If a field is present in the label_map, send its value as a label
|
549
|
+
# (mapping the field name to label name as specified in the config)
|
550
|
+
# and do not send that field as part of the payload.
|
551
|
+
common_labels.merge!(fields_to_labels(record, @label_map))
|
552
|
+
|
553
|
+
if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
554
|
+
@cloudfunctions_log_match &&
|
555
|
+
@cloudfunctions_log_match['execution_id']
|
556
|
+
common_labels['execution_id'] =
|
557
|
+
@cloudfunctions_log_match['execution_id']
|
558
|
+
end
|
559
|
+
resource_labels.merge!(
|
560
|
+
extract_resource_labels(group_resource.type, common_labels))
|
561
|
+
|
562
|
+
[resource_labels, common_labels]
|
563
|
+
end
|
564
|
+
|
356
565
|
def write(chunk)
|
357
566
|
# Group the entries since we have to make one call per tag.
|
358
567
|
grouped_entries = {}
|
@@ -369,74 +578,20 @@ module Fluent
|
|
369
578
|
|
370
579
|
grouped_entries.each do |tag, arr|
|
371
580
|
entries = []
|
372
|
-
|
373
|
-
|
374
|
-
if @running_cloudfunctions
|
375
|
-
# If the current group of entries is coming from a Cloud Functions
|
376
|
-
# function, the function name can be extracted from the tag.
|
377
|
-
match_data = @cloudfunctions_tag_regexp.match(tag)
|
378
|
-
if match_data
|
379
|
-
# Service name is set to Cloud Functions only for logs actually
|
380
|
-
# coming from a function.
|
381
|
-
@service_name = CLOUDFUNCTIONS_SERVICE
|
382
|
-
labels["#{CLOUDFUNCTIONS_SERVICE}/region"] = @gcf_region
|
383
|
-
labels["#{CLOUDFUNCTIONS_SERVICE}/function_name"] =
|
384
|
-
decode_cloudfunctions_function_name(
|
385
|
-
match_data['encoded_function_name'])
|
386
|
-
else
|
387
|
-
# Other logs are considered as coming from the Container Engine
|
388
|
-
# service.
|
389
|
-
@service_name = CONTAINER_SERVICE
|
390
|
-
end
|
391
|
-
end
|
392
|
-
if @service_name == CONTAINER_SERVICE && @compiled_kubernetes_tag_regexp
|
393
|
-
# Container logs in Kubernetes are tagged based on where they came
|
394
|
-
# from, so we can extract useful metadata from the tag.
|
395
|
-
# Do this here to avoid having to repeat it for each record.
|
396
|
-
match_data = @compiled_kubernetes_tag_regexp.match(tag)
|
397
|
-
if match_data
|
398
|
-
%w(namespace_name pod_name container_name).each do |field|
|
399
|
-
labels["#{CONTAINER_SERVICE}/#{field}"] = match_data[field]
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|
581
|
+
group_resource, group_common_labels = compute_group_resource_and_labels(
|
582
|
+
tag)
|
403
583
|
|
404
584
|
arr.each do |time, record|
|
405
585
|
next unless record.is_a?(Hash)
|
406
586
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
labels: {}
|
414
|
-
))
|
415
|
-
else
|
416
|
-
entry = Google::Apis::LoggingV1beta3::LogEntry.new(
|
417
|
-
metadata: Google::Apis::LoggingV1beta3::LogEntryMetadata.new(
|
418
|
-
service_name: @service_name,
|
419
|
-
project_id: @project_id,
|
420
|
-
zone: @zone,
|
421
|
-
labels: {}
|
422
|
-
))
|
423
|
-
end
|
424
|
-
|
425
|
-
if @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
|
426
|
-
@cloudfunctions_log_match =
|
427
|
-
@cloudfunctions_log_regexp.match(record['log'])
|
428
|
-
end
|
429
|
-
if @service_name == CONTAINER_SERVICE
|
430
|
-
# Move the stdout/stderr annotation from the record into a label.
|
431
|
-
field_to_label(record, 'stream', entry.metadata.labels,
|
432
|
-
"#{CONTAINER_SERVICE}/stream")
|
433
|
-
# If the record has been annotated by the kubernetes_metadata_filter
|
434
|
-
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
435
|
-
# populated at the grouped_entries level from the group's tag.
|
436
|
-
if record.key?('kubernetes')
|
437
|
-
handle_container_metadata(record, entry)
|
438
|
-
end
|
587
|
+
extracted_resource_labels, extracted_common_labels = \
|
588
|
+
extract_entry_labels(group_resource, record)
|
589
|
+
entry_resource = group_resource.dup
|
590
|
+
entry_resource.labels.merge!(extracted_resource_labels)
|
591
|
+
entry_common_labels = \
|
592
|
+
group_common_labels.merge(extracted_common_labels)
|
439
593
|
|
594
|
+
if entry_resource.type == CONTAINER_CONSTANTS[:resource_type]
|
440
595
|
# Save the timestamp if available, then clear it out to allow for
|
441
596
|
# determining whether we should parse the log or message field.
|
442
597
|
timestamp = record.key?('time') ? record['time'] : nil
|
@@ -460,57 +615,47 @@ module Fluent
|
|
460
615
|
end
|
461
616
|
end
|
462
617
|
|
463
|
-
ts_secs, ts_nanos = compute_timestamp(
|
618
|
+
ts_secs, ts_nanos = compute_timestamp(
|
619
|
+
entry_resource.type, record, time)
|
620
|
+
severity = compute_severity(
|
621
|
+
entry_resource.type, record, entry_common_labels)
|
622
|
+
|
464
623
|
if @use_grpc
|
624
|
+
entry = Google::Logging::V2::LogEntry.new(
|
625
|
+
labels: entry_common_labels,
|
626
|
+
resource: Google::Api::MonitoredResource.new(
|
627
|
+
type: entry_resource.type,
|
628
|
+
labels: entry_resource.labels.to_h
|
629
|
+
),
|
630
|
+
severity: grpc_severity(severity)
|
631
|
+
)
|
465
632
|
# If "seconds" is null or not an integer, we will omit the timestamp
|
466
633
|
# field and defer the decision on how to handle it to the downstream
|
467
634
|
# Logging API. If "nanos" is null or not an integer, it will be set
|
468
635
|
# to 0.
|
469
636
|
if ts_secs.is_a?(Integer)
|
470
637
|
ts_nanos = 0 unless ts_nanos.is_a?(Integer)
|
471
|
-
entry.
|
638
|
+
entry.timestamp = Google::Protobuf::Timestamp.new(
|
472
639
|
seconds: ts_secs,
|
473
640
|
nanos: ts_nanos
|
474
641
|
)
|
475
642
|
end
|
476
|
-
|
477
|
-
entry.metadata.severity =
|
478
|
-
grpc_severity(compute_severity(record, entry))
|
479
|
-
|
480
|
-
set_http_request_grpc(record, entry) # FIXME
|
481
|
-
else
|
482
|
-
entry.metadata.timestamp = {
|
483
|
-
seconds: ts_secs,
|
484
|
-
nanos: ts_nanos
|
485
|
-
}
|
486
|
-
|
487
|
-
entry.metadata.severity =
|
488
|
-
compute_severity(record, entry)
|
489
|
-
|
490
643
|
set_http_request(record, entry)
|
491
|
-
|
492
|
-
|
493
|
-
# If a field is present in the label_map, send its value as a label
|
494
|
-
# (mapping the field name to label name as specified in the config)
|
495
|
-
# and do not send that field as part of the payload.
|
496
|
-
if @label_map
|
497
|
-
@label_map.each do |field, label|
|
498
|
-
field_to_label(record, field, entry.metadata.labels, label)
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
if @service_name == CLOUDFUNCTIONS_SERVICE &&
|
503
|
-
@cloudfunctions_log_match &&
|
504
|
-
@cloudfunctions_log_match['execution_id']
|
505
|
-
entry.metadata.labels['execution_id'] =
|
506
|
-
@cloudfunctions_log_match['execution_id']
|
507
|
-
end
|
508
|
-
|
509
|
-
if @use_grpc
|
510
|
-
set_payload_grpc(record, entry, is_json)
|
644
|
+
set_payload_grpc(entry_resource.type, record, entry, is_json)
|
511
645
|
else
|
512
|
-
|
513
|
-
|
646
|
+
# Remove the labels if we didn't populate them with anything.
|
647
|
+
entry_resource.labels = nil if entry_resource.labels.empty?
|
648
|
+
entry = Google::Apis::LoggingV2beta1::LogEntry.new(
|
649
|
+
labels: entry_common_labels,
|
650
|
+
resource: entry_resource,
|
651
|
+
severity: severity,
|
652
|
+
timestamp: {
|
653
|
+
seconds: ts_secs,
|
654
|
+
nanos: ts_nanos
|
655
|
+
}
|
656
|
+
)
|
657
|
+
set_http_request(record, entry)
|
658
|
+
set_payload(entry_resource.type, record, entry, is_json)
|
514
659
|
end
|
515
660
|
|
516
661
|
entries.push(entry)
|
@@ -518,25 +663,30 @@ module Fluent
|
|
518
663
|
# Don't send an empty request if we rejected all the entries.
|
519
664
|
next if entries.empty?
|
520
665
|
|
521
|
-
log_name = log_name(
|
666
|
+
log_name = "projects/#{@project_id}/logs/#{log_name(
|
667
|
+
tag, group_resource)}"
|
522
668
|
|
669
|
+
# Does the actual write to the cloud logging api.
|
670
|
+
client = api_client
|
523
671
|
if @use_grpc
|
524
672
|
begin
|
525
|
-
|
526
|
-
|
527
|
-
client = api_client
|
528
|
-
|
529
|
-
labels_utf8_pairs = labels.map do |k, v|
|
673
|
+
labels_utf8_pairs = group_common_labels.map do |k, v|
|
530
674
|
[k.encode('utf-8'), convert_to_utf8(v)]
|
531
675
|
end
|
532
676
|
|
533
|
-
write_request = Google::Logging::
|
534
|
-
log_name:
|
535
|
-
|
677
|
+
write_request = Google::Logging::V2::WriteLogEntriesRequest.new(
|
678
|
+
log_name: log_name,
|
679
|
+
resource: Google::Api::MonitoredResource.new(
|
680
|
+
type: group_resource.type,
|
681
|
+
labels: group_resource.labels.to_h
|
682
|
+
),
|
683
|
+
labels: labels_utf8_pairs.to_h,
|
536
684
|
entries: entries
|
537
685
|
)
|
538
686
|
|
539
687
|
client.write_log_entries(write_request)
|
688
|
+
increment_successful_requests_count
|
689
|
+
increment_ingested_entries_count(entries.length)
|
540
690
|
|
541
691
|
# Let the user explicitly know when the first call succeeded,
|
542
692
|
# to aid with verification and troubleshooting.
|
@@ -546,10 +696,12 @@ module Fluent
|
|
546
696
|
end
|
547
697
|
|
548
698
|
rescue GRPC::Cancelled => error
|
699
|
+
increment_failed_requests_count(GRPC::Core::StatusCodes::CANCELLED)
|
549
700
|
# RPC cancelled, so retry via re-raising the error.
|
550
701
|
raise error
|
551
702
|
|
552
703
|
rescue GRPC::BadStatus => error
|
704
|
+
increment_failed_requests_count(error.code)
|
553
705
|
case error.code
|
554
706
|
when GRPC::Core::StatusCodes::CANCELLED,
|
555
707
|
GRPC::Core::StatusCodes::UNAVAILABLE,
|
@@ -564,6 +716,7 @@ module Fluent
|
|
564
716
|
# Most client errors indicate a problem with the request itself
|
565
717
|
# and should not be retried.
|
566
718
|
dropped = entries.length
|
719
|
+
increment_dropped_entries_count(dropped)
|
567
720
|
@log.warn "Dropping #{dropped} log message(s)",
|
568
721
|
error: error.to_s, error_code: error.code.to_s
|
569
722
|
when GRPC::Core::StatusCodes::UNAUTHENTICATED
|
@@ -571,12 +724,14 @@ module Fluent
|
|
571
724
|
# These are usually solved via a `gcloud auth` call, or by
|
572
725
|
# modifying the permissions on the Google Cloud project.
|
573
726
|
dropped = entries.length
|
727
|
+
increment_dropped_entries_count(dropped)
|
574
728
|
@log.warn "Dropping #{dropped} log message(s)",
|
575
729
|
error: error.to_s, error_code: error.code.to_s
|
576
730
|
else
|
577
731
|
# Assume this is a problem with the request itself
|
578
732
|
# and don't retry.
|
579
733
|
dropped = entries.length
|
734
|
+
increment_dropped_entries_count(dropped)
|
580
735
|
@log.error "Unknown response code #{error.code} from the "\
|
581
736
|
"server, dropping #{dropped} log message(s)",
|
582
737
|
error: error.to_s, error_code: error.code.to_s
|
@@ -584,21 +739,22 @@ module Fluent
|
|
584
739
|
end
|
585
740
|
else
|
586
741
|
begin
|
587
|
-
# Does the actual write to the cloud logging api.
|
588
|
-
|
589
|
-
client = api_client
|
590
|
-
|
591
|
-
# The URI of the write is constructed by the Google::Api request;
|
592
|
-
# it is equivalent to this URL:
|
593
|
-
# 'https://logging.googleapis.com/v1beta3/projects/' \
|
594
|
-
# "#{@project_id}/logs/#{log_name}/entries:write"
|
595
742
|
write_request = \
|
596
|
-
Google::Apis::
|
597
|
-
|
743
|
+
Google::Apis::LoggingV2beta1::WriteLogEntriesRequest.new(
|
744
|
+
log_name: log_name,
|
745
|
+
resource: group_resource,
|
746
|
+
labels: group_common_labels,
|
598
747
|
entries: entries)
|
599
748
|
|
600
749
|
# TODO: RequestOptions
|
601
|
-
|
750
|
+
begin
|
751
|
+
client.write_entry_log_entries(write_request)
|
752
|
+
rescue Google::Apis::Error => error
|
753
|
+
increment_failed_requests_count(error.status_code)
|
754
|
+
raise error
|
755
|
+
end
|
756
|
+
increment_successful_requests_count
|
757
|
+
increment_ingested_entries_count(entries.length)
|
602
758
|
|
603
759
|
# Let the user explicitly know when the first call succeeded,
|
604
760
|
# to aid with verification and troubleshooting.
|
@@ -616,6 +772,7 @@ module Fluent
|
|
616
772
|
# These are usually solved via a `gcloud auth` call, or by modifying
|
617
773
|
# the permissions on the Google Cloud project.
|
618
774
|
dropped = entries.length
|
775
|
+
increment_dropped_entries_count(dropped)
|
619
776
|
@log.warn "Dropping #{dropped} log message(s)",
|
620
777
|
error_class: error.class.to_s, error: error.to_s
|
621
778
|
|
@@ -623,6 +780,7 @@ module Fluent
|
|
623
780
|
# Most ClientErrors indicate a problem with the request itself and
|
624
781
|
# should not be retried.
|
625
782
|
dropped = entries.length
|
783
|
+
increment_dropped_entries_count(dropped)
|
626
784
|
@log.warn "Dropping #{dropped} log message(s)",
|
627
785
|
error_class: error.class.to_s, error: error.to_s
|
628
786
|
end
|
@@ -759,7 +917,7 @@ module Fluent
|
|
759
917
|
instance_prefix
|
760
918
|
end
|
761
919
|
|
762
|
-
def compute_timestamp(record, time)
|
920
|
+
def compute_timestamp(resource_type, record, time)
|
763
921
|
if record.key?('timestamp') &&
|
764
922
|
record['timestamp'].is_a?(Hash) &&
|
765
923
|
record['timestamp'].key?('seconds') &&
|
@@ -783,7 +941,7 @@ module Fluent
|
|
783
941
|
@log.warn 'timeNanos is deprecated - please use ' \
|
784
942
|
'timestampSeconds and timestampNanos instead.'
|
785
943
|
end
|
786
|
-
elsif
|
944
|
+
elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
787
945
|
@cloudfunctions_log_match
|
788
946
|
timestamp = DateTime.parse(@cloudfunctions_log_match['timestamp'])
|
789
947
|
ts_secs = timestamp.strftime('%s').to_i
|
@@ -805,8 +963,8 @@ module Fluent
|
|
805
963
|
[ts_secs, ts_nanos]
|
806
964
|
end
|
807
965
|
|
808
|
-
def compute_severity(record,
|
809
|
-
if
|
966
|
+
def compute_severity(resource_type, record, entry_common_labels)
|
967
|
+
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
810
968
|
if @cloudfunctions_log_match && @cloudfunctions_log_match['severity']
|
811
969
|
return parse_severity(@cloudfunctions_log_match['severity'])
|
812
970
|
elsif record.key?('stream') && record['stream'] == 'stdout'
|
@@ -820,9 +978,9 @@ module Fluent
|
|
820
978
|
end
|
821
979
|
elsif record.key?('severity')
|
822
980
|
return parse_severity(record.delete('severity'))
|
823
|
-
elsif
|
824
|
-
|
825
|
-
stream =
|
981
|
+
elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
|
982
|
+
entry_common_labels.key?("#{CONTAINER_CONSTANTS[:service]}/stream")
|
983
|
+
stream = entry_common_labels["#{CONTAINER_CONSTANTS[:service]}/stream"]
|
826
984
|
if stream == 'stdout'
|
827
985
|
return 'INFO'
|
828
986
|
elsif stream == 'stderr'
|
@@ -835,32 +993,19 @@ module Fluent
|
|
835
993
|
end
|
836
994
|
end
|
837
995
|
|
838
|
-
|
839
|
-
return nil unless record['httpRequest'].is_a?(Hash)
|
840
|
-
input = record['httpRequest']
|
841
|
-
output = Google::Apis::LoggingV1beta3::HttpRequest.new
|
842
|
-
output.request_method = input.delete('requestMethod')
|
843
|
-
output.request_url = input.delete('requestUrl')
|
844
|
-
output.request_size = input.delete('requestSize')
|
845
|
-
output.status = input.delete('status')
|
846
|
-
output.response_size = input.delete('responseSize')
|
847
|
-
output.user_agent = input.delete('userAgent')
|
848
|
-
output.remote_ip = input.delete('remoteIp')
|
849
|
-
output.referer = input.delete('referer')
|
850
|
-
output.cache_hit = input.delete('cacheHit')
|
851
|
-
output.validated_with_origin_server = \
|
852
|
-
input.delete('validatedWithOriginServer')
|
853
|
-
record.delete('httpRequest') if input.empty?
|
854
|
-
entry.http_request = output
|
855
|
-
end
|
996
|
+
NANOS_IN_A_SECOND = 1000 * 1000 * 1000
|
856
997
|
|
857
|
-
def
|
998
|
+
def set_http_request(record, entry)
|
858
999
|
return nil unless record['httpRequest'].is_a?(Hash)
|
859
1000
|
input = record['httpRequest']
|
860
|
-
|
1001
|
+
if @use_grpc
|
1002
|
+
output = Google::Logging::Type::HttpRequest.new
|
1003
|
+
else
|
1004
|
+
output = Google::Apis::LoggingV2beta1::HttpRequest.new
|
1005
|
+
end
|
861
1006
|
# We need to delete each field from 'httpRequest' even if its value is
|
862
|
-
# nil. However we do not want to assign this nil value to
|
863
|
-
#
|
1007
|
+
# nil. However we do not want to assign this nil value to the constructed
|
1008
|
+
# json or proto.
|
864
1009
|
request_method = input.delete('requestMethod')
|
865
1010
|
output.request_method = request_method unless request_method.nil?
|
866
1011
|
request_url = input.delete('requestUrl')
|
@@ -884,6 +1029,33 @@ module Fluent
|
|
884
1029
|
output.cache_validated_with_origin_server = \
|
885
1030
|
cache_validated_with_origin_server \
|
886
1031
|
unless cache_validated_with_origin_server.nil?
|
1032
|
+
|
1033
|
+
latency = input.delete('latency')
|
1034
|
+
unless latency.nil?
|
1035
|
+
# Parse latency. If no valid format is detected, skip setting latency.
|
1036
|
+
# Format: whitespace (optional) + integer + point & decimal (optional)
|
1037
|
+
# + whitespace (optional) + "s" + whitespace (optional)
|
1038
|
+
# e.g.: "1.42 s"
|
1039
|
+
match = @http_latency_regexp.match(latency)
|
1040
|
+
if match
|
1041
|
+
# Split the integer and decimal parts in order to calculate seconds
|
1042
|
+
# and nanos.
|
1043
|
+
latency_seconds = match['seconds'].to_i
|
1044
|
+
latency_nanos = (match['decimal'].to_f * NANOS_IN_A_SECOND).round
|
1045
|
+
if @use_grpc
|
1046
|
+
output.latency = Google::Protobuf::Duration.new(
|
1047
|
+
seconds: latency_seconds,
|
1048
|
+
nanos: latency_nanos
|
1049
|
+
)
|
1050
|
+
else
|
1051
|
+
output.latency = {
|
1052
|
+
seconds: latency_seconds,
|
1053
|
+
nanos: latency_nanos
|
1054
|
+
}.delete_if { |_, v| v == 0 }
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
end
|
1058
|
+
|
887
1059
|
record.delete('httpRequest') if input.empty?
|
888
1060
|
entry.http_request = output
|
889
1061
|
end
|
@@ -989,16 +1161,23 @@ module Fluent
|
|
989
1161
|
end
|
990
1162
|
|
991
1163
|
# Requires that record has a 'kubernetes' field.
|
992
|
-
def
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
1164
|
+
def extract_container_metadata(record)
|
1165
|
+
resource_labels = {}
|
1166
|
+
common_labels = {}
|
1167
|
+
%w(namespace_id pod_id container_name).each do |field|
|
1168
|
+
resource_labels.merge!(
|
1169
|
+
fields_to_labels(record['kubernetes'], field => field))
|
1170
|
+
end
|
1171
|
+
%w(namespace_name pod_name).each do |field|
|
1172
|
+
common_labels.merge!(
|
1173
|
+
fields_to_labels(
|
1174
|
+
record['kubernetes'],
|
1175
|
+
field => "#{CONTAINER_CONSTANTS[:service]}/#{field}"))
|
997
1176
|
end
|
998
1177
|
# Prepend label/ to all user-defined labels' keys.
|
999
1178
|
if record['kubernetes'].key?('labels')
|
1000
1179
|
record['kubernetes']['labels'].each do |key, value|
|
1001
|
-
|
1180
|
+
common_labels["label/#{key}"] = value
|
1002
1181
|
end
|
1003
1182
|
end
|
1004
1183
|
# We've explicitly consumed all the fields we care about -- don't litter
|
@@ -1006,33 +1185,43 @@ module Fluent
|
|
1006
1185
|
# filter plugin includes (or an empty 'kubernetes' field).
|
1007
1186
|
record.delete('kubernetes')
|
1008
1187
|
record.delete('docker')
|
1188
|
+
[resource_labels, common_labels]
|
1009
1189
|
end
|
1010
1190
|
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1191
|
+
# For every original_label => new_label pair in the label_map, delete the
|
1192
|
+
# original_label from the record if it exists, and extract the value to form
|
1193
|
+
# a map with the new_label as the key.
|
1194
|
+
def fields_to_labels(record, label_map)
|
1195
|
+
return {} if label_map.nil? || !label_map.is_a?(Hash)
|
1196
|
+
label_map.each_with_object({}) \
|
1197
|
+
do |(original_label, new_label), extracted_labels|
|
1198
|
+
extracted_labels[new_label] = convert_to_utf8(
|
1199
|
+
record.delete(original_label).to_s) if record.key?(original_label)
|
1200
|
+
end
|
1015
1201
|
end
|
1016
1202
|
|
1017
|
-
def set_payload(record, entry, is_json)
|
1203
|
+
def set_payload(resource_type, record, entry, is_json)
|
1018
1204
|
# If this is a Cloud Functions log that matched the expected regexp,
|
1019
1205
|
# use text payload. Otherwise, use JSON if we found valid JSON, or text
|
1020
1206
|
# payload in the following cases:
|
1021
1207
|
# 1. This is a Cloud Functions log and the 'log' key is available
|
1022
1208
|
# 2. This is an unstructured Container log and the 'log' key is available
|
1023
1209
|
# 3. The only remaining key is 'message'
|
1024
|
-
if
|
1210
|
+
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1211
|
+
@cloudfunctions_log_match
|
1025
1212
|
entry.text_payload = @cloudfunctions_log_match['text']
|
1026
|
-
elsif
|
1213
|
+
elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1214
|
+
record.key?('log')
|
1027
1215
|
entry.text_payload = record['log']
|
1028
1216
|
elsif is_json
|
1029
|
-
entry.
|
1030
|
-
elsif
|
1217
|
+
entry.json_payload = record
|
1218
|
+
elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
|
1219
|
+
record.key?('log')
|
1031
1220
|
entry.text_payload = record['log']
|
1032
1221
|
elsif record.size == 1 && record.key?('message')
|
1033
1222
|
entry.text_payload = record['message']
|
1034
1223
|
else
|
1035
|
-
entry.
|
1224
|
+
entry.json_payload = record
|
1036
1225
|
end
|
1037
1226
|
end
|
1038
1227
|
|
@@ -1080,56 +1269,82 @@ module Fluent
|
|
1080
1269
|
ret
|
1081
1270
|
end
|
1082
1271
|
|
1083
|
-
def set_payload_grpc(record, entry, is_json)
|
1272
|
+
def set_payload_grpc(resource_type, record, entry, is_json)
|
1084
1273
|
# If this is a Cloud Functions log that matched the expected regexp,
|
1085
1274
|
# use text payload. Otherwise, use JSON if we found valid JSON, or text
|
1086
1275
|
# payload in the following cases:
|
1087
1276
|
# 1. This is a Cloud Functions log and the 'log' key is available
|
1088
1277
|
# 2. This is an unstructured Container log and the 'log' key is available
|
1089
1278
|
# 3. The only remaining key is 'message'
|
1090
|
-
if
|
1279
|
+
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1280
|
+
@cloudfunctions_log_match
|
1091
1281
|
entry.text_payload = convert_to_utf8(
|
1092
1282
|
@cloudfunctions_log_match['text'])
|
1093
|
-
elsif
|
1283
|
+
elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1284
|
+
record.key?('log')
|
1094
1285
|
entry.text_payload = convert_to_utf8(record['log'])
|
1095
1286
|
elsif is_json
|
1096
|
-
entry.
|
1097
|
-
elsif
|
1287
|
+
entry.json_payload = struct_from_ruby(record)
|
1288
|
+
elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
|
1289
|
+
record.key?('log')
|
1098
1290
|
entry.text_payload = convert_to_utf8(record['log'])
|
1099
1291
|
elsif record.size == 1 && record.key?('message')
|
1100
1292
|
entry.text_payload = convert_to_utf8(record['message'])
|
1101
1293
|
else
|
1102
|
-
entry.
|
1294
|
+
entry.json_payload = struct_from_ruby(record)
|
1103
1295
|
end
|
1104
1296
|
end
|
1105
1297
|
|
1106
|
-
def log_name(tag,
|
1107
|
-
if
|
1298
|
+
def log_name(tag, resource)
|
1299
|
+
if resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
1108
1300
|
tag = 'cloud-functions'
|
1109
1301
|
elsif @running_on_managed_vm
|
1110
1302
|
# Add a prefix to Managed VM logs to prevent namespace collisions.
|
1111
|
-
tag = "#{
|
1112
|
-
elsif
|
1303
|
+
tag = "#{APPENGINE_CONSTANTS[:service]}/#{tag}"
|
1304
|
+
elsif resource.type == CONTAINER_CONSTANTS[:resource_type]
|
1113
1305
|
# For Kubernetes logs, use just the container name as the log name
|
1114
1306
|
# if we have it.
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
tag = sanitized_log_name unless sanitized_log_name.nil?
|
1307
|
+
if resource.labels && resource.labels.key?('container_name')
|
1308
|
+
sanitized_tag = sanitize_tag(resource.labels['container_name'])
|
1309
|
+
tag = sanitized_tag unless sanitized_tag.nil?
|
1119
1310
|
end
|
1120
1311
|
end
|
1121
|
-
|
1122
|
-
# lib already handles encoding.
|
1123
|
-
tag = ERB::Util.url_encode(tag) if @use_grpc
|
1312
|
+
tag = ERB::Util.url_encode(tag)
|
1124
1313
|
tag
|
1125
1314
|
end
|
1126
1315
|
|
1316
|
+
# Some services set labels (via configuring 'labels' or 'label_map') which
|
1317
|
+
# are now MonitoredResource labels in v2.
|
1318
|
+
# For these services, remove resource labels from 'labels' and return a
|
1319
|
+
# Hash of labels to be merged into the MonitoredResource labels.
|
1320
|
+
# Otherwise, return an empty hash and leave 'labels' unmodified.
|
1321
|
+
def extract_resource_labels(resource_type, labels)
|
1322
|
+
extracted_labels = {}
|
1323
|
+
return extracted_labels if labels.nil? || !labels.is_a?(Hash)
|
1324
|
+
|
1325
|
+
if resource_type == DATAFLOW_CONSTANTS[:resource_type]
|
1326
|
+
label_prefix = DATAFLOW_CONSTANTS[:service]
|
1327
|
+
labels_to_extract = %w(region job_name job_id step_id)
|
1328
|
+
elsif resource_type == ML_CONSTANTS[:resource_type]
|
1329
|
+
label_prefix = ML_CONSTANTS[:service]
|
1330
|
+
labels_to_extract = %w(job_id task_name)
|
1331
|
+
else
|
1332
|
+
return extracted_labels
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
labels_to_extract.each do |label|
|
1336
|
+
extracted_labels[label] = labels.delete("#{label_prefix}/#{label}") if
|
1337
|
+
labels.key?("#{label_prefix}/#{label}")
|
1338
|
+
end
|
1339
|
+
extracted_labels
|
1340
|
+
end
|
1341
|
+
|
1127
1342
|
def init_api_client
|
1128
1343
|
return if @use_grpc
|
1129
1344
|
# TODO: Use a non-default ClientOptions object.
|
1130
1345
|
Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
|
1131
1346
|
Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
|
1132
|
-
@client = Google::Apis::
|
1347
|
+
@client = Google::Apis::LoggingV2beta1::LoggingService.new
|
1133
1348
|
@client.authorization = Google::Auth.get_application_default(
|
1134
1349
|
LOGGING_SCOPE)
|
1135
1350
|
end
|
@@ -1140,7 +1355,7 @@ module Fluent
|
|
1140
1355
|
authentication = Google::Auth.get_application_default
|
1141
1356
|
creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
|
1142
1357
|
creds = ssl_creds.compose(creds)
|
1143
|
-
@client = Google::Logging::
|
1358
|
+
@client = Google::Logging::V2::LoggingServiceV2::Stub.new(
|
1144
1359
|
'logging.googleapis.com', creds)
|
1145
1360
|
else
|
1146
1361
|
unless @client.authorization.expired?
|
@@ -1180,5 +1395,47 @@ module Fluent
|
|
1180
1395
|
end
|
1181
1396
|
end
|
1182
1397
|
end
|
1398
|
+
|
1399
|
+
# Increment the metric for the number of successful requests.
|
1400
|
+
def increment_successful_requests_count
|
1401
|
+
return unless @successful_requests_count
|
1402
|
+
@successful_requests_count.increment(grpc: @use_grpc)
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
# Increment the metric for the number of failed requests, labeled by
|
1406
|
+
# the provided status code.
|
1407
|
+
def increment_failed_requests_count(code)
|
1408
|
+
return unless @failed_requests_count
|
1409
|
+
@failed_requests_count.increment(grpc: @use_grpc, code: code)
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# Increment the metric for the number of log entries, successfully
|
1413
|
+
# ingested by the Stackdriver Logging API.
|
1414
|
+
def increment_ingested_entries_count(count)
|
1415
|
+
return unless @ingested_entries_count
|
1416
|
+
@ingested_entries_count.increment({}, count)
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
# Increment the metric for the number of log entries that were dropped
|
1420
|
+
# and not ingested by the Stackdriver Logging API.
|
1421
|
+
def increment_dropped_entries_count(count)
|
1422
|
+
return unless @dropped_entries_count
|
1423
|
+
@dropped_entries_count.increment({}, count)
|
1424
|
+
end
|
1425
|
+
end
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
module Google
|
1429
|
+
module Apis
|
1430
|
+
module LoggingV2beta1
|
1431
|
+
# Override MonitoredResource::dup to make a deep copy.
|
1432
|
+
class MonitoredResource
|
1433
|
+
def dup
|
1434
|
+
ret = super
|
1435
|
+
ret.labels = labels.dup
|
1436
|
+
ret
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
end
|
1183
1440
|
end
|
1184
1441
|
end
|