fluent-plugin-google-cloud 0.6.6 → 0.6.7.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/fluent-plugin-google-cloud.gemspec +1 -1
- data/lib/fluent/plugin/out_google_cloud.rb +330 -236
- data/test/plugin/base_test.rb +407 -46
- data/test/plugin/constants.rb +152 -13
- data/test/plugin/test_out_google_cloud.rb +2 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4356aeba5dd3cdab64c30787a1dbc798f34f5182
|
4
|
+
data.tar.gz: 81ffb3cb3017fa43d42e3a45fce18cf8ddada913
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: faf46b39db9539a778d3c5144d8382e899e009ef17029334817959d316c979ab1419b91be0dcc61d146e94baf41e43b7a9e606699ac6bea1e595ade86dcd9595
|
7
|
+
data.tar.gz: 10f148519e9ff916b914e3e24020331d702f93878f54314476f907a63c0db081aee4ceda2f3b40d190a48e5b74cc58d314b62fd4d2901110613ae604850b1a1a
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fluent-plugin-google-cloud (0.6.
|
4
|
+
fluent-plugin-google-cloud (0.6.7.pre.1)
|
5
5
|
fluentd (~> 0.10)
|
6
6
|
google-api-client (~> 0.9.0)
|
7
7
|
google-cloud-logging (= 0.24.1)
|
@@ -55,7 +55,7 @@ GEM
|
|
55
55
|
googleauth (~> 0.5.1)
|
56
56
|
grpc (~> 1.0)
|
57
57
|
rly (~> 0.2.3)
|
58
|
-
google-protobuf (3.4.0.2)
|
58
|
+
google-protobuf (3.4.0.2-x86_64-linux)
|
59
59
|
googleapis-common-protos (1.3.5)
|
60
60
|
google-protobuf (~> 3.2)
|
61
61
|
grpc (~> 1.0)
|
@@ -67,7 +67,7 @@ GEM
|
|
67
67
|
multi_json (~> 1.11)
|
68
68
|
os (~> 0.9)
|
69
69
|
signet (~> 0.7)
|
70
|
-
grpc (1.2.5)
|
70
|
+
grpc (1.2.5-x86_64-linux)
|
71
71
|
google-protobuf (~> 3.1)
|
72
72
|
googleauth (~> 0.5.1)
|
73
73
|
hashdiff (0.3.6)
|
@@ -88,7 +88,7 @@ GEM
|
|
88
88
|
mocha (1.3.0)
|
89
89
|
metaclass (~> 0.0.1)
|
90
90
|
msgpack (1.1.0)
|
91
|
-
multi_json (1.12.
|
91
|
+
multi_json (1.12.2)
|
92
92
|
multipart-post (2.0.0)
|
93
93
|
os (0.9.6)
|
94
94
|
parser (2.4.0.0)
|
@@ -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.
|
13
|
+
gem.version = '0.6.7.pre.1'
|
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
|
42
|
-
module
|
41
|
+
# Constants for service names, resource types and etc.
|
42
|
+
module ServiceConstants
|
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)
|
47
47
|
}
|
48
48
|
CLOUDFUNCTIONS_CONSTANTS = {
|
49
49
|
service: 'cloudfunctions.googleapis.com',
|
@@ -53,12 +53,16 @@ module Fluent
|
|
53
53
|
service: 'compute.googleapis.com',
|
54
54
|
resource_type: 'gce_instance'
|
55
55
|
}
|
56
|
-
|
56
|
+
GKE_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)
|
61
|
+
metadata_attributes: %w(kube-env)
|
62
|
+
}
|
63
|
+
DOCKER_CONSTANTS = {
|
64
|
+
service: 'docker.googleapis.com',
|
65
|
+
resource_type: 'docker_container'
|
62
66
|
}
|
63
67
|
DATAFLOW_CONSTANTS = {
|
64
68
|
service: 'dataflow.googleapis.com',
|
@@ -68,8 +72,7 @@ module Fluent
|
|
68
72
|
DATAPROC_CONSTANTS = {
|
69
73
|
service: 'cluster.dataproc.googleapis.com',
|
70
74
|
resource_type: 'cloud_dataproc_cluster',
|
71
|
-
metadata_attributes:
|
72
|
-
%w(dataproc-cluster-uuid dataproc-cluster-name).to_set
|
75
|
+
metadata_attributes: %w(dataproc-cluster-uuid dataproc-cluster-name)
|
73
76
|
}
|
74
77
|
EC2_CONSTANTS = {
|
75
78
|
service: 'ec2.amazonaws.com',
|
@@ -83,7 +86,7 @@ module Fluent
|
|
83
86
|
|
84
87
|
# The map between a subservice name and a resource type.
|
85
88
|
SUBSERVICE_MAP = \
|
86
|
-
[APPENGINE_CONSTANTS,
|
89
|
+
[APPENGINE_CONSTANTS, GKE_CONSTANTS, DATAFLOW_CONSTANTS,
|
87
90
|
DATAPROC_CONSTANTS, ML_CONSTANTS]
|
88
91
|
.map { |consts| [consts[:service], consts[:resource_type]] }.to_h
|
89
92
|
# Default back to GCE if invalid value is detected.
|
@@ -91,18 +94,35 @@ module Fluent
|
|
91
94
|
|
92
95
|
# The map between a resource type and expected subservice attributes.
|
93
96
|
SUBSERVICE_METADATA_ATTRIBUTES = \
|
94
|
-
[APPENGINE_CONSTANTS,
|
95
|
-
.map
|
96
|
-
|
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
|
97
102
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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.
|
101
107
|
DEFAULT_HTTP_REQUEST_KEY = 'httpRequest'
|
102
|
-
DEFAULT_OPERATION_KEY =
|
103
|
-
DEFAULT_SOURCE_LOCATION_KEY =
|
104
|
-
|
105
|
-
|
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'
|
106
126
|
|
107
127
|
# Map from each field name under LogEntry to corresponding variables
|
108
128
|
# required to perform field value extraction from the log record.
|
@@ -155,12 +175,14 @@ module Fluent
|
|
155
175
|
}
|
156
176
|
end
|
157
177
|
|
158
|
-
include self::
|
178
|
+
include self::ServiceConstants
|
179
|
+
include self::ConfigConstants
|
180
|
+
include self::InternalConstants
|
159
181
|
|
160
182
|
Fluent::Plugin.register_output('google_cloud', self)
|
161
183
|
|
162
184
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
163
|
-
PLUGIN_VERSION = '0.6.
|
185
|
+
PLUGIN_VERSION = '0.6.7.pre.1'
|
164
186
|
|
165
187
|
# Name of the the Google cloud logging write scope.
|
166
188
|
LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
|
@@ -198,6 +220,11 @@ module Fluent
|
|
198
220
|
DEFAULT_SOURCE_LOCATION_KEY
|
199
221
|
config_param :trace_key, :string, :default => DEFAULT_TRACE_KEY
|
200
222
|
|
223
|
+
# Whether to try to detect if the record is a text log entry with JSON
|
224
|
+
# content that needs to be parsed.
|
225
|
+
config_param :detect_json, :bool, :default => false
|
226
|
+
# TODO(igorpeshansky): Add a parameter for the text field in the payload.
|
227
|
+
|
201
228
|
# Whether to try to detect if the VM is owned by a "subservice" such as App
|
202
229
|
# Engine of Kubernetes, rather than just associating the logs with the
|
203
230
|
# compute service of the platform. This currently only has any effect when
|
@@ -290,6 +317,11 @@ module Fluent
|
|
290
317
|
config_param :monitoring_type, :string,
|
291
318
|
:default => Monitoring::PrometheusMonitoringRegistry.name
|
292
319
|
|
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
|
+
|
293
325
|
# rubocop:enable Style/HashSyntax
|
294
326
|
|
295
327
|
# TODO: Add a log_name config option rather than just using the tag?
|
@@ -349,28 +381,30 @@ module Fluent
|
|
349
381
|
|
350
382
|
@platform = detect_platform
|
351
383
|
|
352
|
-
# Set
|
353
|
-
#
|
354
|
-
#
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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.
|
361
396
|
set_required_metadata_variables
|
362
397
|
|
363
398
|
# Retrieve monitored resource.
|
364
|
-
#
|
365
|
-
#
|
366
|
-
|
367
|
-
@resource = determine_agent_level_monitored_resource_via_legacy
|
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
|
368
402
|
|
369
403
|
# Set regexp that we should match tags against later on. Using a list
|
370
404
|
# instead of a map to ensure order. For example, tags will be matched
|
371
405
|
# against Cloud Functions first, then GKE.
|
372
406
|
@tag_regexp_list = []
|
373
|
-
if @resource.type ==
|
407
|
+
if @resource.type == GKE_CONSTANTS[:resource_type]
|
374
408
|
# We only support Cloud Functions logs for GKE right now.
|
375
409
|
if fetch_gce_metadata('instance/attributes/'
|
376
410
|
).split.include?('gcf_region')
|
@@ -383,7 +417,7 @@ module Fluent
|
|
383
417
|
]
|
384
418
|
end
|
385
419
|
@tag_regexp_list << [
|
386
|
-
|
420
|
+
GKE_CONSTANTS[:resource_type], @compiled_kubernetes_tag_regexp
|
387
421
|
]
|
388
422
|
end
|
389
423
|
|
@@ -399,7 +433,7 @@ module Fluent
|
|
399
433
|
|
400
434
|
# Log an informational message containing the Logs viewer URL
|
401
435
|
@log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
|
402
|
-
"viewer?project=#{@project_id}&resource=#{@
|
436
|
+
"viewer?project=#{@project_id}&resource=#{@resource.type}/",
|
403
437
|
"instance_id/#{@vm_id}"
|
404
438
|
end
|
405
439
|
|
@@ -415,62 +449,52 @@ module Fluent
|
|
415
449
|
end
|
416
450
|
|
417
451
|
def write(chunk)
|
418
|
-
|
419
|
-
grouped_entries = {}
|
420
|
-
chunk.msgpack_each do |tag, *arr|
|
421
|
-
sanitized_tag = sanitize_tag(tag)
|
422
|
-
if sanitized_tag.nil?
|
423
|
-
@log.warn "Dropping log entries with invalid tag: '#{tag}'. " \
|
424
|
-
'A tag should be a string with utf8 characters.'
|
425
|
-
next
|
426
|
-
end
|
427
|
-
grouped_entries[sanitized_tag] ||= []
|
428
|
-
grouped_entries[sanitized_tag].push(arr)
|
429
|
-
end
|
452
|
+
grouped_entries = group_log_entries_by_tag_and_local_resource_id(chunk)
|
430
453
|
|
431
|
-
grouped_entries.each do |tag, arr|
|
454
|
+
grouped_entries.each do |(tag, local_resource_id), arr|
|
432
455
|
entries = []
|
433
|
-
|
434
|
-
determine_group_level_monitored_resource_and_labels(
|
456
|
+
group_level_resource, group_level_common_labels =
|
457
|
+
determine_group_level_monitored_resource_and_labels(
|
458
|
+
tag, local_resource_id)
|
435
459
|
|
436
460
|
arr.each do |time, record|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
timestamp = record.key?('time') ? record['time'] : nil
|
450
|
-
record.delete('time')
|
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)
|
464
|
+
|
465
|
+
is_json = false
|
466
|
+
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.
|
470
|
+
timestamp = record.delete('time')
|
471
|
+
severity = record.delete('severity')
|
472
|
+
|
451
473
|
# If the log is json, we want to export it as a structured log
|
452
474
|
# unless there is additional metadata that would be lost.
|
453
|
-
|
454
|
-
if record.length == 1
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
475
|
+
record_json = nil
|
476
|
+
if record.length == 1
|
477
|
+
%w(log message msg).each do |field|
|
478
|
+
if record.key?(field)
|
479
|
+
record_json = parse_json_or_nil(record[field])
|
480
|
+
end
|
481
|
+
end
|
459
482
|
end
|
460
483
|
unless record_json.nil?
|
461
484
|
record = record_json
|
462
485
|
is_json = true
|
463
486
|
end
|
464
|
-
# Restore timestamp if necessary.
|
465
|
-
|
466
|
-
|
467
|
-
|
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
|
468
492
|
end
|
469
493
|
|
470
494
|
ts_secs, ts_nanos = compute_timestamp(
|
471
|
-
|
495
|
+
entry_level_resource.type, record, time)
|
472
496
|
severity = compute_severity(
|
473
|
-
|
497
|
+
entry_level_resource.type, record, entry_level_common_labels)
|
474
498
|
|
475
499
|
ts_secs = begin
|
476
500
|
Integer ts_secs
|
@@ -482,12 +506,13 @@ module Fluent
|
|
482
506
|
rescue ArgumentError, TypeError
|
483
507
|
ts_nanos
|
484
508
|
end
|
509
|
+
|
485
510
|
if @use_grpc
|
486
511
|
entry = Google::Logging::V2::LogEntry.new(
|
487
|
-
labels:
|
512
|
+
labels: entry_level_common_labels,
|
488
513
|
resource: Google::Api::MonitoredResource.new(
|
489
|
-
type:
|
490
|
-
labels:
|
514
|
+
type: entry_level_resource.type,
|
515
|
+
labels: entry_level_resource.labels.to_h
|
491
516
|
),
|
492
517
|
severity: grpc_severity(severity)
|
493
518
|
)
|
@@ -504,10 +529,11 @@ module Fluent
|
|
504
529
|
end
|
505
530
|
else
|
506
531
|
# Remove the labels if we didn't populate them with anything.
|
507
|
-
|
532
|
+
entry_level_resource.labels = nil if
|
533
|
+
entry_level_resource.labels.empty?
|
508
534
|
entry = Google::Apis::LoggingV2beta1::LogEntry.new(
|
509
|
-
labels:
|
510
|
-
resource:
|
535
|
+
labels: entry_level_common_labels,
|
536
|
+
resource: entry_level_resource,
|
511
537
|
severity: severity,
|
512
538
|
timestamp: {
|
513
539
|
seconds: ts_secs,
|
@@ -516,17 +542,12 @@ module Fluent
|
|
516
542
|
)
|
517
543
|
end
|
518
544
|
|
519
|
-
# Get fully-qualified trace id for LogEntry "trace" field
|
545
|
+
# Get fully-qualified trace id for LogEntry "trace" field.
|
520
546
|
fq_trace_id = record.delete(@trace_key)
|
521
547
|
entry.trace = fq_trace_id if fq_trace_id
|
522
548
|
|
523
549
|
set_log_entry_fields(record, entry)
|
524
|
-
|
525
|
-
if @use_grpc
|
526
|
-
set_payload_grpc(entry_resource.type, record, entry, is_json)
|
527
|
-
else
|
528
|
-
set_payload(entry_resource.type, record, entry, is_json)
|
529
|
-
end
|
550
|
+
set_payload(entry_level_resource.type, record, entry, is_json)
|
530
551
|
|
531
552
|
entries.push(entry)
|
532
553
|
end
|
@@ -534,21 +555,21 @@ module Fluent
|
|
534
555
|
next if entries.empty?
|
535
556
|
|
536
557
|
log_name = "projects/#{@project_id}/logs/#{log_name(
|
537
|
-
tag,
|
558
|
+
tag, group_level_resource)}"
|
538
559
|
|
539
560
|
# Does the actual write to the cloud logging api.
|
540
561
|
client = api_client
|
541
562
|
if @use_grpc
|
542
563
|
begin
|
543
|
-
labels_utf8_pairs =
|
564
|
+
labels_utf8_pairs = group_level_common_labels.map do |k, v|
|
544
565
|
[k.encode('utf-8'), convert_to_utf8(v)]
|
545
566
|
end
|
546
567
|
|
547
568
|
write_request = Google::Logging::V2::WriteLogEntriesRequest.new(
|
548
569
|
log_name: log_name,
|
549
570
|
resource: Google::Api::MonitoredResource.new(
|
550
|
-
type:
|
551
|
-
labels:
|
571
|
+
type: group_level_resource.type,
|
572
|
+
labels: group_level_resource.labels.to_h
|
552
573
|
),
|
553
574
|
labels: labels_utf8_pairs.to_h,
|
554
575
|
entries: entries
|
@@ -558,8 +579,8 @@ module Fluent
|
|
558
579
|
increment_successful_requests_count
|
559
580
|
increment_ingested_entries_count(entries.length)
|
560
581
|
|
561
|
-
# Let the user explicitly know when the first call succeeded,
|
562
|
-
#
|
582
|
+
# Let the user explicitly know when the first call succeeded, to aid
|
583
|
+
# with verification and troubleshooting.
|
563
584
|
unless @successful_call
|
564
585
|
@successful_call = true
|
565
586
|
@log.info 'Successfully sent gRPC to Stackdriver Logging API.'
|
@@ -598,8 +619,8 @@ module Fluent
|
|
598
619
|
@log.warn "Dropping #{dropped} log message(s)",
|
599
620
|
error: error.to_s, error_code: error.code.to_s
|
600
621
|
else
|
601
|
-
# Assume this is a problem with the request itself
|
602
|
-
#
|
622
|
+
# Assume this is a problem with the request itself and don't
|
623
|
+
# retry.
|
603
624
|
dropped = entries.length
|
604
625
|
increment_dropped_entries_count(dropped)
|
605
626
|
@log.error "Unknown response code #{error.code} from the "\
|
@@ -612,8 +633,8 @@ module Fluent
|
|
612
633
|
write_request = \
|
613
634
|
Google::Apis::LoggingV2beta1::WriteLogEntriesRequest.new(
|
614
635
|
log_name: log_name,
|
615
|
-
resource:
|
616
|
-
labels:
|
636
|
+
resource: group_level_resource,
|
637
|
+
labels: group_level_common_labels,
|
617
638
|
entries: entries)
|
618
639
|
|
619
640
|
# TODO: RequestOptions
|
@@ -626,8 +647,8 @@ module Fluent
|
|
626
647
|
increment_successful_requests_count
|
627
648
|
increment_ingested_entries_count(entries.length)
|
628
649
|
|
629
|
-
# Let the user explicitly know when the first call succeeded,
|
630
|
-
#
|
650
|
+
# Let the user explicitly know when the first call succeeded, to aid
|
651
|
+
# with verification and troubleshooting.
|
631
652
|
unless @successful_call
|
632
653
|
@successful_call = true
|
633
654
|
@log.info 'Successfully sent to Stackdriver Logging API.'
|
@@ -818,8 +839,9 @@ module Fluent
|
|
818
839
|
|
819
840
|
# Retrieve monitored resource via the legacy way.
|
820
841
|
#
|
821
|
-
#
|
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
845
|
def determine_agent_level_monitored_resource_via_legacy
|
824
846
|
resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
825
847
|
labels: {})
|
@@ -881,7 +903,7 @@ module Fluent
|
|
881
903
|
}
|
882
904
|
|
883
905
|
# GKE container.
|
884
|
-
when
|
906
|
+
when GKE_CONSTANTS[:resource_type]
|
885
907
|
raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
|
886
908
|
kube_env = YAML.load(raw_kube_env)
|
887
909
|
return {
|
@@ -916,7 +938,7 @@ module Fluent
|
|
916
938
|
rescue StandardError => e
|
917
939
|
@log.error "Failed to set monitored resource labels for #{type}: ",
|
918
940
|
error: e
|
919
|
-
|
941
|
+
{}
|
920
942
|
end
|
921
943
|
|
922
944
|
# Determine the common labels that should be added to all log entries
|
@@ -940,7 +962,7 @@ module Fluent
|
|
940
962
|
|
941
963
|
# GCE instance and GKE container.
|
942
964
|
when COMPUTE_CONSTANTS[:resource_type],
|
943
|
-
|
965
|
+
GKE_CONSTANTS[:resource_type]
|
944
966
|
labels.merge!(
|
945
967
|
"#{COMPUTE_CONSTANTS[:service]}/resource_name" => @vm_name)
|
946
968
|
|
@@ -952,138 +974,175 @@ module Fluent
|
|
952
974
|
labels
|
953
975
|
end
|
954
976
|
|
955
|
-
#
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
[group_resource, group_common_labels]
|
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
|
980
1000
|
end
|
981
1001
|
|
982
|
-
# Determine group level monitored resource
|
983
|
-
# entries.
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
1002
|
+
# Determine the group level monitored resource and common labels shared by a
|
1003
|
+
# 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.
|
988
1013
|
@tag_regexp_list.each do |derived_type, tag_regexp|
|
989
|
-
|
990
|
-
|
991
|
-
|
1014
|
+
matched_regexp_group = tag_regexp.match(tag)
|
1015
|
+
if matched_regexp_group
|
1016
|
+
resource.type = derived_type
|
1017
|
+
break
|
1018
|
+
end
|
992
1019
|
end
|
993
|
-
[@resource.type, nil]
|
994
|
-
end
|
995
1020
|
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
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
|
1001
1048
|
|
1002
|
-
|
1049
|
+
# Once the resource type is settled down, determine the labels.
|
1050
|
+
case resource.type
|
1003
1051
|
# Cloud Functions.
|
1004
1052
|
when CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
1005
|
-
|
1053
|
+
resource.labels.merge!(
|
1006
1054
|
'region' => @gcf_region,
|
1007
1055
|
'function_name' => decode_cloudfunctions_function_name(
|
1008
|
-
|
1056
|
+
matched_regexp_group['encoded_function_name'])
|
1009
1057
|
)
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
"#{CONTAINER_CONSTANTS[:service]}/instance_id" => instance_id,
|
1058
|
+
instance_id = resource.labels.delete('instance_id')
|
1059
|
+
common_labels.merge!(
|
1060
|
+
"#{GKE_CONSTANTS[:service]}/instance_id" => instance_id,
|
1014
1061
|
"#{COMPUTE_CONSTANTS[:service]}/resource_id" => instance_id,
|
1015
|
-
"#{
|
1016
|
-
|
1062
|
+
"#{GKE_CONSTANTS[:service]}/cluster_name" =>
|
1063
|
+
resource.labels.delete('cluster_name'),
|
1017
1064
|
"#{COMPUTE_CONSTANTS[:service]}/zone" =>
|
1018
|
-
|
1065
|
+
resource.labels.delete('zone')
|
1019
1066
|
)
|
1020
1067
|
|
1021
1068
|
# GKE container.
|
1022
|
-
when
|
1023
|
-
if
|
1069
|
+
when GKE_CONSTANTS[:resource_type]
|
1070
|
+
if matched_regexp_group
|
1024
1071
|
# We only expect one occurrence of each key in the match group.
|
1025
1072
|
resource_labels_candidates =
|
1026
|
-
|
1027
|
-
common_labels_candidates =
|
1028
|
-
|
1029
|
-
group_resource_labels.merge!(
|
1073
|
+
matched_regexp_group.names.zip(matched_regexp_group.captures).to_h
|
1074
|
+
common_labels_candidates = resource_labels_candidates.dup
|
1075
|
+
resource.labels.merge!(
|
1030
1076
|
delete_and_extract_labels(
|
1031
1077
|
resource_labels_candidates,
|
1032
1078
|
# The kubernetes_tag_regexp is poorly named. 'namespace_name' is
|
1033
1079
|
# in fact 'namespace_id'. 'pod_name' is in fact 'pod_id'.
|
1034
1080
|
# TODO(qingling128): Figure out how to put this map into
|
1035
|
-
# constants like
|
1081
|
+
# constants like GKE_CONSTANTS[:extra_resource_labels].
|
1036
1082
|
'container_name' => 'container_name',
|
1037
1083
|
'namespace_name' => 'namespace_id',
|
1038
1084
|
'pod_name' => 'pod_id'))
|
1039
1085
|
|
1040
|
-
|
1086
|
+
common_labels.merge!(
|
1041
1087
|
delete_and_extract_labels(
|
1042
1088
|
common_labels_candidates,
|
1043
|
-
|
1044
|
-
.map { |l| [l, "#{
|
1089
|
+
GKE_CONSTANTS[:extra_common_labels]
|
1090
|
+
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h))
|
1045
1091
|
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")
|
1046
1098
|
end
|
1047
1099
|
|
1048
|
-
|
1100
|
+
resource.freeze
|
1101
|
+
resource.labels.freeze
|
1102
|
+
common_labels.freeze
|
1103
|
+
|
1104
|
+
[resource, common_labels]
|
1049
1105
|
end
|
1050
1106
|
|
1051
|
-
# Extract entry resource and common labels that should be
|
1052
|
-
# individual entries
|
1053
|
-
def
|
1054
|
-
|
1055
|
-
|
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
|
1056
1114
|
|
1115
|
+
case resource.type
|
1057
1116
|
# Cloud Functions.
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
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
|
1067
1126
|
|
1068
|
-
# GKE
|
1069
|
-
|
1127
|
+
# GKE container.
|
1128
|
+
when GKE_CONSTANTS[:resource_type]
|
1070
1129
|
# Move the stdout/stderr annotation from the record into a label.
|
1071
1130
|
common_labels.merge!(
|
1072
1131
|
delete_and_extract_labels(
|
1073
|
-
record, 'stream' => "#{
|
1132
|
+
record, 'stream' => "#{GKE_CONSTANTS[:service]}/stream"))
|
1074
1133
|
|
1075
1134
|
# If the record has been annotated by the kubernetes_metadata_filter
|
1076
1135
|
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
1077
|
-
# populated
|
1136
|
+
# populated from the group's tag.
|
1078
1137
|
if record.key?('kubernetes')
|
1079
|
-
|
1138
|
+
resource.labels.merge!(
|
1080
1139
|
delete_and_extract_labels(
|
1081
|
-
record['kubernetes'],
|
1140
|
+
record['kubernetes'], GKE_CONSTANTS[:extra_resource_labels]
|
1082
1141
|
.map { |l| [l, l] }.to_h))
|
1083
1142
|
common_labels.merge!(
|
1084
1143
|
delete_and_extract_labels(
|
1085
|
-
record['kubernetes'],
|
1086
|
-
.map { |l| [l, "#{
|
1144
|
+
record['kubernetes'], GKE_CONSTANTS[:extra_common_labels]
|
1145
|
+
.map { |l| [l, "#{GKE_CONSTANTS[:service]}/#{l}"] }.to_h))
|
1087
1146
|
# Prepend label/ to all user-defined labels' keys.
|
1088
1147
|
if record['kubernetes'].key?('labels')
|
1089
1148
|
common_labels.merge!(
|
@@ -1109,14 +1168,56 @@ module Fluent
|
|
1109
1168
|
# Report them as monitored resource labels instead of common labels.
|
1110
1169
|
# e.g. "dataflow.googleapis.com/job_id" => "job_id"
|
1111
1170
|
[DATAFLOW_CONSTANTS, ML_CONSTANTS].each do |service_constants|
|
1112
|
-
next unless
|
1113
|
-
|
1171
|
+
next unless resource.type == service_constants[:resource_type]
|
1172
|
+
resource.labels.merge!(
|
1114
1173
|
delete_and_extract_labels(
|
1115
1174
|
common_labels, service_constants[:extra_common_labels]
|
1116
1175
|
.map { |l| ["#{service_constants[:service]}/#{l}", l] }.to_h))
|
1117
1176
|
end
|
1118
1177
|
|
1119
|
-
[
|
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
|
1120
1221
|
end
|
1121
1222
|
|
1122
1223
|
# TODO: This functionality should eventually be available in another
|
@@ -1209,7 +1310,7 @@ module Fluent
|
|
1209
1310
|
[ts_secs, ts_nanos]
|
1210
1311
|
end
|
1211
1312
|
|
1212
|
-
def compute_severity(resource_type, record,
|
1313
|
+
def compute_severity(resource_type, record, entry_level_common_labels)
|
1213
1314
|
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
1214
1315
|
if @cloudfunctions_log_match && @cloudfunctions_log_match['severity']
|
1215
1316
|
return parse_severity(@cloudfunctions_log_match['severity'])
|
@@ -1224,9 +1325,8 @@ module Fluent
|
|
1224
1325
|
end
|
1225
1326
|
elsif record.key?('severity')
|
1226
1327
|
return parse_severity(record.delete('severity'))
|
1227
|
-
elsif resource_type ==
|
1228
|
-
|
1229
|
-
stream = entry_common_labels["#{CONTAINER_CONSTANTS[:service]}/stream"]
|
1328
|
+
elsif resource_type == GKE_CONSTANTS[:resource_type]
|
1329
|
+
stream = entry_level_common_labels["#{GKE_CONSTANTS[:service]}/stream"]
|
1230
1330
|
if stream == 'stdout'
|
1231
1331
|
return 'INFO'
|
1232
1332
|
elsif stream == 'stderr'
|
@@ -1296,6 +1396,9 @@ module Fluent
|
|
1296
1396
|
'FINE' => 'DEBUG',
|
1297
1397
|
'FINER' => 'DEBUG',
|
1298
1398
|
'FINEST' => 'DEBUG',
|
1399
|
+
# java.util.logging levels (only missing ones from above listed).
|
1400
|
+
'SEVERE' => 'ERROR',
|
1401
|
+
'CONFIG' => 'DEBUG',
|
1299
1402
|
# nginx levels (only missing ones from above listed).
|
1300
1403
|
'CRIT' => 'CRITICAL',
|
1301
1404
|
'EMERG' => 'EMERGENCY',
|
@@ -1447,34 +1550,8 @@ module Fluent
|
|
1447
1550
|
hash.nil? || !hash.is_a?(Hash)
|
1448
1551
|
label_map.each_with_object({}) \
|
1449
1552
|
do |(original_label, new_label), extracted_labels|
|
1450
|
-
|
1451
|
-
|
1452
|
-
hash.key?(original_label)
|
1453
|
-
end
|
1454
|
-
end
|
1455
|
-
|
1456
|
-
def set_payload(resource_type, record, entry, is_json)
|
1457
|
-
# If this is a Cloud Functions log that matched the expected regexp,
|
1458
|
-
# use text payload. Otherwise, use JSON if we found valid JSON, or text
|
1459
|
-
# payload in the following cases:
|
1460
|
-
# 1. This is a Cloud Functions log and the 'log' key is available
|
1461
|
-
# 2. This is an unstructured Container log and the 'log' key is available
|
1462
|
-
# 3. The only remaining key is 'message'
|
1463
|
-
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1464
|
-
@cloudfunctions_log_match
|
1465
|
-
entry.text_payload = @cloudfunctions_log_match['text']
|
1466
|
-
elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1467
|
-
record.key?('log')
|
1468
|
-
entry.text_payload = record['log']
|
1469
|
-
elsif is_json
|
1470
|
-
entry.json_payload = record
|
1471
|
-
elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
|
1472
|
-
record.key?('log')
|
1473
|
-
entry.text_payload = record['log']
|
1474
|
-
elsif record.size == 1 && record.key?('message')
|
1475
|
-
entry.text_payload = record['message']
|
1476
|
-
else
|
1477
|
-
entry.json_payload = record
|
1553
|
+
value = hash.delete(original_label)
|
1554
|
+
extracted_labels[new_label] = convert_to_utf8(value.to_s) if value
|
1478
1555
|
end
|
1479
1556
|
end
|
1480
1557
|
|
@@ -1522,7 +1599,10 @@ module Fluent
|
|
1522
1599
|
ret
|
1523
1600
|
end
|
1524
1601
|
|
1525
|
-
def
|
1602
|
+
def set_payload(resource_type, record, entry, is_json)
|
1603
|
+
# Only one of {text_payload, json_payload} will be set.
|
1604
|
+
text_payload = nil
|
1605
|
+
json_payload = nil
|
1526
1606
|
# If this is a Cloud Functions log that matched the expected regexp,
|
1527
1607
|
# use text payload. Otherwise, use JSON if we found valid JSON, or text
|
1528
1608
|
# payload in the following cases:
|
@@ -1531,20 +1611,34 @@ module Fluent
|
|
1531
1611
|
# 3. The only remaining key is 'message'
|
1532
1612
|
if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1533
1613
|
@cloudfunctions_log_match
|
1534
|
-
|
1535
|
-
@cloudfunctions_log_match['text'])
|
1614
|
+
text_payload = @cloudfunctions_log_match['text']
|
1536
1615
|
elsif resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1537
1616
|
record.key?('log')
|
1538
|
-
|
1617
|
+
text_payload = record['log']
|
1539
1618
|
elsif is_json
|
1540
|
-
|
1541
|
-
elsif
|
1619
|
+
json_payload = record
|
1620
|
+
elsif [GKE_CONSTANTS[:resource_type],
|
1621
|
+
DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
|
1542
1622
|
record.key?('log')
|
1543
|
-
|
1623
|
+
text_payload = record['log']
|
1544
1624
|
elsif record.size == 1 && record.key?('message')
|
1545
|
-
|
1625
|
+
text_payload = record['message']
|
1546
1626
|
else
|
1547
|
-
|
1627
|
+
json_payload = record
|
1628
|
+
end
|
1629
|
+
|
1630
|
+
if json_payload
|
1631
|
+
entry.json_payload = if @use_grpc
|
1632
|
+
struct_from_ruby(json_payload)
|
1633
|
+
else
|
1634
|
+
json_payload
|
1635
|
+
end
|
1636
|
+
elsif text_payload
|
1637
|
+
entry.text_payload = if @use_grpc
|
1638
|
+
convert_to_utf8(text_payload)
|
1639
|
+
else
|
1640
|
+
text_payload
|
1641
|
+
end
|
1548
1642
|
end
|
1549
1643
|
end
|
1550
1644
|
|
@@ -1554,7 +1648,7 @@ module Fluent
|
|
1554
1648
|
elsif resource.type == APPENGINE_CONSTANTS[:resource_type]
|
1555
1649
|
# Add a prefix to Managed VM logs to prevent namespace collisions.
|
1556
1650
|
tag = "#{APPENGINE_CONSTANTS[:service]}/#{tag}"
|
1557
|
-
elsif resource.type ==
|
1651
|
+
elsif resource.type == GKE_CONSTANTS[:resource_type]
|
1558
1652
|
# For Kubernetes logs, use just the container name as the log name
|
1559
1653
|
# if we have it.
|
1560
1654
|
if resource.labels && resource.labels.key?('container_name')
|