fluent-plugin-google-cloud 0.6.4 → 0.6.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab431f0283f1c6bc88669159be387f21bb1e908f
4
- data.tar.gz: 77d320fd8b6e4c5370542142fd296581fcd9186e
3
+ metadata.gz: 7b1a26eab783c87890f78500848a29048b4ab615
4
+ data.tar.gz: 81711bc2e00bcf222b7249bde80d78c6bab34957
5
5
  SHA512:
6
- metadata.gz: 15fc471919366c52a612cb2b1941d0e9ffdf5e1d5c94ad6f3395c1860ca2f03edd888959a0ed267375bf80133458251bc1555c7416ef81f8be4f4aa83fd5c405
7
- data.tar.gz: 4aed5e4e2686eb4776d32f3b44f61fc86849f4f02a64bd12399d0e0ac4a6cc1a8752489513fa5e482fdb7cac432d517ed5b7b26e71365e344e2dcf2ca8e96832
6
+ metadata.gz: 7d5c1ca8f704998cd36c5a040dc4fa10989d2cbb7717dea8fa67032c3c42b428a5f3ef37f04581caea41bf991232688d3245ebe285e7fb72f9f0b783712d399f
7
+ data.tar.gz: 8a95c809c56fcc6351f08bd81d3124cd31b6529c54afb3c72d109519c4aecd5764d26514b4dc8a99417510c3883451ae54189e65a1e10d81465143888bf68b11
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fluent-plugin-google-cloud (0.6.4)
4
+ fluent-plugin-google-cloud (0.6.5.pre.1)
5
+ excon (~> 0.57.1)
5
6
  fluentd (~> 0.10)
6
7
  google-api-client (~> 0.9.0)
7
- google-cloud-logging (~> 0.23.2)
8
+ google-cloud-logging (= 0.24.1)
8
9
  googleapis-common-protos (~> 1.3)
9
- googleauth (~> 0.4)
10
+ googleauth (~> 0.4, < 0.5.2)
10
11
  grpc (~> 1.0, < 1.3)
11
12
  json (~> 1.8)
12
13
 
@@ -18,15 +19,17 @@ GEM
18
19
  ast (2.3.0)
19
20
  astrolabe (1.3.1)
20
21
  parser (~> 2.2)
21
- cool.io (1.5.0)
22
+ cool.io (1.5.1)
22
23
  crack (0.4.3)
23
24
  safe_yaml (~> 1.0.0)
24
- faraday (0.12.1)
25
+ excon (0.57.1)
26
+ faraday (0.12.2)
25
27
  multipart-post (>= 1.2, < 3)
26
- fluentd (0.14.18)
28
+ fluentd (0.14.20)
27
29
  cool.io (>= 1.4.5, < 2.0.0)
28
30
  http_parser.rb (>= 0.5.1, < 0.7.0)
29
31
  msgpack (>= 0.7.0, < 2.0.0)
32
+ ruby_dig (~> 0.0.2)
30
33
  serverengine (>= 2.0.4, < 3.0.0)
31
34
  sigdump (~> 0.2.2)
32
35
  strptime (~> 0.1.7)
@@ -44,20 +47,17 @@ GEM
44
47
  retriable (~> 2.0)
45
48
  google-cloud-core (0.21.1)
46
49
  googleauth (~> 0.5.1)
47
- google-cloud-logging (0.23.2)
50
+ google-cloud-logging (0.24.1)
48
51
  google-cloud-core (~> 0.21.1)
49
- google-gax (~> 0.6.0)
50
- google-protobuf (~> 3.0)
51
- googleapis-common-protos (~> 1.3)
52
- grpc (~> 1.0)
53
- orderedhash (= 0.0.6)
52
+ google-gax (~> 0.8.0)
54
53
  stackdriver-core (~> 0.21.0)
55
- google-gax (0.6.0)
56
- googleapis-common-protos (~> 1.3.1)
54
+ google-gax (0.8.5)
55
+ google-protobuf (~> 3.2)
56
+ googleapis-common-protos (~> 1.3.5)
57
57
  googleauth (~> 0.5.1)
58
58
  grpc (~> 1.0)
59
59
  rly (~> 0.2.3)
60
- google-protobuf (3.3.0)
60
+ google-protobuf (3.3.0-x86_64-linux)
61
61
  googleapis-common-protos (1.3.5)
62
62
  google-protobuf (~> 3.2)
63
63
  grpc (~> 1.0)
@@ -69,10 +69,10 @@ GEM
69
69
  multi_json (~> 1.11)
70
70
  os (~> 0.9)
71
71
  signet (~> 0.7)
72
- grpc (1.2.5)
72
+ grpc (1.2.5-x86_64-linux)
73
73
  google-protobuf (~> 3.1)
74
74
  googleauth (~> 0.5.1)
75
- hashdiff (0.3.4)
75
+ hashdiff (0.3.5)
76
76
  http_parser.rb (0.6.0)
77
77
  httpclient (2.8.3)
78
78
  hurley (0.2)
@@ -92,7 +92,6 @@ GEM
92
92
  msgpack (1.1.0)
93
93
  multi_json (1.12.1)
94
94
  multipart-post (2.0.0)
95
- orderedhash (0.0.6)
96
95
  os (0.9.6)
97
96
  parser (2.4.0.0)
98
97
  ast (~> 2.2)
@@ -117,6 +116,7 @@ GEM
117
116
  ruby-progressbar (~> 1.7)
118
117
  tins (<= 1.6.0)
119
118
  ruby-progressbar (1.8.1)
119
+ ruby_dig (0.0.2)
120
120
  safe_yaml (1.0.4)
121
121
  serverengine (2.0.5)
122
122
  sigdump (~> 0.2.2)
@@ -137,7 +137,7 @@ GEM
137
137
  tzinfo-data (1.2017.2)
138
138
  tzinfo (>= 1.0.0)
139
139
  uber (0.0.15)
140
- webmock (1.24.6)
140
+ webmock (2.3.2)
141
141
  addressable (>= 2.3.6)
142
142
  crack (>= 0.3.2)
143
143
  hashdiff
@@ -153,7 +153,7 @@ DEPENDENCIES
153
153
  rake (~> 10.3)
154
154
  rubocop (~> 0.35.0)
155
155
  test-unit (~> 3.0)
156
- webmock (~> 1.17)
156
+ webmock (~> 2.3.1)
157
157
 
158
158
  BUNDLED WITH
159
- 1.15.0
159
+ 1.15.3
@@ -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.4'
13
+ gem.version = '0.6.5.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')
@@ -19,18 +19,19 @@ eos
19
19
  gem.test_files = gem.files.grep(/^(test)/)
20
20
  gem.require_paths = ['lib']
21
21
 
22
+ gem.add_runtime_dependency 'excon', '~> 0.57.1'
22
23
  gem.add_runtime_dependency 'fluentd', '~> 0.10'
23
24
  gem.add_runtime_dependency 'googleapis-common-protos', '~> 1.3'
24
25
  gem.add_runtime_dependency 'google-api-client', '~> 0.9.0'
25
- gem.add_runtime_dependency 'google-cloud-logging', '~> 0.23.2'
26
- gem.add_runtime_dependency 'googleauth', '~> 0.4'
26
+ gem.add_runtime_dependency 'google-cloud-logging', '0.24.1'
27
+ gem.add_runtime_dependency 'googleauth', '~> 0.4', '< 0.5.2'
27
28
  gem.add_runtime_dependency 'grpc', '~> 1.0', '< 1.3'
28
29
  gem.add_runtime_dependency 'json', '~> 1.8'
29
30
 
30
31
  gem.add_development_dependency 'mocha', '~> 1.1'
31
32
  gem.add_development_dependency 'rake', '~> 10.3'
32
33
  gem.add_development_dependency 'rubocop', '~> 0.35.0'
33
- gem.add_development_dependency 'webmock', '~> 1.17'
34
+ gem.add_development_dependency 'webmock', '~> 2.3.1'
34
35
  gem.add_development_dependency 'test-unit', '~> 3.0'
35
36
  gem.add_development_dependency 'prometheus-client', '~> 0.7.1'
36
37
  end
@@ -11,9 +11,11 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ require 'excon'
14
15
  require 'grpc'
15
16
  require 'json'
16
17
  require 'open-uri'
18
+ require 'rubygems'
17
19
  require 'socket'
18
20
  require 'time'
19
21
  require 'yaml'
@@ -38,8 +40,10 @@ end
38
40
  module Fluent
39
41
  # fluentd output plugin for the Stackdriver Logging API
40
42
  class GoogleCloudOutput < BufferedOutput
41
- # Constants for service names and resource types.
43
+ # Constants.
42
44
  module Constants
45
+ # Service names and resource types.
46
+
43
47
  APPENGINE_CONSTANTS = {
44
48
  service: 'appengine.googleapis.com',
45
49
  resource_type: 'gae_app'
@@ -56,6 +60,10 @@ module Fluent
56
60
  service: 'container.googleapis.com',
57
61
  resource_type: 'container'
58
62
  }
63
+ DOCKER_CONSTANTS = {
64
+ service: 'dockercontainer.googleapis.com',
65
+ resource_type: 'docker_container'
66
+ }
59
67
  DATAFLOW_CONSTANTS = {
60
68
  service: 'dataflow.googleapis.com',
61
69
  resource_type: 'dataflow_step'
@@ -72,6 +80,23 @@ module Fluent
72
80
  service: 'ml.googleapis.com',
73
81
  resource_type: 'ml_job'
74
82
  }
83
+
84
+ # Default value for trace_key config param to set "trace" LogEntry field.
85
+ DEFAULT_TRACE_KEY = 'logging.googleapis.com/trace'
86
+
87
+ # Metadata Agent support.
88
+
89
+ # Use empty string as request path when locally-unique key of monitored
90
+ # resource can be implicitly inferred by Metadata Agent.
91
+ IMPLICIT_MONITORED_RESOURCE_UNIQUE_KEY = ''
92
+
93
+ # The label name of locally unique id in the json payload. When a record
94
+ # has this field in the payload, we will use the value to retrieve
95
+ # monitored resource from Stackdriver Metadata agent.
96
+ LOCALLY_UNIQUE_ID_LABEL_NAME = 'logging.googleapis.com/locally_unique_id'
97
+
98
+ # Docker container support.
99
+ DEFAULT_DOCKER_API_SOCKET_PATH = '/var/run/docker.sock'
75
100
  end
76
101
 
77
102
  include self::Constants
@@ -79,7 +104,7 @@ module Fluent
79
104
  Fluent::Plugin.register_output('google_cloud', self)
80
105
 
81
106
  PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
82
- PLUGIN_VERSION = '0.6.3'
107
+ PLUGIN_VERSION = '0.6.5.pre.1'
83
108
 
84
109
  # Name of the the Google cloud logging write scope.
85
110
  LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
@@ -109,6 +134,11 @@ module Fluent
109
134
  config_param :vm_id, :string, :default => nil
110
135
  config_param :vm_name, :string, :default => nil
111
136
 
137
+ # Set values from JSON payload with this key to the "trace" LogEntry field.
138
+ config_param :trace_key, :string, :default => DEFAULT_TRACE_KEY
139
+ # Whether to also keep the trace key/value in the payload.
140
+ config_param :keep_trace_key, :bool, :default => false
141
+
112
142
  # Whether to try to detect if the VM is owned by a "subservice" such as App
113
143
  # Engine of Kubernetes, rather than just associating the logs with the
114
144
  # compute service of the platform. This currently only has any effect when
@@ -126,7 +156,7 @@ module Fluent
126
156
  config_param :require_valid_tags, :bool, :default => false
127
157
 
128
158
  # The regular expression to use on Kubernetes logs to extract some basic
129
- # information about the log source. The regex must contain capture groups
159
+ # information about the log source. The regexp must contain capture groups
130
160
  # for pod_name, namespace_name, and container_name.
131
161
  config_param :kubernetes_tag_regexp, :string, :default =>
132
162
  '\.(?<pod_name>[^_]+)_(?<namespace_name>[^_]+)_(?<container_name>.+)$'
@@ -201,6 +231,18 @@ module Fluent
201
231
  config_param :monitoring_type, :string,
202
232
  :default => Monitoring::PrometheusMonitoringRegistry.name
203
233
 
234
+ # Whether to call metadata agent to retrieve monitored resource.
235
+ config_param :enable_metadata_agent, :bool, :default => false
236
+ config_param :metadata_agent_url, :string,
237
+ :default => 'http://local-metadata-agent.stackdriver.com:8000'
238
+
239
+ # Whether to call Docker Remote API locally when Metadata Agent is not
240
+ # enabled or if the request fails.
241
+ config_param :call_docker_api_locally, :bool, :default => true
242
+ # Docker Remote API unix socket path.
243
+ config_param :docker_remote_api_socket_path, :string,
244
+ :default => DEFAULT_DOCKER_API_SOCKET_PATH
245
+
204
246
  # rubocop:enable Style/HashSyntax
205
247
 
206
248
  # TODO: Add a log_name config option rather than just using the tag?
@@ -210,9 +252,6 @@ module Fluent
210
252
  attr_reader :project_id
211
253
  attr_reader :zone
212
254
  attr_reader :vm_id
213
- attr_reader :running_on_managed_vm
214
- attr_reader :gae_backend_name
215
- attr_reader :gae_backend_version
216
255
  attr_reader :resource
217
256
  attr_reader :common_labels
218
257
 
@@ -222,6 +261,35 @@ module Fluent
222
261
  @log = $log # rubocop:disable Style/GlobalVars
223
262
  end
224
263
 
264
+ # Set up regex patterns used to parse tags and logs.
265
+ def setup_regex_patterns
266
+ @compiled_kubernetes_tag_regexp = nil
267
+ if @kubernetes_tag_regexp
268
+ @compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
269
+ end
270
+
271
+ @cloudfunctions_tag_regexp =
272
+ /\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
273
+ @cloudfunctions_log_regexp = /^
274
+ (?:\[(?<severity>.)\])?
275
+ \[(?<timestamp>.{24})\]
276
+ (?:\[(?<execution_id>[^\]]+)\])?
277
+ [ ](?<text>.*)$/x
278
+
279
+ # Docker container tag format:
280
+ # "container.<container_id>.<container_name>".
281
+ @dockercontainer_tag_regexp =
282
+ /^container\.(?<container_id>[a-zA-Z0-9]+)\.
283
+ (?<container_name>[a-zA-Z0-9_.-]+)$/x
284
+ # Docker container with application tag format:
285
+ # "application-container.<container_name>.<additional_tag>".
286
+ @dockercontainer_tag_with_application_regexp =
287
+ /^application-container\.(?<container_name>[a-zA-Z0-9_.-]+)\.
288
+ (?<additional_tag>.+)$/x
289
+
290
+ @http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
291
+ end
292
+
225
293
  def configure(conf)
226
294
  super
227
295
 
@@ -259,162 +327,54 @@ module Fluent
259
327
  extra.join(' ')
260
328
  end
261
329
 
262
- # TODO: Send instance tags as labels as well?
263
- @common_labels = {}
264
- @common_labels.merge!(@labels) if @labels
265
-
266
- # TODO: Construct Google::Api::MonitoredResource when @use_grpc is
267
- # true after the protobuf map corruption issue is fixed.
268
- @resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
269
- labels: {})
270
-
271
- @compiled_kubernetes_tag_regexp = nil
272
- if @kubernetes_tag_regexp
273
- @compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
274
- end
330
+ setup_regex_patterns
275
331
 
276
- @cloudfunctions_tag_regexp =
277
- /\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
278
- @cloudfunctions_log_regexp = /^
279
- (?:\[(?<severity>.)\])?
280
- \[(?<timestamp>.{24})\]
281
- (?:\[(?<execution_id>[^\]]+)\])?
282
- [ ](?<text>.*)$/x
283
-
284
- @http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
285
-
286
- # set attributes from metadata (unless overriden by static config)
287
- @vm_name = Socket.gethostname if @vm_name.nil?
288
332
  @platform = detect_platform
289
- case @platform
290
- when Platform::GCE
291
- if @project_id.nil?
292
- @project_id = fetch_gce_metadata('project/project-id')
293
- end
294
- if @zone.nil?
295
- # this returns "projects/<number>/zones/<zone>"; we only want
296
- # the part after the final slash.
297
- fully_qualified_zone = fetch_gce_metadata('instance/zone')
298
- @zone = fully_qualified_zone.rpartition('/')[2]
299
- end
300
- @vm_id = fetch_gce_metadata('instance/id') if @vm_id.nil?
301
- when Platform::EC2
302
- metadata = fetch_ec2_metadata
303
- if @zone.nil? && metadata.key?('availabilityZone')
304
- @zone = 'aws:' + metadata['availabilityZone']
305
- end
306
- if @vm_id.nil? && metadata.key?('instanceId')
307
- @vm_id = metadata['instanceId']
308
- end
309
- if metadata.key?('accountId')
310
- @resource.labels['aws_account'] = metadata['accountId']
311
- end
312
- when Platform::OTHER
313
- # do nothing
314
- else
315
- fail Fluent::ConfigError, 'Unknown platform ' + @platform
316
- end
317
333
 
318
- # If we still don't have a project ID, try to obtain it from the
319
- # credentials.
320
- if @project_id.nil?
321
- @project_id = CredentialsInfo.project_id
322
- @log.info 'Set Project ID from credentials: ', @project_id unless
323
- @project_id.nil?
334
+ # Set agent-level monitored resource. This monitored resource is initiated
335
+ # as the logging agent starts up. It will be inherited by all log entries
336
+ # processed by this agent. First try to retrieve it via Metadata Agent.
337
+ if @enable_metadata_agent
338
+ # The locally-unique key for this should be the instance id. Since this
339
+ # can be implicitly inferred by Metadata Agent, we do not need to
340
+ # explicitly send the key.
341
+ @resource = call_metadata_agent_for_monitored_resource(
342
+ IMPLICIT_MONITORED_RESOURCE_UNIQUE_KEY)
324
343
  end
325
344
 
326
- # all metadata parameters must now be set
327
- unless @project_id && @zone && @vm_id
328
- missing = []
329
- missing << 'project_id' unless @project_id
330
- missing << 'zone' unless @zone
331
- missing << 'vm_id' unless @vm_id
332
- fail Fluent::ConfigError, 'Unable to obtain metadata parameters: ' +
333
- missing.join(' ')
334
- end
335
-
336
- # Default this to false; it is only overwritten if we detect Managed VM.
337
- @running_on_managed_vm = false
338
-
339
- # Default this to false; it is only overwritten if we detect Cloud
340
- # Functions.
345
+ # Set required variables: @project_id, @vm_id, @vm_name and @zone.
346
+ # If any info above is included in the response from Metadata Agent, make
347
+ # use of that. Otherwise make some additional requests to metadata server.
348
+ #
349
+ # Note: Once we support metadata injection on the Logging API side, we
350
+ # might no longer need to require all these metadata in logging agent. But
351
+ # for now, they are still required.
352
+ set_required_metadata_variables
353
+
354
+ # Fail over to retrieve monitored resource via the legacy path if we fail
355
+ # to get it from Metadata Agent.
356
+ @resource ||= determine_agent_level_monitored_resource_via_legacy
357
+
358
+ # Set variables specific to CLoud Functions. This has to be called after
359
+ # we have determined the resource type. The purpose is to avoid repeated
360
+ # calls to metadata server.
341
361
  @running_cloudfunctions = false
342
-
343
- # Set up the MonitoredResource, labels, etc. based on the config.
344
- case @platform
345
- when Platform::GCE
346
- @resource.type = COMPUTE_CONSTANTS[:resource_type]
347
- # TODO: introduce a new MonitoredResource-centric configuration and
348
- # deprecate subservice-name; for now, translate known uses.
349
- if @subservice_name
350
- # TODO: what should we do if we encounter an unknown value?
351
- if @subservice_name == DATAFLOW_CONSTANTS[:service]
352
- @resource.type = DATAFLOW_CONSTANTS[:resource_type]
353
- elsif @subservice_name == ML_CONSTANTS[:service]
354
- @resource.type = ML_CONSTANTS[:resource_type]
355
- end
356
- elsif @detect_subservice
357
- # Check for specialized GCE environments.
358
- # TODO: Add config options for these to allow for running outside GCE?
359
- attributes = fetch_gce_metadata('instance/attributes/').split
360
- # Do nothing, just don't populate other service's labels.
361
- if attributes.include?('gae_backend_name') &&
362
- attributes.include?('gae_backend_version')
363
- # Managed VM
364
- @running_on_managed_vm = true
365
- @gae_backend_name =
366
- fetch_gce_metadata('instance/attributes/gae_backend_name')
367
- @gae_backend_version =
368
- fetch_gce_metadata('instance/attributes/gae_backend_version')
369
- @resource.type = APPENGINE_CONSTANTS[:resource_type]
370
- @resource.labels['module_id'] = @gae_backend_name
371
- @resource.labels['version_id'] = @gae_backend_version
372
- elsif attributes.include?('kube-env')
373
- # Kubernetes/Container Engine
374
- @resource.type = CONTAINER_CONSTANTS[:resource_type]
375
- @raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
376
- @kube_env = YAML.load(@raw_kube_env)
377
- @resource.labels['cluster_name'] =
378
- cluster_name_from_kube_env(@kube_env)
379
- detect_cloudfunctions(attributes)
380
- elsif attributes.include?('dataproc-cluster-uuid') &&
381
- attributes.include?('dataproc-cluster-name')
382
- # Dataproc
383
- @resource.type = DATAPROC_CONSTANTS[:resource_type]
384
- @resource.labels['cluster_uuid'] =
385
- fetch_gce_metadata('instance/attributes/dataproc-cluster-uuid')
386
- @resource.labels['cluster_name'] =
387
- fetch_gce_metadata('instance/attributes/dataproc-cluster-name')
388
- @resource.labels['region'] =
389
- fetch_gce_metadata('instance/attributes/dataproc-region')
390
- end
391
- end
392
- # Some services have the GCE instance_id and zone as MonitoredResource
393
- # labels; for other services we send them as entry labels.
394
- if @resource.type == COMPUTE_CONSTANTS[:resource_type] ||
395
- @resource.type == CONTAINER_CONSTANTS[:resource_type]
396
- @resource.labels['instance_id'] = @vm_id
397
- @resource.labels['zone'] = @zone
398
- else
399
- common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
400
- common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
401
- end
402
- common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
403
- when Platform::EC2
404
- @resource.type = EC2_CONSTANTS[:resource_type]
405
- @resource.labels['instance_id'] = @vm_id
406
- @resource.labels['region'] = @zone
407
- # the aws_account label is populated above.
408
- common_labels["#{EC2_CONSTANTS[:service]}/resource_name"] = @vm_name
409
- when Platform::OTHER
410
- # Use GCE as the default environment.
411
- @resource.type = COMPUTE_CONSTANTS[:resource_type]
412
- @resource.labels['instance_id'] = @vm_id
413
- @resource.labels['zone'] = @zone
414
- common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
362
+ # We only support Cloud Functions logs for GKE right now.
363
+ if @resource.type == CONTAINER_CONSTANTS[:resource_type] &&
364
+ fetch_gce_metadata('instance/attributes/').split.include?('gcf_region')
365
+ # We are not setting resource type as Cloud Functions here because
366
+ # whether a log entry is truly coming from a Cloud Functions function
367
+ # depends on the log tag. Only when @running_cloudfunctions is true will
368
+ # we try to match log tags against Cloud Functions tag regexp when
369
+ # processing log entries.
370
+ @running_cloudfunctions = true
371
+ # Fetch this info and store it to avoid recurring metadata server calls.
372
+ @gcf_region = fetch_gce_metadata('instance/attributes/gcf_region')
415
373
  end
416
- @resource.labels.merge!(
417
- extract_resource_labels(@resource.type, common_labels))
374
+
375
+ # Determine the common labels that should be added to all log entries
376
+ # processed by this logging agent.
377
+ @common_labels = determine_agent_level_common_labels
418
378
 
419
379
  # The resource and labels are now set up; ensure they can't be modified
420
380
  # without first duping them.
@@ -424,7 +384,7 @@ module Fluent
424
384
 
425
385
  # Log an informational message containing the Logs viewer URL
426
386
  @log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
427
- "viewer?project=#{@project_id}&resource=#{@resource_type}/",
387
+ "viewer?project=#{@project_id}&resource=#{@resource.type}/",
428
388
  "instance_id/#{@vm_id}"
429
389
  end
430
390
 
@@ -439,130 +399,6 @@ module Fluent
439
399
  super
440
400
  end
441
401
 
442
- def format(tag, time, record)
443
- [tag, time, record].to_msgpack
444
- end
445
-
446
- # Given a tag, returns the corresponding valid tag if possible, or nil if
447
- # the tag should be rejected. If 'require_valid_tags' is false, non-string
448
- # tags are converted to strings, and invalid characters are sanitized;
449
- # otherwise such tags are rejected.
450
- def sanitize_tag(tag)
451
- if @require_valid_tags &&
452
- (!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
453
- return nil
454
- end
455
- tag = convert_to_utf8(tag.to_s)
456
- tag = '_' if tag == ''
457
- tag
458
- end
459
-
460
- # Compute the monitored resource and common labels shared by a collection of
461
- # entries.
462
- def compute_group_resource_and_labels(tag)
463
- # Note that we assume that labels added to group_common_labels below are
464
- # not 'service' labels (i.e. we do not call extract_resource_labels
465
- # again).
466
- group_resource = @resource.dup
467
- group_common_labels = @common_labels.dup
468
-
469
- if @running_cloudfunctions
470
- # If the current group of entries is coming from a Cloud Functions
471
- # function, the function name can be extracted from the tag.
472
- match_data = @cloudfunctions_tag_regexp.match(tag)
473
- if match_data
474
- # Resource type is set to Cloud Functions only for logs actually
475
- # coming from a function, otherwise we leave it as Container.
476
- group_resource.type = CLOUDFUNCTIONS_CONSTANTS[:resource_type]
477
- group_resource.labels['region'] = @gcf_region
478
- group_resource.labels['function_name'] =
479
- decode_cloudfunctions_function_name(
480
- match_data['encoded_function_name'])
481
- # Move GKE container labels from the MonitoredResource to the
482
- # LogEntry.
483
- instance_id = group_resource.labels.delete('instance_id')
484
- group_common_labels["#{CONTAINER_CONSTANTS[:service]}/cluster_name"] =
485
- group_resource.labels.delete('cluster_name')
486
- group_common_labels["#{CONTAINER_CONSTANTS[:service]}/instance_id"] =
487
- instance_id
488
- group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
489
- instance_id
490
- group_common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] =
491
- group_resource.labels.delete('zone')
492
- end
493
- end
494
- if group_resource.type == CONTAINER_CONSTANTS[:resource_type] &&
495
- @compiled_kubernetes_tag_regexp
496
- # Container logs in Kubernetes are tagged based on where they came
497
- # from, so we can extract useful metadata from the tag.
498
- # Do this here to avoid having to repeat it for each record.
499
- match_data = @compiled_kubernetes_tag_regexp.match(tag)
500
- if match_data
501
- group_resource.labels['container_name'] = match_data['container_name']
502
- group_resource.labels['namespace_id'] = match_data['namespace_name']
503
- group_resource.labels['pod_id'] = match_data['pod_name']
504
- %w(namespace_name pod_name).each do |field|
505
- group_common_labels["#{CONTAINER_CONSTANTS[:service]}/#{field}"] =
506
- match_data[field]
507
- end
508
- end
509
- end
510
-
511
- # Freeze the per-request state. Any further changes must be made on a
512
- # per-entry basis.
513
- group_resource.freeze
514
- group_resource.labels.freeze
515
- group_common_labels.freeze
516
-
517
- [group_resource, group_common_labels]
518
- end
519
-
520
- # Extract entry resource and common labels that should be applied to
521
- # individual entries from the group resource.
522
- def extract_entry_labels(group_resource, record)
523
- resource_labels = {}
524
- common_labels = {}
525
-
526
- if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
527
- record.key?('log')
528
- @cloudfunctions_log_match =
529
- @cloudfunctions_log_regexp.match(record['log'])
530
- end
531
-
532
- if group_resource.type == CONTAINER_CONSTANTS[:resource_type]
533
- # Move the stdout/stderr annotation from the record into a label
534
- common_labels.merge!(
535
- fields_to_labels(
536
- record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
537
-
538
- # If the record has been annotated by the kubernetes_metadata_filter
539
- # plugin, then use that metadata. Otherwise, rely on commonLabels
540
- # populated at the grouped_entries level from the group's tag.
541
- if record.key?('kubernetes')
542
- extracted_resource_labels, extracted_common_labels = \
543
- extract_container_metadata(record)
544
- resource_labels.merge!(extracted_resource_labels)
545
- common_labels.merge!(extracted_common_labels)
546
- end
547
- end
548
-
549
- # If a field is present in the label_map, send its value as a label
550
- # (mapping the field name to label name as specified in the config)
551
- # and do not send that field as part of the payload.
552
- common_labels.merge!(fields_to_labels(record, @label_map))
553
-
554
- if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
555
- @cloudfunctions_log_match &&
556
- @cloudfunctions_log_match['execution_id']
557
- common_labels['execution_id'] =
558
- @cloudfunctions_log_match['execution_id']
559
- end
560
- resource_labels.merge!(
561
- extract_resource_labels(group_resource.type, common_labels))
562
-
563
- [resource_labels, common_labels]
564
- end
565
-
566
402
  def write(chunk)
567
403
  # Group the entries since we have to make one call per tag.
568
404
  grouped_entries = {}
@@ -579,20 +415,22 @@ module Fluent
579
415
 
580
416
  grouped_entries.each do |tag, arr|
581
417
  entries = []
582
- group_resource, group_common_labels = compute_group_resource_and_labels(
583
- tag)
418
+ group_resource, group_common_labels =
419
+ determine_group_level_monitored_resource_and_labels(tag)
584
420
 
585
421
  arr.each do |time, record|
586
422
  next unless record.is_a?(Hash)
587
423
 
588
- extracted_resource_labels, extracted_common_labels = \
589
- extract_entry_labels(group_resource, record)
424
+ resource_type, extracted_resource_labels, extracted_common_labels = \
425
+ determine_entry_level_labels(group_resource, record)
590
426
  entry_resource = group_resource.dup
427
+ entry_resource.type = resource_type
591
428
  entry_resource.labels.merge!(extracted_resource_labels)
592
429
  entry_common_labels = \
593
430
  group_common_labels.merge(extracted_common_labels)
594
431
 
595
- if entry_resource.type == CONTAINER_CONSTANTS[:resource_type]
432
+ if [CONTAINER_CONSTANTS[:resource_type],
433
+ DOCKER_CONSTANTS[:resource_type]].include?(entry_resource.type)
596
434
  # Save the timestamp if available, then clear it out to allow for
597
435
  # determining whether we should parse the log or message field.
598
436
  timestamp = record.key?('time') ? record['time'] : nil
@@ -621,6 +459,13 @@ module Fluent
621
459
  severity = compute_severity(
622
460
  entry_resource.type, record, entry_common_labels)
623
461
 
462
+ # Get fully-qualified trace id for LogEntry "trace" field per config.
463
+ fq_trace_id = if @keep_trace_key
464
+ record[@trace_key]
465
+ else
466
+ record.delete(@trace_key)
467
+ end
468
+
624
469
  if @use_grpc
625
470
  entry = Google::Logging::V2::LogEntry.new(
626
471
  labels: entry_common_labels,
@@ -630,6 +475,7 @@ module Fluent
630
475
  ),
631
476
  severity: grpc_severity(severity)
632
477
  )
478
+ entry.trace = fq_trace_id if fq_trace_id
633
479
  # If "seconds" is null or not an integer, we will omit the timestamp
634
480
  # field and defer the decision on how to handle it to the downstream
635
481
  # Logging API. If "nanos" is null or not an integer, it will be set
@@ -655,6 +501,7 @@ module Fluent
655
501
  nanos: ts_nanos
656
502
  }
657
503
  )
504
+ entry.trace = fq_trace_id if fq_trace_id
658
505
  set_http_request(record, entry)
659
506
  set_payload(entry_resource.type, record, entry, is_json)
660
507
  end
@@ -841,7 +688,7 @@ module Fluent
841
688
  end
842
689
  end
843
690
  rescue StandardError => e
844
- @log.debug 'Failed to access metadata service: ', error: e
691
+ @log.error 'Failed to access metadata service: ', error: e
845
692
  end
846
693
 
847
694
  @log.info 'Unable to determine platform'
@@ -856,15 +703,630 @@ module Fluent
856
703
  metadata_path, 'Metadata-Flavor' => 'Google', &:read)
857
704
  end
858
705
 
859
- def fetch_ec2_metadata
860
- fail "Called fetch_ec2_metadata with platform=#{@platform}" unless
706
+ # EC2 Metadata server returns everything in one call. Store it after the
707
+ # first fetch to avoid making multiple calls.
708
+ def ec2_metadata
709
+ fail "Called ec2_metadata with platform=#{@platform}" unless
861
710
  @platform == Platform::EC2
862
- # See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
863
- open('http://' + METADATA_SERVICE_ADDR +
864
- '/latest/dynamic/instance-identity/document') do |f|
865
- contents = f.read
866
- return JSON.parse(contents)
711
+ unless @ec2_metadata
712
+ # See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
713
+ open('http://' + METADATA_SERVICE_ADDR +
714
+ '/latest/dynamic/instance-identity/document') do |f|
715
+ contents = f.read
716
+ @ec2_metadata = JSON.parse(contents)
717
+ end
718
+ end
719
+
720
+ @ec2_metadata
721
+ end
722
+
723
+ # Set regexp patterns to parse tags and logs.
724
+ def set_regexp_patterns
725
+ @compiled_kubernetes_tag_regexp = nil
726
+ if @kubernetes_tag_regexp
727
+ @compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
867
728
  end
729
+
730
+ @cloudfunctions_tag_regexp =
731
+ /\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
732
+ @cloudfunctions_log_regexp = /^
733
+ (?:\[(?<severity>.)\])?
734
+ \[(?<timestamp>.{24})\]
735
+ (?:\[(?<execution_id>[^\]]+)\])?
736
+ [ ](?<text>.*)$/x
737
+
738
+ @http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
739
+ end
740
+
741
+ # Set required variables like @project_id, @vm_id, @vm_name and @zone.
742
+ def set_required_metadata_variables
743
+ set_project_id
744
+ set_vm_id
745
+ set_vm_name
746
+ set_location
747
+
748
+ # All metadata parameters must now be set.
749
+ return if @project_id && @zone && @vm_id
750
+ missing = []
751
+ missing << 'project_id' unless @project_id
752
+ missing << 'zone' unless @zone
753
+ missing << 'vm_id' unless @vm_id
754
+ fail Fluent::ConfigError, 'Unable to obtain metadata parameters: ' +
755
+ missing.join(' ')
756
+ end
757
+
758
+ # 1. Return the value if it is explicitly set in the config already.
759
+ # 2. If not, try to retrieve it by calling metadata server directly.
760
+ # 3. If still not set, try to obtain it from the credentials.
761
+ def set_project_id
762
+ @project_id ||= fetch_gce_metadata('project/project-id') if
763
+ @platform == Platform::GCE
764
+ @project_id ||= CredentialsInfo.project_id
765
+ rescue StandardError => e
766
+ @log.error 'Failed to obtain project id: ', error: e
767
+ end
768
+
769
+ # 1. Return the value if it is explicitly set in the config already.
770
+ # 2. If not, check if the response from Metadata Agent includes this info.
771
+ # 3. If not, try to retrieve it by calling metadata servers directly.
772
+ def set_vm_id
773
+ @vm_id ||= @resource.labels['instance_id'] if
774
+ !@resource.nil? && @resource.labels.key?('instance_id')
775
+ @vm_id ||= fetch_gce_metadata('instance/id') if @platform == Platform::GCE
776
+ @vm_id ||= ec2_metadata['instanceId'] if @platform == Platform::EC2
777
+ rescue StandardError => e
778
+ @log.error 'Failed to obtain vm_id: ', error: e
779
+ end
780
+
781
+ # 1. Return the value if it is explicitly set in the config already.
782
+ # 2. If not, check if the response from Metadata Agent includes this info.
783
+ # 3. If not, try to retrieve it locally.
784
+ def set_vm_name
785
+ @vm_name ||= @resource.labels['instance_name'] if
786
+ !@resource.nil? && @resource.labels.key?('instance_name')
787
+ @vm_name ||= Socket.gethostname
788
+ rescue StandardError => e
789
+ @log.error 'Failed to obtain vm name: ', error: e
790
+ end
791
+
792
+ # 1. Return the value if it is explicitly set in the config already.
793
+ # 2. If not, check if the response from Metadata Agent includes this info.
794
+ # 3. If not, try to retrieve it locally.
795
+ def set_location
796
+ unless @resource.nil?
797
+ @zone ||= @resource.labels['location'] if
798
+ @resource.type == DOCKER_CONSTANTS[:resource_type] &&
799
+ @resource.labels.key?('location')
800
+ @zone ||= @resource.labels['zone'] if
801
+ @platform == Platform::GCE && @resource.labels.key?('zone')
802
+ @zone ||= @resource.labels['region'] if
803
+ @platform == Platform::EC2 && @resource.labels.key?('region')
804
+ end
805
+ # Response format: "projects/<number>/zones/<zone>"
806
+ @zone ||= fetch_gce_metadata('instance/zone').rpartition('/')[2] if
807
+ @platform == Platform::GCE
808
+ @zone ||= 'aws:' + ec2_metadata['availabilityZone'] if
809
+ @platform == Platform::EC2 && ec2_metadata.key?('availabilityZone')
810
+ rescue StandardError => e
811
+ @log.error 'Failed to obtain location: ', error: e
812
+ end
813
+
814
+ # Retrieve monitored resource via the legacy way.
815
+ #
816
+ # Note: This is just a failover plan if we fail to get metadata from
817
+ # Metadata Agent. Thus it should be equivalent to what Metadata Agent
818
+ # returns.
819
+ def determine_agent_level_monitored_resource_via_legacy
820
+ resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
821
+ labels: {})
822
+ resource.type = determine_agent_level_monitored_resource_type
823
+ resource.labels = determine_agent_level_monitored_resource_labels(
824
+ resource.type)
825
+ resource
826
+ end
827
+
828
+ # Determine agent level monitored resource type.
829
+ def determine_agent_level_monitored_resource_type
830
+ # EC2 instance.
831
+ return EC2_CONSTANTS[:resource_type] if
832
+ @platform == Platform::EC2
833
+
834
+ # Unknown platform will be defaulted to GCE instance..
835
+ return COMPUTE_CONSTANTS[:resource_type] if
836
+ @platform == Platform::OTHER
837
+
838
+ # Resource types determined by @subservice_name config.
839
+ # Cloud Dataflow.
840
+ return DATAFLOW_CONSTANTS[:resource_type] if
841
+ @subservice_name == DATAFLOW_CONSTANTS[:service]
842
+ # Cloud ML.
843
+ return ML_CONSTANTS[:resource_type] if
844
+ @subservice_name == ML_CONSTANTS[:service]
845
+ # Default back to GCE if invalid value is detected.
846
+ return COMPUTE_CONSTANTS[:resource_type] if
847
+ @subservice_name
848
+
849
+ # Resource types determined by @detect_subservice config.
850
+ if @detect_subservice
851
+ begin
852
+ attributes = fetch_gce_metadata('instance/attributes/').split
853
+ rescue StandardError => e
854
+ @log.error 'Failed to detect subservice: ', error: e
855
+ end
856
+ # GAE app.
857
+ return APPENGINE_CONSTANTS[:resource_type] if
858
+ attributes.include?('gae_backend_name') &&
859
+ attributes.include?('gae_backend_version')
860
+ # GKE container.
861
+ return CONTAINER_CONSTANTS[:resource_type] if
862
+ attributes.include?('kube-env')
863
+ # Cloud Dataproc.
864
+ return DATAPROC_CONSTANTS[:resource_type] if
865
+ attributes.include?('dataproc-cluster-uuid') &&
866
+ attributes.include?('dataproc-cluster-name')
867
+ end
868
+ # GCE instance.
869
+ COMPUTE_CONSTANTS[:resource_type]
870
+ end
871
+
872
+ # Determine agent level monitored resource labels based on the resource
873
+ # type. Each resource type has its own labels that need to be filled in.
874
+ def determine_agent_level_monitored_resource_labels(type)
875
+ labels = {}
876
+
877
+ case type
878
+
879
+ # GAE app.
880
+ when APPENGINE_CONSTANTS[:resource_type]
881
+ begin
882
+ labels['module_id'] = fetch_gce_metadata(
883
+ 'instance/attributes/gae_backend_name')
884
+ labels['version_id'] = fetch_gce_metadata(
885
+ 'instance/attributes/gae_backend_version')
886
+ rescue StandardError => e
887
+ @log.error 'Failed to set monitored resource labels for GAE: ',
888
+ error: e
889
+ end
890
+
891
+ # GCE.
892
+ when COMPUTE_CONSTANTS[:resource_type]
893
+ labels['instance_id'] = @vm_id
894
+ labels['zone'] = @zone
895
+
896
+ # GKE container.
897
+ when CONTAINER_CONSTANTS[:resource_type]
898
+ labels['instance_id'] = @vm_id
899
+ labels['zone'] = @zone
900
+ begin
901
+ raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
902
+ kube_env = YAML.load(raw_kube_env)
903
+ labels['cluster_name'] =
904
+ cluster_name_from_kube_env(kube_env)
905
+ rescue StandardError => e
906
+ @log.error 'Failed to set monitored resource labels for GKE: ',
907
+ error: e
908
+ end
909
+
910
+ # Cloud Dataproc.
911
+ when DATAPROC_CONSTANTS[:resource_type]
912
+ begin
913
+ labels['cluster_uuid'] =
914
+ fetch_gce_metadata('instance/attributes/dataproc-cluster-uuid')
915
+ labels['cluster_name'] =
916
+ fetch_gce_metadata('instance/attributes/dataproc-cluster-name')
917
+ labels['region'] =
918
+ fetch_gce_metadata('instance/attributes/dataproc-region')
919
+ rescue StandardError => e
920
+ @log.error 'Failed to set monitored resource labels for Cloud ' \
921
+ 'Dataproc: ', error: e
922
+ end
923
+
924
+ # EC2.
925
+ when EC2_CONSTANTS[:resource_type]
926
+ labels['instance_id'] = @vm_id
927
+ labels['region'] = @zone
928
+ labels['aws_account'] = ec2_metadata['accountId'] if
929
+ ec2_metadata.key?('accountId')
930
+ end
931
+ labels
932
+ end
933
+
934
+ # Determine the common labels that should be added to all log entries
935
+ # processed by this logging agent.
936
+ def determine_agent_level_common_labels
937
+ labels = {}
938
+ # User can specify labels via config. We want to capture those as well.
939
+ # TODO: Send instance tags as labels as well?
940
+ labels.merge!(@labels) if @labels
941
+
942
+ case @resource.type
943
+
944
+ # GAE app.
945
+ when APPENGINE_CONSTANTS[:resource_type]
946
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
947
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
948
+ labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
949
+
950
+ # GCE and GKE container.
951
+ when COMPUTE_CONSTANTS[:resource_type],
952
+ CONTAINER_CONSTANTS[:resource_type]
953
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
954
+
955
+ # Cloud Dataflow and Cloud Dataproc.
956
+ when DATAFLOW_CONSTANTS[:resource_type],
957
+ DATAPROC_CONSTANTS[:resource_type]
958
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
959
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
960
+ labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
961
+
962
+ # EC2.
963
+ when EC2_CONSTANTS[:resource_type]
964
+ labels["#{EC2_CONSTANTS[:service]}/resource_name"] = @vm_name
965
+
966
+ # Cloud ML.
967
+ when ML_CONSTANTS[:resource_type]
968
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
969
+ labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
970
+ labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
971
+ end
972
+ labels
973
+ end
974
+
975
+ # Determine the group level monitored resource and common labels shared by a
976
+ # collection of entries.
977
+ def determine_group_level_monitored_resource_and_labels(tag)
978
+ # Determine group level monitored resource type. For certain types,
979
+ # extract useful info from the tag and store those in
980
+ # matched_regexp_group.
981
+ group_resource_type, matched_regexp_group =
982
+ determine_group_level_monitored_resource_type(tag)
983
+
984
+ # Determine group level monitored resource labels and common labels.
985
+ group_resource_type, group_resource_labels, group_common_labels = \
986
+ determine_group_level_labels_and_adjust_type(
987
+ group_resource_type, matched_regexp_group)
988
+
989
+ group_resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
990
+ type: group_resource_type,
991
+ labels: group_resource_labels.to_h
992
+ )
993
+
994
+ # Freeze the per-request state. Any further changes must be made on a
995
+ # per-entry basis.
996
+ group_resource.freeze
997
+ group_resource.labels.freeze
998
+ group_common_labels.freeze
999
+
1000
+ [group_resource, group_common_labels]
1001
+ end
1002
+
1003
+ # Determine group level monitored resource type shared by a collection of
1004
+ # entries.
1005
+ # Returns the resource type and tag regexp matched groups. The matched
1006
+ # groups only apply to some resource types. Return nil if not applicable or
1007
+ # if there is no match.
1008
+ def determine_group_level_monitored_resource_type(tag)
1009
+ # Match tag against Cloud Functions format.
1010
+ if @running_cloudfunctions
1011
+ matched_regexp_group = @cloudfunctions_tag_regexp.match(tag)
1012
+ return [CLOUDFUNCTIONS_CONSTANTS[:resource_type],
1013
+ matched_regexp_group] if matched_regexp_group
1014
+ end
1015
+
1016
+ # Match tag against Docker container stderr / stdout log format and
1017
+ # Docker container application log format.
1018
+ matched_regexp_group =
1019
+ # Format: "container.<container_id>.<container_name>"
1020
+ @dockercontainer_tag_regexp.match(tag) ||
1021
+ # Format: "application-container.<container_name>.<additional_tag>"
1022
+ @dockercontainer_tag_with_application_regexp.match(tag)
1023
+ return [DOCKER_CONSTANTS[:resource_type], matched_regexp_group] if
1024
+ matched_regexp_group
1025
+
1026
+ # Match tag against GKE Container format.
1027
+ if @resource.type == CONTAINER_CONSTANTS[:resource_type] &&
1028
+ @compiled_kubernetes_tag_regexp
1029
+ # Container logs in Kubernetes are tagged based on where they came from,
1030
+ # so we can extract useful metadata from the tag. Do this here to avoid
1031
+ # having to repeat it for each record.
1032
+ matched_regexp_group = @compiled_kubernetes_tag_regexp.match(tag)
1033
+ return [@resource.type, matched_regexp_group] if matched_regexp_group
1034
+ end
1035
+
1036
+ # Otherwise, return the original type.
1037
+ [@resource.type, nil]
1038
+ end
1039
+
1040
+ # Determine group level monitored resource labels and common labels. These
1041
+ # labels will be shared by a collection of entries. In certain cases, we
1042
+ # might also adjust the resource type.
1043
+ def determine_group_level_labels_and_adjust_type(group_resource_type,
1044
+ matched_regexp_group)
1045
+ group_resource_labels = @resource.labels.dup
1046
+ group_common_labels = @common_labels.dup
1047
+
1048
+ case group_resource_type
1049
+
1050
+ # Cloud Functions.
1051
+ when CLOUDFUNCTIONS_CONSTANTS[:resource_type]
1052
+ group_resource_labels['region'] = @gcf_region
1053
+ group_resource_labels['function_name'] =
1054
+ decode_cloudfunctions_function_name(
1055
+ matched_regexp_group['encoded_function_name'])
1056
+ instance_id = group_resource_labels.delete('instance_id')
1057
+ group_common_labels["#{CONTAINER_CONSTANTS[:service]}/cluster_name"] =
1058
+ group_resource_labels.delete('cluster_name')
1059
+ group_common_labels["#{CONTAINER_CONSTANTS[:service]}/instance_id"] =
1060
+ instance_id
1061
+ group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
1062
+ instance_id
1063
+ group_common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] =
1064
+ group_resource_labels.delete('zone')
1065
+
1066
+ # GKE container.
1067
+ when CONTAINER_CONSTANTS[:resource_type]
1068
+ if matched_regexp_group
1069
+ group_resource_labels['container_name'] =
1070
+ matched_regexp_group['container_name']
1071
+ # The kubernetes_tag_regexp is poorly named. 'namespace_name' is in
1072
+ # fact 'namespace_id'. 'pod_name' is in fact 'pod_id'.
1073
+ group_resource_labels['namespace_id'] =
1074
+ matched_regexp_group['namespace_name']
1075
+ group_resource_labels['pod_id'] =
1076
+ matched_regexp_group['pod_name']
1077
+ %w(namespace_name pod_name).each do |field|
1078
+ group_common_labels["#{CONTAINER_CONSTANTS[:service]}/#{field}"] =
1079
+ matched_regexp_group[field]
1080
+ end
1081
+ end
1082
+
1083
+ # Docker container.
1084
+ when DOCKER_CONSTANTS[:resource_type]
1085
+ # For Docker container stderr / stdout logs generated by Docker Fluentd
1086
+ # Logging Driver, tags are in the format of "container.<container_id>.
1087
+ # <container_name>", thus they include 'container_id' info.
1088
+ # For logs generated by applications running in Docker containers,
1089
+ # tags are in the format of "application-container.<container_name>.
1090
+ # <additional_tag>", thus 'container_id' info is unknown yet.
1091
+ # 'container_name' info on the other hand is always available.
1092
+ container_id = matched_regexp_group['container_id'] if
1093
+ matched_regexp_group.names.include? 'container_id'
1094
+ container_name = matched_regexp_group['container_name']
1095
+
1096
+ if @enable_metadata_agent
1097
+ # Call Metadata Agent with "container.<container_id>" or
1098
+ # "application-container.<container_name>" as the locally-unique key
1099
+ # to retrieve monitored resource. This should be different from the
1100
+ # original @resource value that got initiated when the agent starts up
1101
+ # because that one is always at the VM level.
1102
+ if container_id
1103
+ locally_unique_id = "container.#{container_id}"
1104
+ else
1105
+ locally_unique_id = "containerName.#{container_name}"
1106
+ end
1107
+ retrieved_resource = call_metadata_agent_for_monitored_resource(
1108
+ locally_unique_id)
1109
+ end
1110
+
1111
+ if !retrieved_resource.nil?
1112
+ # If we successfully get a monitored resource from Metadata Agent,
1113
+ # use this one instead of the original instance monitored resource.
1114
+ group_resource_labels = retrieved_resource.labels.dup
1115
+ @log.debug 'Retrieved monitored resource from Metadata Agent: ' \
1116
+ "#{retrieved_resource.inspect}."
1117
+ else
1118
+ # If Metadata Agent is not enabled, or we failed to get a monitored
1119
+ # resource, we need to have some backup plan.
1120
+ @log.debug 'Metadata Agent not enabled or failed to retrieve ' \
1121
+ 'docker container monitored resource from Metadata ' \
1122
+ 'Agent.'
1123
+
1124
+ # 1. Check if 'container_id' is set already. It should be available
1125
+ # for stdout / stderr). If so, use that.
1126
+ # 2. If not, call Docker Remote API to retrieve the container ID from
1127
+ # container name, but only if @call_docker_api_locally is true.
1128
+ container_id ||= retrieve_container_id_by_name_locally(
1129
+ container_name) if @call_docker_api_locally
1130
+ unless container_id
1131
+ @log.debug 'No docker container id retrieved. Falling back to
1132
+ instance monitored resource.'
1133
+ # If a container id is not available, fall back to the instance
1134
+ # monitored resource.
1135
+ return [COMPUTE_CONSTANTS[:resource_type], group_resource_labels,
1136
+ group_common_labels]
1137
+ end
1138
+ group_resource_labels['container_id'] = container_id
1139
+ # 'zone' for GCP and 'region' for EC2 must have been set at this
1140
+ # point. Rename them to 'location'.
1141
+ group_resource_labels['location'] = @zone
1142
+ if @platform == Platform::EC2
1143
+ group_resource_labels.delete('region')
1144
+ else
1145
+ group_resource_labels.delete('zone')
1146
+ end
1147
+ # vm id info should be reported as a metadata label instead.
1148
+ group_resource_labels.delete('instance_id')
1149
+
1150
+ end
1151
+ # Set metadata labels.
1152
+ group_common_labels["#{DOCKER_CONSTANTS[:service]}/container_name"] =
1153
+ matched_regexp_group['container_name']
1154
+ group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
1155
+ @vm_id
1156
+ end
1157
+
1158
+ [group_resource_type, group_resource_labels, group_common_labels]
1159
+ end
1160
+
1161
+ # Extract entry resource and common labels that should be applied to
1162
+ # individual entries from the group resource.
1163
+ def determine_entry_level_labels(group_resource, record)
1164
+ resource_type = group_resource.type
1165
+ resource_labels = {}
1166
+ common_labels = {}
1167
+
1168
+ # The format of the locally unique key varies by monitored resource.
1169
+ #
1170
+ # Docker container:
1171
+ # "container.<container_id>"
1172
+ # "containerName.<container_name>"
1173
+ # GKE container:
1174
+ # "gke_containerName.<namespace_id>.<pod_name>.<container_name>"
1175
+ if @enable_metadata_agent && record.key?(LOCALLY_UNIQUE_ID_LABEL_NAME)
1176
+ locally_unique_id = record.delete(LOCALLY_UNIQUE_ID_LABEL_NAME)
1177
+ @log.debug 'Calling metadata agent with locally unique id: ' \
1178
+ "#{locally_unique_id}."
1179
+ retrieved_resource = call_metadata_agent_for_monitored_resource(
1180
+ locally_unique_id)
1181
+ @log.debug 'Retrieved monitored resource from metadata agent: ' \
1182
+ "#{retrieved_resource.inspect}."
1183
+ unless retrieved_resource.nil?
1184
+ resource_type = retrieved_resource.type
1185
+ # Temporarily renaming 'gke_container' to 'container'.
1186
+ resource_type = 'container' if resource_type == 'gke_container'
1187
+ # If we successfully get a monitored resource from Metadata Agent,
1188
+ # use this one instead of the original VM-level monitored resource.
1189
+ resource_labels = retrieved_resource.labels.dup
1190
+ @log.debug 'Retrieved gke_container monitored resource from' \
1191
+ 'Stackdriver Metadata agent: ' \
1192
+ "#{retrieved_resource.inspect}."
1193
+ end
1194
+ end
1195
+
1196
+ # Cloud Functions.
1197
+ if resource_type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
1198
+ record.key?('log')
1199
+ @cloudfunctions_log_match =
1200
+ @cloudfunctions_log_regexp.match(record['log'])
1201
+ common_labels['execution_id'] =
1202
+ @cloudfunctions_log_match['execution_id'] if \
1203
+ @cloudfunctions_log_match &&
1204
+ @cloudfunctions_log_match['execution_id']
1205
+ end
1206
+
1207
+ # GKE containers.
1208
+ if resource_type == CONTAINER_CONSTANTS[:resource_type]
1209
+ # Move the stdout/stderr annotation from the record into a label.
1210
+ common_labels.merge!(
1211
+ delete_and_extract_labels(
1212
+ record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
1213
+
1214
+ # If the record has been annotated by the kubernetes_metadata_filter
1215
+ # plugin, then use that metadata. Otherwise, rely on commonLabels
1216
+ # populated at the grouped_entries level from the group's tag.
1217
+ if record.key?('kubernetes')
1218
+ resource_labels.merge!(
1219
+ delete_and_extract_labels(
1220
+ record['kubernetes'], %w(namespace_id pod_id container_name)
1221
+ .map { |l| [l, l] }.to_h))
1222
+ common_labels.merge!(
1223
+ delete_and_extract_labels(
1224
+ record['kubernetes'], %w(namespace_name pod_name)
1225
+ .map { |l| [l, "#{CONTAINER_CONSTANTS[:service]}/#{l}"] }.to_h))
1226
+ # Prepend label/ to all user-defined labels' keys.
1227
+ if record['kubernetes'].key?('labels')
1228
+ record['kubernetes']['labels'].each do |key, value|
1229
+ common_labels["label/#{key}"] = value
1230
+ end
1231
+ end
1232
+ # We've explicitly consumed all the fields we care about -- don't
1233
+ # litter the log entries with the remaining fields that the kubernetes
1234
+ # metadata filter plugin includes (or an empty 'kubernetes' field).
1235
+ record.delete('kubernetes')
1236
+ record.delete('docker')
1237
+ end
1238
+ end
1239
+
1240
+ # Docker containers.
1241
+ if resource_type == DOCKER_CONSTANTS[:resource_type]
1242
+ # For logs coming from Docker Fluentd Logging Driver, the log record
1243
+ # has 4 fields: 'container_id', 'container_name', 'source' and 'log'.
1244
+ # Extract 'container_id', 'container_name' and 'source' from json
1245
+ # record, set corresponding labels, and remove these fields from record.
1246
+ {
1247
+ 'container_name' => 'container_name',
1248
+ 'source' => 'stream'
1249
+ }.each do |field_name, label_name|
1250
+ common_labels.merge!(
1251
+ delete_and_extract_labels(
1252
+ record,
1253
+ field_name => "#{DOCKER_CONSTANTS[:service]}/#{label_name}"
1254
+ )
1255
+ )
1256
+ end
1257
+ resource_labels.merge!(
1258
+ delete_and_extract_labels(record, 'container_id' => 'container_id'))
1259
+ end
1260
+
1261
+ # If the name of a field in the record is present in the @label_map
1262
+ # configured by users, report its value as a label and do not send that
1263
+ # field as part of the payload.
1264
+ common_labels.merge!(delete_and_extract_labels(record, @label_map))
1265
+
1266
+ # Cloud Dataflow.
1267
+ # These labels can be set via configuring 'labels' or 'label_map'.
1268
+ # Report them as monitored resource labels instead of common labels.
1269
+ if group_resource.type == DATAFLOW_CONSTANTS[:resource_type]
1270
+ resource_labels.merge!(
1271
+ delete_and_extract_labels(
1272
+ common_labels, %w(region job_name job_id step_id)
1273
+ .map { |l| ["#{DATAFLOW_CONSTANTS[:service]}/#{l}", l] }.to_h))
1274
+ end
1275
+
1276
+ # Cloud ML.
1277
+ # These labels can be set via configuring 'labels' or 'label_map'.
1278
+ # Report them as monitored resource labels instead of common labels.
1279
+ if group_resource.type == ML_CONSTANTS[:resource_type]
1280
+ resource_labels.merge!(
1281
+ delete_and_extract_labels(
1282
+ common_labels, %w(job_id task_name)
1283
+ .map { |l| ["#{ML_CONSTANTS[:service]}/#{l}", l] }.to_h))
1284
+ end
1285
+
1286
+ [resource_type, resource_labels, common_labels]
1287
+ end
1288
+
1289
+ # Call Metadata Agent to get monitored resource information and parse
1290
+ # response to Google::Api::MonitoredResource.
1291
+ def call_metadata_agent_for_monitored_resource(unique_key)
1292
+ response = call_metadata_agent("monitoredResource/#{unique_key}")
1293
+ return nil if response.nil?
1294
+ begin
1295
+ resource = Google::Api::MonitoredResource.decode_json(response.to_json)
1296
+ rescue Google::Protobuf::ParseError, ArgumentError => e
1297
+ @log.error 'Error paring monitored resource from Metadata Agent. ' \
1298
+ "response: #{response.inspect}", error: e
1299
+ return nil
1300
+ end
1301
+
1302
+ # TODO(qingling128): Use Google::Api::MonitoredResource directly after we
1303
+ # upgrade gRPC version to include the fix for the protobuf map
1304
+ # corruption issue.
1305
+ Google::Apis::LoggingV2beta1::MonitoredResource.new(
1306
+ type: resource.type,
1307
+ labels: resource.labels.to_h
1308
+ )
1309
+ end
1310
+
1311
+ # Call Metadata Agent and parse response to json. Return nil in case of any
1312
+ # error / failure.
1313
+ def call_metadata_agent(path)
1314
+ url = "#{@metadata_agent_url}/#{path}"
1315
+ @log.debug("Calling Metadata Agent: #{url}")
1316
+ open(url) do |f|
1317
+ response = f.read
1318
+ parsed_hash = parse_json_or_nil(response)
1319
+ if parsed_hash.nil?
1320
+ @log.error 'Response from Metadata Agent is not in valid json ' \
1321
+ "format: '#{response.inspect}'."
1322
+ return nil
1323
+ end
1324
+ @log.debug "Response from Metadata Agent: #{parsed_hash}"
1325
+ return parsed_hash
1326
+ end
1327
+ rescue StandardError => e
1328
+ @log.error 'Error calling Metadata Agent.', error: e
1329
+ return nil
868
1330
  end
869
1331
 
870
1332
  # TODO: This functionality should eventually be available in another
@@ -902,11 +1364,28 @@ module Fluent
902
1364
  end
903
1365
  end
904
1366
 
905
- def detect_cloudfunctions(attributes)
906
- return unless attributes.include?('gcf_region')
907
- # Cloud Functions detected
908
- @running_cloudfunctions = true
909
- @gcf_region = fetch_gce_metadata('instance/attributes/gcf_region')
1367
+ # Calling Docker Remote API to get container id by name.
1368
+ def retrieve_container_id_by_name_locally(container_name)
1369
+ response = Excon.get(
1370
+ "unix:///containers/#{container_name}/json",
1371
+ socket: @docker_remote_api_socket_path)
1372
+ @log.debug "Response from Docker API with name '#{container_name}': " \
1373
+ "#{response.inspect}."
1374
+ return parse_container_id_from_docker_api_response(response)
1375
+ rescue StandardError => e
1376
+ @log.error 'Error calling Docker API to get container id.', error: e
1377
+ return nil
1378
+ end
1379
+
1380
+ # Parse the container id from Docker Remote API response.
1381
+ # TODO(qingling128) Add a config for Docker API version to support parsing
1382
+ # different versions of Docker Remote API when the format varies.
1383
+ def parse_container_id_from_docker_api_response(response)
1384
+ JSON.parse(response.data[:body])['Id']
1385
+ rescue StandardError => e
1386
+ @log.error 'Error parsing Docker API response to get container id.',
1387
+ error: e
1388
+ return nil
910
1389
  end
911
1390
 
912
1391
  def cluster_name_from_kube_env(kube_env)
@@ -979,9 +1458,14 @@ module Fluent
979
1458
  end
980
1459
  elsif record.key?('severity')
981
1460
  return parse_severity(record.delete('severity'))
982
- elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
983
- entry_common_labels.key?("#{CONTAINER_CONSTANTS[:service]}/stream")
984
- stream = entry_common_labels["#{CONTAINER_CONSTANTS[:service]}/stream"]
1461
+ elsif [CONTAINER_CONSTANTS[:resource_type],
1462
+ DOCKER_CONSTANTS[:resource_type]].include?(resource_type)
1463
+ stream = entry_common_labels[
1464
+ "#{CONTAINER_CONSTANTS[:service]}/stream"] if
1465
+ resource_type == CONTAINER_CONSTANTS[:resource_type]
1466
+ stream = entry_common_labels[
1467
+ "#{DOCKER_CONSTANTS[:service]}/stream"] if
1468
+ resource_type == DOCKER_CONSTANTS[:resource_type]
985
1469
  if stream == 'stdout'
986
1470
  return 'INFO'
987
1471
  elsif stream == 'stderr'
@@ -1161,43 +1645,35 @@ module Fluent
1161
1645
  .gsub('u.u', '_').gsub('d.d', '$').gsub('a.a', '@').gsub('p.p', '.')
1162
1646
  end
1163
1647
 
1164
- # Requires that record has a 'kubernetes' field.
1165
- def extract_container_metadata(record)
1166
- resource_labels = {}
1167
- common_labels = {}
1168
- %w(namespace_id pod_id container_name).each do |field|
1169
- resource_labels.merge!(
1170
- fields_to_labels(record['kubernetes'], field => field))
1171
- end
1172
- %w(namespace_name pod_name).each do |field|
1173
- common_labels.merge!(
1174
- fields_to_labels(
1175
- record['kubernetes'],
1176
- field => "#{CONTAINER_CONSTANTS[:service]}/#{field}"))
1177
- end
1178
- # Prepend label/ to all user-defined labels' keys.
1179
- if record['kubernetes'].key?('labels')
1180
- record['kubernetes']['labels'].each do |key, value|
1181
- common_labels["label/#{key}"] = value
1182
- end
1648
+ def format(tag, time, record)
1649
+ [tag, time, record].to_msgpack
1650
+ end
1651
+
1652
+ # Given a tag, returns the corresponding valid tag if possible, or nil if
1653
+ # the tag should be rejected. If 'require_valid_tags' is false, non-string
1654
+ # tags are converted to strings, and invalid characters are sanitized;
1655
+ # otherwise such tags are rejected.
1656
+ def sanitize_tag(tag)
1657
+ if @require_valid_tags &&
1658
+ (!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
1659
+ return nil
1183
1660
  end
1184
- # We've explicitly consumed all the fields we care about -- don't litter
1185
- # the log entries with the remaining fields that the kubernetes metadata
1186
- # filter plugin includes (or an empty 'kubernetes' field).
1187
- record.delete('kubernetes')
1188
- record.delete('docker')
1189
- [resource_labels, common_labels]
1661
+ tag = convert_to_utf8(tag.to_s)
1662
+ tag = '_' if tag == ''
1663
+ tag
1190
1664
  end
1191
1665
 
1192
1666
  # For every original_label => new_label pair in the label_map, delete the
1193
- # original_label from the record if it exists, and extract the value to form
1194
- # a map with the new_label as the key.
1195
- def fields_to_labels(record, label_map)
1196
- return {} if label_map.nil? || !label_map.is_a?(Hash)
1667
+ # original_label from the original_resource map if it exists, and extract
1668
+ # the value to form a map with the new_label as the key.
1669
+ def delete_and_extract_labels(original_resource, label_map)
1670
+ return {} if label_map.nil? || !label_map.is_a?(Hash) ||
1671
+ original_resource.nil? || !original_resource.is_a?(Hash)
1197
1672
  label_map.each_with_object({}) \
1198
1673
  do |(original_label, new_label), extracted_labels|
1199
1674
  extracted_labels[new_label] = convert_to_utf8(
1200
- record.delete(original_label).to_s) if record.key?(original_label)
1675
+ original_resource.delete(original_label).to_s) if
1676
+ original_resource.key?(original_label)
1201
1677
  end
1202
1678
  end
1203
1679
 
@@ -1216,7 +1692,8 @@ module Fluent
1216
1692
  entry.text_payload = record['log']
1217
1693
  elsif is_json
1218
1694
  entry.json_payload = record
1219
- elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
1695
+ elsif [CONTAINER_CONSTANTS[:resource_type],
1696
+ DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
1220
1697
  record.key?('log')
1221
1698
  entry.text_payload = record['log']
1222
1699
  elsif record.size == 1 && record.key?('message')
@@ -1286,7 +1763,8 @@ module Fluent
1286
1763
  entry.text_payload = convert_to_utf8(record['log'])
1287
1764
  elsif is_json
1288
1765
  entry.json_payload = struct_from_ruby(record)
1289
- elsif resource_type == CONTAINER_CONSTANTS[:resource_type] &&
1766
+ elsif [CONTAINER_CONSTANTS[:resource_type],
1767
+ DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
1290
1768
  record.key?('log')
1291
1769
  entry.text_payload = convert_to_utf8(record['log'])
1292
1770
  elsif record.size == 1 && record.key?('message')
@@ -1299,7 +1777,7 @@ module Fluent
1299
1777
  def log_name(tag, resource)
1300
1778
  if resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
1301
1779
  tag = 'cloud-functions'
1302
- elsif @running_on_managed_vm
1780
+ elsif resource.type == APPENGINE_CONSTANTS[:resource_type]
1303
1781
  # Add a prefix to Managed VM logs to prevent namespace collisions.
1304
1782
  tag = "#{APPENGINE_CONSTANTS[:service]}/#{tag}"
1305
1783
  elsif resource.type == CONTAINER_CONSTANTS[:resource_type]
@@ -1314,32 +1792,6 @@ module Fluent
1314
1792
  tag
1315
1793
  end
1316
1794
 
1317
- # Some services set labels (via configuring 'labels' or 'label_map') which
1318
- # are now MonitoredResource labels in v2.
1319
- # For these services, remove resource labels from 'labels' and return a
1320
- # Hash of labels to be merged into the MonitoredResource labels.
1321
- # Otherwise, return an empty hash and leave 'labels' unmodified.
1322
- def extract_resource_labels(resource_type, labels)
1323
- extracted_labels = {}
1324
- return extracted_labels if labels.nil? || !labels.is_a?(Hash)
1325
-
1326
- if resource_type == DATAFLOW_CONSTANTS[:resource_type]
1327
- label_prefix = DATAFLOW_CONSTANTS[:service]
1328
- labels_to_extract = %w(region job_name job_id step_id)
1329
- elsif resource_type == ML_CONSTANTS[:resource_type]
1330
- label_prefix = ML_CONSTANTS[:service]
1331
- labels_to_extract = %w(job_id task_name)
1332
- else
1333
- return extracted_labels
1334
- end
1335
-
1336
- labels_to_extract.each do |label|
1337
- extracted_labels[label] = labels.delete("#{label_prefix}/#{label}") if
1338
- labels.key?("#{label_prefix}/#{label}")
1339
- end
1340
- extracted_labels
1341
- end
1342
-
1343
1795
  def init_api_client
1344
1796
  return if @use_grpc
1345
1797
  # TODO: Use a non-default ClientOptions object.