fluent-plugin-google-cloud 0.6.7.pre.1 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4356aeba5dd3cdab64c30787a1dbc798f34f5182
4
- data.tar.gz: 81ffb3cb3017fa43d42e3a45fce18cf8ddada913
3
+ metadata.gz: f19f3c278abefebea1b068850a3e7d09a31835e2
4
+ data.tar.gz: e4588beb45b2dba23d3901a6a527091134c1f5a6
5
5
  SHA512:
6
- metadata.gz: faf46b39db9539a778d3c5144d8382e899e009ef17029334817959d316c979ab1419b91be0dcc61d146e94baf41e43b7a9e606699ac6bea1e595ade86dcd9595
7
- data.tar.gz: 10f148519e9ff916b914e3e24020331d702f93878f54314476f907a63c0db081aee4ceda2f3b40d190a48e5b74cc58d314b62fd4d2901110613ae604850b1a1a
6
+ metadata.gz: 86369bce68f370907f35d589830ea73330032590ae43bc75b22bc7ff99157997740fdfeac46079c96fd832b90ca7bc36da9e66f8187e4f1c566277e028b38b0f
7
+ data.tar.gz: 9c8189c85d4980c160403b09bd2ccb58c3bcd264f36062e994ea7bf63aa3fc70aa79cf357f54a83ff255ae9ceb7d9bee9243866f2a7d00af6b6b2c147001e549
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.6.7.pre.1)
4
+ fluent-plugin-google-cloud (0.6.7)
5
5
  fluentd (~> 0.10)
6
6
  google-api-client (~> 0.9.0)
7
7
  google-cloud-logging (= 0.24.1)
data/Rakefile CHANGED
@@ -22,6 +22,7 @@ desc 'Fix file permissions'
22
22
  task :fix_perms do
23
23
  files = [
24
24
  'lib/fluent/plugin/out_google_cloud.rb',
25
+ 'lib/fluent/plugin/monitoring.rb',
25
26
  'lib/google/**/*.rb'
26
27
  ].flat_map do |file|
27
28
  file.include?('*') ? Dir.glob(file) : [file]
@@ -37,6 +38,6 @@ task :fix_perms do
37
38
  end
38
39
 
39
40
  desc 'Run unit tests and RuboCop to check for style violations'
40
- task all: [:test, :rubocop, :fix_perms]
41
+ task all: [:rubocop, :test, :fix_perms]
41
42
 
42
43
  task default: :all
@@ -10,7 +10,7 @@ eos
10
10
  gem.homepage = \
11
11
  'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
12
12
  gem.license = 'Apache-2.0'
13
- gem.version = '0.6.7.pre.1'
13
+ gem.version = '0.6.7'
14
14
  gem.authors = ['Todd Derr', 'Alex Robinson']
15
15
  gem.email = ['salty@google.com']
16
16
  gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
@@ -38,12 +38,12 @@ end
38
38
  module Fluent
39
39
  # fluentd output plugin for the Stackdriver Logging API
40
40
  class GoogleCloudOutput < BufferedOutput
41
- # Constants for service names, resource types and etc.
42
- module ServiceConstants
41
+ # Constants for service names and resource types.
42
+ module Constants
43
43
  APPENGINE_CONSTANTS = {
44
44
  service: 'appengine.googleapis.com',
45
45
  resource_type: 'gae_app',
46
- metadata_attributes: %w(gae_backend_name gae_backend_version)
46
+ metadata_attributes: %w(gae_backend_name gae_backend_version).to_set
47
47
  }
48
48
  CLOUDFUNCTIONS_CONSTANTS = {
49
49
  service: 'cloudfunctions.googleapis.com',
@@ -53,16 +53,12 @@ module Fluent
53
53
  service: 'compute.googleapis.com',
54
54
  resource_type: 'gce_instance'
55
55
  }
56
- GKE_CONSTANTS = {
56
+ CONTAINER_CONSTANTS = {
57
57
  service: 'container.googleapis.com',
58
58
  resource_type: 'container',
59
59
  extra_resource_labels: %w(namespace_id pod_id container_name),
60
60
  extra_common_labels: %w(namespace_name pod_name),
61
- metadata_attributes: %w(kube-env)
62
- }
63
- DOCKER_CONSTANTS = {
64
- service: 'docker.googleapis.com',
65
- resource_type: 'docker_container'
61
+ metadata_attributes: %w(kube-env).to_set
66
62
  }
67
63
  DATAFLOW_CONSTANTS = {
68
64
  service: 'dataflow.googleapis.com',
@@ -72,7 +68,8 @@ module Fluent
72
68
  DATAPROC_CONSTANTS = {
73
69
  service: 'cluster.dataproc.googleapis.com',
74
70
  resource_type: 'cloud_dataproc_cluster',
75
- metadata_attributes: %w(dataproc-cluster-uuid dataproc-cluster-name)
71
+ metadata_attributes:
72
+ %w(dataproc-cluster-uuid dataproc-cluster-name).to_set
76
73
  }
77
74
  EC2_CONSTANTS = {
78
75
  service: 'ec2.amazonaws.com',
@@ -86,7 +83,7 @@ module Fluent
86
83
 
87
84
  # The map between a subservice name and a resource type.
88
85
  SUBSERVICE_MAP = \
89
- [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAFLOW_CONSTANTS,
86
+ [APPENGINE_CONSTANTS, CONTAINER_CONSTANTS, DATAFLOW_CONSTANTS,
90
87
  DATAPROC_CONSTANTS, ML_CONSTANTS]
91
88
  .map { |consts| [consts[:service], consts[:resource_type]] }.to_h
92
89
  # Default back to GCE if invalid value is detected.
@@ -94,35 +91,18 @@ module Fluent
94
91
 
95
92
  # The map between a resource type and expected subservice attributes.
96
93
  SUBSERVICE_METADATA_ATTRIBUTES = \
97
- [APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAPROC_CONSTANTS]
98
- .map do |consts|
99
- [consts[:resource_type], consts[:metadata_attributes].to_set]
100
- end.to_h
101
- end
94
+ [APPENGINE_CONSTANTS, CONTAINER_CONSTANTS, DATAPROC_CONSTANTS]
95
+ .map { |consts| [consts[:resource_type], consts[:metadata_attributes]] }
96
+ .to_h
102
97
 
103
- # Constants for configuration.
104
- module ConfigConstants
105
- # Default values for JSON payload keys to set the "httpRequest",
106
- # "operation", "sourceLocation", "trace" fields in the LogEntry.
98
+ # Default values for JSON payload keys to set the "trace",
99
+ # "sourceLocation", "operation" and "labels" fields in the LogEntry.
100
+ DEFAULT_PAYLOAD_KEY_PREFIX = 'logging.googleapis.com'
107
101
  DEFAULT_HTTP_REQUEST_KEY = 'httpRequest'
108
- DEFAULT_OPERATION_KEY = 'logging.googleapis.com/operation'
109
- DEFAULT_SOURCE_LOCATION_KEY = 'logging.googleapis.com/sourceLocation'
110
- DEFAULT_TRACE_KEY = 'logging.googleapis.com/trace'
111
-
112
- DEFAULT_METADATA_AGENT_URL =
113
- 'http://local-metadata-agent.stackdriver.com:8000'
114
- end
115
-
116
- # Constants for log entry field extraction.
117
- module InternalConstants
118
- # Use empty string as request path when the local_resource_id of monitored
119
- # resource can be implicitly inferred by Metadata Agent.
120
- IMPLICIT_LOCAL_RESOURCE_ID = ''
121
-
122
- # The label name of local_resource_id in the json payload. When a record
123
- # has this field in the payload, we will use the value to retrieve
124
- # monitored resource from Stackdriver Metadata agent.
125
- LOCAL_RESOURCE_ID_KEY = 'logging.googleapis.com/local_resource_id'
102
+ DEFAULT_OPERATION_KEY = "#{DEFAULT_PAYLOAD_KEY_PREFIX}/operation"
103
+ DEFAULT_SOURCE_LOCATION_KEY =
104
+ "#{DEFAULT_PAYLOAD_KEY_PREFIX}/sourceLocation"
105
+ DEFAULT_TRACE_KEY = "#{DEFAULT_PAYLOAD_KEY_PREFIX}/trace"
126
106
 
127
107
  # Map from each field name under LogEntry to corresponding variables
128
108
  # required to perform field value extraction from the log record.
@@ -175,14 +155,12 @@ module Fluent
175
155
  }
176
156
  end
177
157
 
178
- include self::ServiceConstants
179
- include self::ConfigConstants
180
- include self::InternalConstants
158
+ include self::Constants
181
159
 
182
160
  Fluent::Plugin.register_output('google_cloud', self)
183
161
 
184
162
  PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
185
- PLUGIN_VERSION = '0.6.7.pre.1'
163
+ PLUGIN_VERSION = '0.6.7'
186
164
 
187
165
  # Name of the the Google cloud logging write scope.
188
166
  LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
@@ -317,11 +295,6 @@ module Fluent
317
295
  config_param :monitoring_type, :string,
318
296
  :default => Monitoring::PrometheusMonitoringRegistry.name
319
297
 
320
- # Whether to call metadata agent to retrieve monitored resource.
321
- config_param :enable_metadata_agent, :bool, :default => false
322
- config_param :metadata_agent_url, :string,
323
- :default => DEFAULT_METADATA_AGENT_URL
324
-
325
298
  # rubocop:enable Style/HashSyntax
326
299
 
327
300
  # TODO: Add a log_name config option rather than just using the tag?
@@ -381,30 +354,28 @@ module Fluent
381
354
 
382
355
  @platform = detect_platform
383
356
 
384
- # Set agent-level monitored resource. This monitored resource is initiated
385
- # as the logging agent starts up. It will be inherited by all log entries
386
- # processed by this agent. First try to retrieve it via Metadata Agent.
387
- if @enable_metadata_agent
388
- # The local_resource_id for this should be the instance id. Since this
389
- # can be implicitly inferred by Metadata Agent, we do not need to
390
- # explicitly send the key.
391
- @resource = call_metadata_agent_for_monitored_resource(
392
- IMPLICIT_LOCAL_RESOURCE_ID)
393
- end
394
-
395
- # Set required variables: @project_id, @vm_id, @vm_name and @zone.
357
+ # Set required variables: @project_id, @vm_id, @vm_name and @zone by
358
+ # making some requests to metadata server.
359
+ #
360
+ # Note: Once we support metadata injection at Logging API side, we might
361
+ # no longer need to require all these metadata in logging agent. But for
362
+ # now, they are still required.
363
+ #
364
+ # TODO(qingling128): After Metadata Agent support is added, try extracting
365
+ # these info from responses from Metadata Agent first.
396
366
  set_required_metadata_variables
397
367
 
398
368
  # Retrieve monitored resource.
399
- # Fail over to retrieve monitored resource via the legacy path if we fail
400
- # to get it from Metadata Agent.
401
- @resource ||= determine_agent_level_monitored_resource_via_legacy
369
+ #
370
+ # TODO(qingling128): After Metadata Agent support is added, try retrieving
371
+ # the monitored resource from Metadata Agent first.
372
+ @resource = determine_agent_level_monitored_resource_via_legacy
402
373
 
403
374
  # Set regexp that we should match tags against later on. Using a list
404
375
  # instead of a map to ensure order. For example, tags will be matched
405
376
  # against Cloud Functions first, then GKE.
406
377
  @tag_regexp_list = []
407
- if @resource.type == GKE_CONSTANTS[:resource_type]
378
+ if @resource.type == CONTAINER_CONSTANTS[:resource_type]
408
379
  # We only support Cloud Functions logs for GKE right now.
409
380
  if fetch_gce_metadata('instance/attributes/'
410
381
  ).split.include?('gcf_region')
@@ -417,7 +388,7 @@ module Fluent
417
388
  ]
418
389
  end
419
390
  @tag_regexp_list << [
420
- GKE_CONSTANTS[:resource_type], @compiled_kubernetes_tag_regexp
391
+ CONTAINER_CONSTANTS[:resource_type], @compiled_kubernetes_tag_regexp
421
392
  ]
422
393
  end
423
394
 
@@ -433,7 +404,7 @@ module Fluent
433
404
 
434
405
  # Log an informational message containing the Logs viewer URL
435
406
  @log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
436
- "viewer?project=#{@project_id}&resource=#{@resource.type}/",
407
+ "viewer?project=#{@project_id}&resource=#{@resource_type}/",
437
408
  "instance_id/#{@vm_id}"
438
409
  end
439
410
 
@@ -449,27 +420,39 @@ module Fluent
449
420
  end
450
421
 
451
422
  def write(chunk)
452
- grouped_entries = group_log_entries_by_tag_and_local_resource_id(chunk)
423
+ # Group the entries since we have to make one call per tag.
424
+ grouped_entries = {}
425
+ chunk.msgpack_each do |tag, *arr|
426
+ sanitized_tag = sanitize_tag(tag)
427
+ if sanitized_tag.nil?
428
+ @log.warn "Dropping log entries with invalid tag: '#{tag}'. " \
429
+ 'A tag should be a string with utf8 characters.'
430
+ next
431
+ end
432
+ grouped_entries[sanitized_tag] ||= []
433
+ grouped_entries[sanitized_tag].push(arr)
434
+ end
453
435
 
454
- grouped_entries.each do |(tag, local_resource_id), arr|
436
+ grouped_entries.each do |tag, arr|
455
437
  entries = []
456
- group_level_resource, group_level_common_labels =
457
- determine_group_level_monitored_resource_and_labels(
458
- tag, local_resource_id)
438
+ group_resource, group_common_labels =
439
+ determine_group_level_monitored_resource_and_labels(tag)
459
440
 
460
441
  arr.each do |time, record|
461
- entry_level_resource, entry_level_common_labels =
462
- determine_entry_level_monitored_resource_and_labels(
463
- group_level_resource, group_level_common_labels, record)
442
+ next unless record.is_a?(Hash)
443
+
444
+ extracted_resource_labels, extracted_common_labels = \
445
+ determine_entry_level_labels(group_resource, record)
446
+ entry_resource = group_resource.dup
447
+ entry_resource.labels.merge!(extracted_resource_labels)
448
+ entry_common_labels = \
449
+ group_common_labels.merge(extracted_common_labels)
464
450
 
465
451
  is_json = false
466
452
  if @detect_json
467
- # Save the timestamp and severity if available, then clear it out to
468
- # allow for determining whether we should parse the log or message
469
- # field.
453
+ # Save the timestamp if available, then clear it out to allow for
454
+ # determining whether we should parse the log or message field.
470
455
  timestamp = record.delete('time')
471
- severity = record.delete('severity')
472
-
473
456
  # If the log is json, we want to export it as a structured log
474
457
  # unless there is additional metadata that would be lost.
475
458
  record_json = nil
@@ -484,17 +467,16 @@ module Fluent
484
467
  record = record_json
485
468
  is_json = true
486
469
  end
487
- # Restore timestamp and severity if necessary. Note that the nested
488
- # json might also has 'time' and 'severity' fields. If that is the
489
- # case, we do not want to override the value.
490
- record['time'] ||= timestamp if timestamp
491
- record['severity'] ||= severity if severity
470
+ # Restore timestamp if necessary.
471
+ unless record.key?('time') || timestamp.nil?
472
+ record['time'] = timestamp
473
+ end
492
474
  end
493
475
 
494
476
  ts_secs, ts_nanos = compute_timestamp(
495
- entry_level_resource.type, record, time)
477
+ entry_resource.type, record, time)
496
478
  severity = compute_severity(
497
- entry_level_resource.type, record, entry_level_common_labels)
479
+ entry_resource.type, record, entry_common_labels)
498
480
 
499
481
  ts_secs = begin
500
482
  Integer ts_secs
@@ -506,13 +488,12 @@ module Fluent
506
488
  rescue ArgumentError, TypeError
507
489
  ts_nanos
508
490
  end
509
-
510
491
  if @use_grpc
511
492
  entry = Google::Logging::V2::LogEntry.new(
512
- labels: entry_level_common_labels,
493
+ labels: entry_common_labels,
513
494
  resource: Google::Api::MonitoredResource.new(
514
- type: entry_level_resource.type,
515
- labels: entry_level_resource.labels.to_h
495
+ type: entry_resource.type,
496
+ labels: entry_resource.labels.to_h
516
497
  ),
517
498
  severity: grpc_severity(severity)
518
499
  )
@@ -529,11 +510,10 @@ module Fluent
529
510
  end
530
511
  else
531
512
  # Remove the labels if we didn't populate them with anything.
532
- entry_level_resource.labels = nil if
533
- entry_level_resource.labels.empty?
513
+ entry_resource.labels = nil if entry_resource.labels.empty?
534
514
  entry = Google::Apis::LoggingV2beta1::LogEntry.new(
535
- labels: entry_level_common_labels,
536
- resource: entry_level_resource,
515
+ labels: entry_common_labels,
516
+ resource: entry_resource,
537
517
  severity: severity,
538
518
  timestamp: {
539
519
  seconds: ts_secs,
@@ -542,12 +522,13 @@ module Fluent
542
522
  )
543
523
  end
544
524
 
545
- # Get fully-qualified trace id for LogEntry "trace" field.
525
+ # Get fully-qualified trace id for LogEntry "trace" field per config.
546
526
  fq_trace_id = record.delete(@trace_key)
547
527
  entry.trace = fq_trace_id if fq_trace_id
548
528
 
549
529
  set_log_entry_fields(record, entry)
550
- set_payload(entry_level_resource.type, record, entry, is_json)
530
+
531
+ set_payload(entry_resource.type, record, entry, is_json)
551
532
 
552
533
  entries.push(entry)
553
534
  end
@@ -555,21 +536,21 @@ module Fluent
555
536
  next if entries.empty?
556
537
 
557
538
  log_name = "projects/#{@project_id}/logs/#{log_name(
558
- tag, group_level_resource)}"
539
+ tag, group_resource)}"
559
540
 
560
541
  # Does the actual write to the cloud logging api.
561
542
  client = api_client
562
543
  if @use_grpc
563
544
  begin
564
- labels_utf8_pairs = group_level_common_labels.map do |k, v|
545
+ labels_utf8_pairs = group_common_labels.map do |k, v|
565
546
  [k.encode('utf-8'), convert_to_utf8(v)]
566
547
  end
567
548
 
568
549
  write_request = Google::Logging::V2::WriteLogEntriesRequest.new(
569
550
  log_name: log_name,
570
551
  resource: Google::Api::MonitoredResource.new(
571
- type: group_level_resource.type,
572
- labels: group_level_resource.labels.to_h
552
+ type: group_resource.type,
553
+ labels: group_resource.labels.to_h
573
554
  ),
574
555
  labels: labels_utf8_pairs.to_h,
575
556
  entries: entries
@@ -579,8 +560,8 @@ module Fluent
579
560
  increment_successful_requests_count
580
561
  increment_ingested_entries_count(entries.length)
581
562
 
582
- # Let the user explicitly know when the first call succeeded, to aid
583
- # with verification and troubleshooting.
563
+ # Let the user explicitly know when the first call succeeded,
564
+ # to aid with verification and troubleshooting.
584
565
  unless @successful_call
585
566
  @successful_call = true
586
567
  @log.info 'Successfully sent gRPC to Stackdriver Logging API.'
@@ -619,8 +600,8 @@ module Fluent
619
600
  @log.warn "Dropping #{dropped} log message(s)",
620
601
  error: error.to_s, error_code: error.code.to_s
621
602
  else
622
- # Assume this is a problem with the request itself and don't
623
- # retry.
603
+ # Assume this is a problem with the request itself
604
+ # and don't retry.
624
605
  dropped = entries.length
625
606
  increment_dropped_entries_count(dropped)
626
607
  @log.error "Unknown response code #{error.code} from the "\
@@ -633,8 +614,8 @@ module Fluent
633
614
  write_request = \
634
615
  Google::Apis::LoggingV2beta1::WriteLogEntriesRequest.new(
635
616
  log_name: log_name,
636
- resource: group_level_resource,
637
- labels: group_level_common_labels,
617
+ resource: group_resource,
618
+ labels: group_common_labels,
638
619
  entries: entries)
639
620
 
640
621
  # TODO: RequestOptions
@@ -647,8 +628,8 @@ module Fluent
647
628
  increment_successful_requests_count
648
629
  increment_ingested_entries_count(entries.length)
649
630
 
650
- # Let the user explicitly know when the first call succeeded, to aid
651
- # with verification and troubleshooting.
631
+ # Let the user explicitly know when the first call succeeded,
632
+ # to aid with verification and troubleshooting.
652
633
  unless @successful_call
653
634
  @successful_call = true
654
635
  @log.info 'Successfully sent to Stackdriver Logging API.'
@@ -839,9 +820,8 @@ module Fluent
839
820
 
840
821
  # Retrieve monitored resource via the legacy way.
841
822
  #
842
- # Note: This is just a failover plan if we fail to get metadata from
843
- # Metadata Agent. Thus it should be equivalent to what Metadata Agent
844
- # returns.
823
+ # TODO(qingling128): Use this as only a fallback plan after Metadata Agent
824
+ # support is added.
845
825
  def determine_agent_level_monitored_resource_via_legacy
846
826
  resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
847
827
  labels: {})
@@ -903,7 +883,7 @@ module Fluent
903
883
  }
904
884
 
905
885
  # GKE container.
906
- when GKE_CONSTANTS[:resource_type]
886
+ when CONTAINER_CONSTANTS[:resource_type]
907
887
  raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
908
888
  kube_env = YAML.load(raw_kube_env)
909
889
  return {
@@ -938,7 +918,7 @@ module Fluent
938
918
  rescue StandardError => e
939
919
  @log.error "Failed to set monitored resource labels for #{type}: ",
940
920
  error: e
941
- {}
921
+ return {}
942
922
  end
943
923
 
944
924
  # Determine the common labels that should be added to all log entries
@@ -962,7 +942,7 @@ module Fluent
962
942
 
963
943
  # GCE instance and GKE container.
964
944
  when COMPUTE_CONSTANTS[:resource_type],
965
- GKE_CONSTANTS[:resource_type]
945
+ CONTAINER_CONSTANTS[:resource_type]
966
946
  labels.merge!(
967
947
  "#{COMPUTE_CONSTANTS[:service]}/resource_name" => @vm_name)
968
948
 
@@ -974,175 +954,138 @@ module Fluent
974
954
  labels
975
955
  end
976
956
 
977
- # Group the log entries by tag and local_resource_id pairs.
978
- def group_log_entries_by_tag_and_local_resource_id(chunk)
979
- groups = {}
980
- chunk.msgpack_each do |tag, time, record|
981
- unless record.is_a?(Hash)
982
- @log.warn 'Dropping log entries with malformed record: ' \
983
- "'#{record.inspect}'. " \
984
- 'A log record should be in JSON format.'
985
- next
986
- end
987
- sanitized_tag = sanitize_tag(tag)
988
- if sanitized_tag.nil?
989
- @log.warn "Dropping log entries with invalid tag: '#{tag.inspect}'." \
990
- ' A tag should be a string with utf8 characters.'
991
- next
992
- end
993
- local_resource_id = record.delete(LOCAL_RESOURCE_ID_KEY)
994
- # A nil local_resource_id means "fall back to legacy".
995
- hash_key = [sanitized_tag, local_resource_id].freeze
996
- groups[hash_key] ||= []
997
- groups[hash_key].push([time, record])
998
- end
999
- groups
1000
- end
1001
-
1002
957
  # Determine the group level monitored resource and common labels shared by a
1003
958
  # collection of entries.
1004
- def determine_group_level_monitored_resource_and_labels(tag,
1005
- local_resource_id)
1006
- resource = @resource.dup
1007
- resource.labels = @resource.labels.dup
1008
- common_labels = @common_labels.dup
1009
-
1010
- # Change the resource type and set matched_regexp_group if the tag matches
1011
- # certain regexp.
1012
- matched_regexp_group = nil # @tag_regexp_list can be an empty list.
959
+ def determine_group_level_monitored_resource_and_labels(tag)
960
+ # Determine group level monitored resource type. For certain types,
961
+ # extract useful info from the tag and store those in
962
+ # matched_regex_group.
963
+ group_resource_type, matched_regex_group =
964
+ determine_group_level_monitored_resource_type(tag)
965
+
966
+ # Determine group level monitored resource labels and common labels.
967
+ group_resource_labels, group_common_labels =
968
+ determine_group_level_labels(group_resource_type, matched_regex_group)
969
+
970
+ group_resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
971
+ type: group_resource_type,
972
+ labels: group_resource_labels.to_h
973
+ )
974
+
975
+ # Freeze the per-request state. Any further changes must be made on a
976
+ # per-entry basis.
977
+ group_resource.freeze
978
+ group_resource.labels.freeze
979
+ group_common_labels.freeze
980
+
981
+ [group_resource, group_common_labels]
982
+ end
983
+
984
+ # Determine group level monitored resource type shared by a collection of
985
+ # entries.
986
+ # Return the resource type and tag regexp matched groups. The matched groups
987
+ # only apply to some resource types. Return nil if not applicable or if
988
+ # there is no match.
989
+ def determine_group_level_monitored_resource_type(tag)
1013
990
  @tag_regexp_list.each do |derived_type, tag_regexp|
1014
- matched_regexp_group = tag_regexp.match(tag)
1015
- if matched_regexp_group
1016
- resource.type = derived_type
1017
- break
1018
- end
991
+ matched_regex_group = tag_regexp.match(tag)
992
+ return [derived_type, matched_regex_group] if
993
+ matched_regex_group
1019
994
  end
995
+ [@resource.type, nil]
996
+ end
1020
997
 
1021
- # Determine the monitored resource based on the local_resource_id.
1022
- # Different monitored resource types have unique ids in different format.
1023
- # We will query Metadata Agent for the monitored resource. Return the
1024
- # legacy monitored resource (either the instance resource or the resource
1025
- # inferred from the tag) if failed to get a monitored resource from
1026
- # Metadata Agent with this key.
1027
- #
1028
- # Docker container:
1029
- # "container.<container_id>"
1030
- # "containerName.<container_name>"
1031
- # GKE container:
1032
- # "gke_containerName.<namespace_id>.<pod_name>.<container_name>"
1033
- if @enable_metadata_agent && local_resource_id
1034
- @log.debug 'Calling metadata agent with local_resource_id: ' \
1035
- "#{local_resource_id}."
1036
- retrieved_resource = call_metadata_agent_for_monitored_resource(
1037
- local_resource_id)
1038
- @log.debug 'Retrieved monitored resource from metadata agent: ' \
1039
- "#{retrieved_resource.inspect}."
1040
- unless retrieved_resource.nil?
1041
- # TODO(qingling128): Fix this temporary renaming from 'gke_container'
1042
- # to 'container'.
1043
- retrieved_resource.type = 'container' if
1044
- retrieved_resource.type == 'gke_container'
1045
- resource = retrieved_resource
1046
- end
1047
- end
998
+ # Determine group level monitored resource labels and common labels. These
999
+ # labels will be shared by a collection of entries.
1000
+ def determine_group_level_labels(group_resource_type, matched_regex_group)
1001
+ group_resource_labels = @resource.labels.dup
1002
+ group_common_labels = @common_labels.dup
1048
1003
 
1049
- # Once the resource type is settled down, determine the labels.
1050
- case resource.type
1004
+ case group_resource_type
1051
1005
  # Cloud Functions.
1052
1006
  when CLOUDFUNCTIONS_CONSTANTS[:resource_type]
1053
- resource.labels.merge!(
1007
+ group_resource_labels.merge!(
1054
1008
  'region' => @gcf_region,
1055
1009
  'function_name' => decode_cloudfunctions_function_name(
1056
- matched_regexp_group['encoded_function_name'])
1010
+ matched_regex_group['encoded_function_name'])
1057
1011
  )
1058
- instance_id = resource.labels.delete('instance_id')
1059
- common_labels.merge!(
1060
- "#{GKE_CONSTANTS[:service]}/instance_id" => instance_id,
1012
+
1013
+ instance_id = group_resource_labels.delete('instance_id')
1014
+ group_common_labels.merge!(
1015
+ "#{CONTAINER_CONSTANTS[:service]}/instance_id" => instance_id,
1061
1016
  "#{COMPUTE_CONSTANTS[:service]}/resource_id" => instance_id,
1062
- "#{GKE_CONSTANTS[:service]}/cluster_name" =>
1063
- resource.labels.delete('cluster_name'),
1017
+ "#{CONTAINER_CONSTANTS[:service]}/cluster_name" =>
1018
+ group_resource_labels.delete('cluster_name'),
1064
1019
  "#{COMPUTE_CONSTANTS[:service]}/zone" =>
1065
- resource.labels.delete('zone')
1020
+ group_resource_labels.delete('zone')
1066
1021
  )
1067
1022
 
1068
1023
  # GKE container.
1069
- when GKE_CONSTANTS[:resource_type]
1070
- if matched_regexp_group
1024
+ when CONTAINER_CONSTANTS[:resource_type]
1025
+ if matched_regex_group
1071
1026
  # We only expect one occurrence of each key in the match group.
1072
1027
  resource_labels_candidates =
1073
- matched_regexp_group.names.zip(matched_regexp_group.captures).to_h
1074
- common_labels_candidates = resource_labels_candidates.dup
1075
- resource.labels.merge!(
1028
+ matched_regex_group.names.zip(matched_regex_group.captures).to_h
1029
+ common_labels_candidates =
1030
+ resource_labels_candidates.dup
1031
+ group_resource_labels.merge!(
1076
1032
  delete_and_extract_labels(
1077
1033
  resource_labels_candidates,
1078
1034
  # The kubernetes_tag_regexp is poorly named. 'namespace_name' is
1079
1035
  # in fact 'namespace_id'. 'pod_name' is in fact 'pod_id'.
1080
1036
  # TODO(qingling128): Figure out how to put this map into
1081
- # constants like GKE_CONSTANTS[:extra_resource_labels].
1037
+ # constants like CONTAINER_CONSTANTS[:extra_resource_labels].
1082
1038
  'container_name' => 'container_name',
1083
1039
  'namespace_name' => 'namespace_id',
1084
1040
  'pod_name' => 'pod_id'))
1085
1041
 
1086
- common_labels.merge!(
1042
+ group_common_labels.merge!(
1087
1043
  delete_and_extract_labels(
1088
1044
  common_labels_candidates,
1089
- GKE_CONSTANTS[:extra_common_labels]
1090
- .map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h))
1045
+ CONTAINER_CONSTANTS[:extra_common_labels]
1046
+ .map { |l| [l, "#{CONTAINER_CONSTANTS[:service]}/#{l}"] }.to_h))
1091
1047
  end
1092
-
1093
- # Docker container.
1094
- # TODO(qingling128): Remove this logic once the resource is retrieved at a
1095
- # proper time (b/65175256).
1096
- when DOCKER_CONSTANTS[:resource_type]
1097
- common_labels.delete("#{COMPUTE_CONSTANTS[:service]}/resource_name")
1098
1048
  end
1099
1049
 
1100
- resource.freeze
1101
- resource.labels.freeze
1102
- common_labels.freeze
1103
-
1104
- [resource, common_labels]
1050
+ [group_resource_labels, group_common_labels]
1105
1051
  end
1106
1052
 
1107
- # Extract entry level monitored resource and common labels that should be
1108
- # applied to individual entries.
1109
- def determine_entry_level_monitored_resource_and_labels(
1110
- group_level_resource, group_level_common_labels, record)
1111
- resource = group_level_resource.dup
1112
- resource.labels = group_level_resource.labels.dup
1113
- common_labels = group_level_common_labels.dup
1053
+ # Extract entry resource and common labels that should be applied to
1054
+ # individual entries from the group resource.
1055
+ def determine_entry_level_labels(group_resource, record)
1056
+ resource_labels = {}
1057
+ common_labels = {}
1114
1058
 
1115
- case resource.type
1116
1059
  # Cloud Functions.
1117
- when CLOUDFUNCTIONS_CONSTANTS[:resource_type]
1118
- if record.key?('log')
1119
- @cloudfunctions_log_match =
1120
- @compiled_cloudfunctions_log_regexp.match(record['log'])
1121
- common_labels['execution_id'] =
1122
- @cloudfunctions_log_match['execution_id'] if
1123
- @cloudfunctions_log_match &&
1124
- @cloudfunctions_log_match['execution_id']
1125
- end
1060
+ if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
1061
+ record.key?('log')
1062
+ @cloudfunctions_log_match =
1063
+ @compiled_cloudfunctions_log_regexp.match(record['log'])
1064
+ common_labels['execution_id'] =
1065
+ @cloudfunctions_log_match['execution_id'] if \
1066
+ @cloudfunctions_log_match &&
1067
+ @cloudfunctions_log_match['execution_id']
1068
+ end
1126
1069
 
1127
- # GKE container.
1128
- when GKE_CONSTANTS[:resource_type]
1070
+ # GKE containers.
1071
+ if group_resource.type == CONTAINER_CONSTANTS[:resource_type]
1129
1072
  # Move the stdout/stderr annotation from the record into a label.
1130
1073
  common_labels.merge!(
1131
1074
  delete_and_extract_labels(
1132
- record, 'stream' => "#{GKE_CONSTANTS[:service]}/stream"))
1075
+ record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
1133
1076
 
1134
1077
  # If the record has been annotated by the kubernetes_metadata_filter
1135
1078
  # plugin, then use that metadata. Otherwise, rely on commonLabels
1136
- # populated from the group's tag.
1079
+ # populated at the grouped_entries level from the group's tag.
1137
1080
  if record.key?('kubernetes')
1138
- resource.labels.merge!(
1081
+ resource_labels.merge!(
1139
1082
  delete_and_extract_labels(
1140
- record['kubernetes'], GKE_CONSTANTS[:extra_resource_labels]
1083
+ record['kubernetes'], CONTAINER_CONSTANTS[:extra_resource_labels]
1141
1084
  .map { |l| [l, l] }.to_h))
1142
1085
  common_labels.merge!(
1143
1086
  delete_and_extract_labels(
1144
- record['kubernetes'], GKE_CONSTANTS[:extra_common_labels]
1145
- .map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h))
1087
+ record['kubernetes'], CONTAINER_CONSTANTS[:extra_common_labels]
1088
+ .map { |l| [l, "#{CONTAINER_CONSTANTS[:service]}/#{l}"] }.to_h))
1146
1089
  # Prepend label/ to all user-defined labels' keys.
1147
1090
  if record['kubernetes'].key?('labels')
1148
1091
  common_labels.merge!(
@@ -1168,56 +1111,14 @@ module Fluent
1168
1111
  # Report them as monitored resource labels instead of common labels.
1169
1112
  # e.g. "dataflow.googleapis.com/job_id" => "job_id"
1170
1113
  [DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
1171
- next unless resource.type == service_constants[:resource_type]
1172
- resource.labels.merge!(
1114
+ next unless group_resource.type == service_constants[:resource_type]
1115
+ resource_labels.merge!(
1173
1116
  delete_and_extract_labels(
1174
1117
  common_labels, service_constants[:extra_common_labels]
1175
1118
  .map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h))
1176
1119
  end
1177
1120
 
1178
- [resource, common_labels]
1179
- end
1180
-
1181
- # Call Metadata Agent to get monitored resource information and parse
1182
- # response to Google::Api::MonitoredResource.
1183
- def call_metadata_agent_for_monitored_resource(local_resource_id)
1184
- response = query_metadata_agent("monitoredResource/#{local_resource_id}")
1185
- return nil if response.nil?
1186
- begin
1187
- resource = Google::Api::MonitoredResource.decode_json(response.to_json)
1188
- rescue Google::Protobuf::ParseError, ArgumentError => e
1189
- @log.error 'Error paring monitored resource from Metadata Agent. ' \
1190
- "response: #{response.inspect}", error: e
1191
- return nil
1192
- end
1193
-
1194
- # TODO(qingling128): Use Google::Api::MonitoredResource directly after we
1195
- # upgrade gRPC version to include the fix for the protobuf map
1196
- # corruption issue.
1197
- Google::Apis::LoggingV2beta1::MonitoredResource.new(
1198
- type: resource.type,
1199
- labels: resource.labels.to_h
1200
- )
1201
- end
1202
-
1203
- # Issue a request to the Metadata Agent's local API and parse the response
1204
- # to JSON. Return nil in case of failure.
1205
- def query_metadata_agent(path)
1206
- url = "#{@metadata_agent_url}/#{path}"
1207
- @log.debug("Calling Metadata Agent: #{url}")
1208
- open(url) do |f|
1209
- response = f.read
1210
- parsed_hash = parse_json_or_nil(response)
1211
- if parsed_hash.nil?
1212
- @log.error 'Response from Metadata Agent is not in valid json ' \
1213
- "format: '#{response.inspect}'."
1214
- return nil
1215
- end
1216
- @log.debug "Response from Metadata Agent: #{parsed_hash}"
1217
- return parsed_hash
1218
- end
1219
- rescue StandardError => e
1220
- @log.error 'Error calling Metadata Agent.', error: e
1121
+ [resource_labels, common_labels]
1221
1122
  end
1222
1123
 
1223
1124
  # TODO: This functionality should eventually be available in another
@@ -1310,7 +1211,7 @@ module Fluent
1310
1211
  [ts_secs, ts_nanos]
1311
1212
  end
1312
1213
 
1313
- def compute_severity(resource_type, record, entry_level_common_labels)
1214
+ def compute_severity(resource_type, record, entry_common_labels)
1314
1215
  if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
1315
1216
  if @cloudfunctions_log_match && @cloudfunctions_log_match['severity']
1316
1217
  return parse_severity(@cloudfunctions_log_match['severity'])
@@ -1325,8 +1226,9 @@ module Fluent
1325
1226
  end
1326
1227
  elsif record.key?('severity')
1327
1228
  return parse_severity(record.delete('severity'))
1328
- elsif resource_type == GKE_CONSTANTS[:resource_type]
1329
- stream = entry_level_common_labels["#{GKE_CONSTANTS[:service]}/stream"]
1229
+ elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
1230
+ entry_common_labels.key?("#{CONTAINER_CONSTANTS[:service]}/stream")
1231
+ stream = entry_common_labels["#{CONTAINER_CONSTANTS[:service]}/stream"]
1330
1232
  if stream == 'stdout'
1331
1233
  return 'INFO'
1332
1234
  elsif stream == 'stderr'
@@ -1550,8 +1452,9 @@ module Fluent
1550
1452
  hash.nil? || !hash.is_a?(Hash)
1551
1453
  label_map.each_with_object({}) \
1552
1454
  do |(original_label, new_label), extracted_labels|
1553
- value = hash.delete(original_label)
1554
- extracted_labels[new_label] = convert_to_utf8(value.to_s) if value
1455
+ extracted_labels[new_label] =
1456
+ convert_to_utf8(hash.delete(original_label).to_s) if
1457
+ hash.key?(original_label)
1555
1458
  end
1556
1459
  end
1557
1460
 
@@ -1617,8 +1520,7 @@ module Fluent
1617
1520
  text_payload = record['log']
1618
1521
  elsif is_json
1619
1522
  json_payload = record
1620
- elsif [GKE_CONSTANTS[:resource_type],
1621
- DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
1523
+ elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
1622
1524
  record.key?('log')
1623
1525
  text_payload = record['log']
1624
1526
  elsif record.size == 1 && record.key?('message')
@@ -1648,7 +1550,7 @@ module Fluent
1648
1550
  elsif resource.type == APPENGINE_CONSTANTS[:resource_type]
1649
1551
  # Add a prefix to Managed VM logs to prevent namespace collisions.
1650
1552
  tag = "#{APPENGINE_CONSTANTS[:service]}/#{tag}"
1651
- elsif resource.type == GKE_CONSTANTS[:resource_type]
1553
+ elsif resource.type == CONTAINER_CONSTANTS[:resource_type]
1652
1554
  # For Kubernetes logs, use just the container name as the log name
1653
1555
  # if we have it.
1654
1556
  if resource.labels && resource.labels.key?('container_name')