fluent-plugin-google-cloud 0.6.3 → 0.6.4.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 +8 -6
- data/fluent-plugin-google-cloud.gemspec +2 -1
- data/lib/fluent/plugin/out_google_cloud.rb +733 -290
- data/test/plugin/base_test.rb +316 -77
- data/test/plugin/constants.rb +143 -0
- metadata +19 -6
- data/fluent-plugin-google-cloud-0.6.2.gem +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8b2b5d197dc8da098e69040d3c1e33cf1d13390
|
4
|
+
data.tar.gz: d01388ebf1188dcd27061bdc3466a65be62856e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6720df5c417423746b423fd34cebbcb3d417cdbc1cdfe91307970841661cf8d18edd987e0f622680bcc51b4503b5e3458c3029774362396eea769f2a2378dc5
|
7
|
+
data.tar.gz: 0ebf298b06bc970370d4a7640096f2fd14a2646339c9db6e93f29dacbf640140e52d1016c89cc63811b8248c314e945e54f8e47088b58a4384cd6082da233d59
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fluent-plugin-google-cloud (0.6.
|
4
|
+
fluent-plugin-google-cloud (0.6.4.pre.1)
|
5
|
+
excon (~> 0.56.0)
|
5
6
|
fluentd (~> 0.10)
|
6
7
|
google-api-client (~> 0.9.0)
|
7
8
|
google-cloud-logging (~> 0.23.2)
|
@@ -21,9 +22,10 @@ GEM
|
|
21
22
|
cool.io (1.5.0)
|
22
23
|
crack (0.4.3)
|
23
24
|
safe_yaml (~> 1.0.0)
|
25
|
+
excon (0.56.0)
|
24
26
|
faraday (0.12.1)
|
25
27
|
multipart-post (>= 1.2, < 3)
|
26
|
-
fluentd (0.14.
|
28
|
+
fluentd (0.14.18)
|
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)
|
@@ -57,7 +59,7 @@ GEM
|
|
57
59
|
googleauth (~> 0.5.1)
|
58
60
|
grpc (~> 1.0)
|
59
61
|
rly (~> 0.2.3)
|
60
|
-
google-protobuf (3.3.0
|
62
|
+
google-protobuf (3.3.0)
|
61
63
|
googleapis-common-protos (1.3.5)
|
62
64
|
google-protobuf (~> 3.2)
|
63
65
|
grpc (~> 1.0)
|
@@ -69,7 +71,7 @@ GEM
|
|
69
71
|
multi_json (~> 1.11)
|
70
72
|
os (~> 0.9)
|
71
73
|
signet (~> 0.7)
|
72
|
-
grpc (1.2.5
|
74
|
+
grpc (1.2.5)
|
73
75
|
google-protobuf (~> 3.1)
|
74
76
|
googleauth (~> 0.5.1)
|
75
77
|
hashdiff (0.3.4)
|
@@ -82,7 +84,7 @@ GEM
|
|
82
84
|
logging (2.2.2)
|
83
85
|
little-plugger (~> 1.1)
|
84
86
|
multi_json (~> 1.10)
|
85
|
-
memoist (0.
|
87
|
+
memoist (0.16.0)
|
86
88
|
metaclass (0.0.4)
|
87
89
|
mime-types (3.1)
|
88
90
|
mime-types-data (~> 3.2015)
|
@@ -152,4 +154,4 @@ DEPENDENCIES
|
|
152
154
|
webmock (~> 1.17)
|
153
155
|
|
154
156
|
BUNDLED WITH
|
155
|
-
1.15.
|
157
|
+
1.15.1
|
@@ -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.4.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,6 +19,7 @@ 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.56.0'
|
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'
|
@@ -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'
|
@@ -36,8 +38,10 @@ end
|
|
36
38
|
module Fluent
|
37
39
|
# fluentd output plugin for the Stackdriver Logging API
|
38
40
|
class GoogleCloudOutput < BufferedOutput
|
39
|
-
# Constants
|
41
|
+
# Constants.
|
40
42
|
module Constants
|
43
|
+
# Service names and resource types.
|
44
|
+
|
41
45
|
APPENGINE_CONSTANTS = {
|
42
46
|
service: 'appengine.googleapis.com',
|
43
47
|
resource_type: 'gae_app'
|
@@ -54,10 +58,18 @@ module Fluent
|
|
54
58
|
service: 'container.googleapis.com',
|
55
59
|
resource_type: 'container'
|
56
60
|
}
|
61
|
+
DOCKER_CONSTANTS = {
|
62
|
+
service: 'dockercontainer.googleapis.com',
|
63
|
+
resource_type: 'docker_container'
|
64
|
+
}
|
57
65
|
DATAFLOW_CONSTANTS = {
|
58
66
|
service: 'dataflow.googleapis.com',
|
59
67
|
resource_type: 'dataflow_step'
|
60
68
|
}
|
69
|
+
DATAPROC_CONSTANTS = {
|
70
|
+
service: 'cluster.dataproc.googleapis.com',
|
71
|
+
resource_type: 'cloud_dataproc_cluster'
|
72
|
+
}
|
61
73
|
EC2_CONSTANTS = {
|
62
74
|
service: 'ec2.amazonaws.com',
|
63
75
|
resource_type: 'aws_ec2_instance'
|
@@ -66,6 +78,15 @@ module Fluent
|
|
66
78
|
service: 'ml.googleapis.com',
|
67
79
|
resource_type: 'ml_job'
|
68
80
|
}
|
81
|
+
|
82
|
+
# Metadata agent support.
|
83
|
+
|
84
|
+
# Use empty string as request path when locally-unique key of monitored
|
85
|
+
# resource can be implicitly inferred by Metadata Agent.
|
86
|
+
IMPLICIT_MONITORED_RESOURCE_UNIQUE_KEY = ''
|
87
|
+
|
88
|
+
# Docker container support.
|
89
|
+
DEFAULT_DOCKER_API_SOCKET_PATH = '/var/run/docker.sock'
|
69
90
|
end
|
70
91
|
|
71
92
|
include self::Constants
|
@@ -73,7 +94,7 @@ module Fluent
|
|
73
94
|
Fluent::Plugin.register_output('google_cloud', self)
|
74
95
|
|
75
96
|
PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
|
76
|
-
PLUGIN_VERSION = '0.6.
|
97
|
+
PLUGIN_VERSION = '0.6.4.pre.1'
|
77
98
|
|
78
99
|
# Name of the the Google cloud logging write scope.
|
79
100
|
LOGGING_SCOPE = 'https://www.googleapis.com/auth/logging.write'
|
@@ -120,7 +141,7 @@ module Fluent
|
|
120
141
|
config_param :require_valid_tags, :bool, :default => false
|
121
142
|
|
122
143
|
# The regular expression to use on Kubernetes logs to extract some basic
|
123
|
-
# information about the log source. The
|
144
|
+
# information about the log source. The regexp must contain capture groups
|
124
145
|
# for pod_name, namespace_name, and container_name.
|
125
146
|
config_param :kubernetes_tag_regexp, :string, :default =>
|
126
147
|
'\.(?<pod_name>[^_]+)_(?<namespace_name>[^_]+)_(?<container_name>.+)$'
|
@@ -182,6 +203,18 @@ module Fluent
|
|
182
203
|
:default => nil,
|
183
204
|
:secret => true
|
184
205
|
|
206
|
+
# Whether to call metadata agent to retrieve monitored resource.
|
207
|
+
config_param :enable_metadata_agent, :bool, :default => false
|
208
|
+
config_param :metadata_agent_url, :string,
|
209
|
+
:default => 'http://local-metadata-agent.stackdriver.com:8000'
|
210
|
+
|
211
|
+
# Whether to call Docker Remote API locally when Metadata Agent is not
|
212
|
+
# enabled or if the request fails.
|
213
|
+
config_param :call_docker_api_locally, :bool, :default => true
|
214
|
+
# Docker Remote API unix socket path.
|
215
|
+
config_param :docker_remote_api_socket_path, :string,
|
216
|
+
:default => '/var/run/docker.sock'
|
217
|
+
|
185
218
|
# rubocop:enable Style/HashSyntax
|
186
219
|
|
187
220
|
# TODO: Add a log_name config option rather than just using the tag?
|
@@ -191,9 +224,6 @@ module Fluent
|
|
191
224
|
attr_reader :project_id
|
192
225
|
attr_reader :zone
|
193
226
|
attr_reader :vm_id
|
194
|
-
attr_reader :running_on_managed_vm
|
195
|
-
attr_reader :gae_backend_name
|
196
|
-
attr_reader :gae_backend_version
|
197
227
|
attr_reader :resource
|
198
228
|
attr_reader :common_labels
|
199
229
|
|
@@ -203,6 +233,35 @@ module Fluent
|
|
203
233
|
@log = $log # rubocop:disable Style/GlobalVars
|
204
234
|
end
|
205
235
|
|
236
|
+
# Set up regex patterns used to parse tags and logs.
|
237
|
+
def setup_regex_patterns
|
238
|
+
@compiled_kubernetes_tag_regexp = nil
|
239
|
+
if @kubernetes_tag_regexp
|
240
|
+
@compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
|
241
|
+
end
|
242
|
+
|
243
|
+
@cloudfunctions_tag_regexp =
|
244
|
+
/\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
|
245
|
+
@cloudfunctions_log_regexp = /^
|
246
|
+
(?:\[(?<severity>.)\])?
|
247
|
+
\[(?<timestamp>.{24})\]
|
248
|
+
(?:\[(?<execution_id>[^\]]+)\])?
|
249
|
+
[ ](?<text>.*)$/x
|
250
|
+
|
251
|
+
# Docker container tag format:
|
252
|
+
# "container.<container_id>.<container_name>".
|
253
|
+
@dockercontainer_tag_regexp =
|
254
|
+
/^container\.(?<container_id>[a-zA-Z0-9]+)\.
|
255
|
+
(?<container_name>[a-zA-Z0-9_.-]+)$/x
|
256
|
+
# Docker container with application tag format:
|
257
|
+
# "application-container.<container_name>.<additional_tag>".
|
258
|
+
@dockercontainer_tag_with_application_regexp =
|
259
|
+
/^application-container\.(?<container_name>[a-zA-Z0-9_.-]+)\.
|
260
|
+
(?<additional_tag>.+)$/x
|
261
|
+
|
262
|
+
@http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
|
263
|
+
end
|
264
|
+
|
206
265
|
def configure(conf)
|
207
266
|
super
|
208
267
|
|
@@ -221,150 +280,57 @@ module Fluent
|
|
221
280
|
extra.join(' ')
|
222
281
|
end
|
223
282
|
|
224
|
-
|
225
|
-
@common_labels = {}
|
226
|
-
@common_labels.merge!(@labels) if @labels
|
227
|
-
|
228
|
-
# TODO: Construct Google::Api::MonitoredResource when @use_grpc is
|
229
|
-
# true after the protobuf map corruption issue is fixed.
|
230
|
-
@resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
231
|
-
labels: {})
|
232
|
-
|
233
|
-
@compiled_kubernetes_tag_regexp = nil
|
234
|
-
if @kubernetes_tag_regexp
|
235
|
-
@compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
|
236
|
-
end
|
237
|
-
|
238
|
-
@cloudfunctions_tag_regexp =
|
239
|
-
/\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
|
240
|
-
@cloudfunctions_log_regexp = /^
|
241
|
-
(?:\[(?<severity>.)\])?
|
242
|
-
\[(?<timestamp>.{24})\]
|
243
|
-
(?:\[(?<execution_id>[^\]]+)\])?
|
244
|
-
[ ](?<text>.*)$/x
|
245
|
-
|
246
|
-
@http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
|
283
|
+
setup_regex_patterns
|
247
284
|
|
248
|
-
# set attributes from metadata (unless overriden by static config)
|
249
|
-
@vm_name = Socket.gethostname if @vm_name.nil?
|
250
285
|
@platform = detect_platform
|
251
|
-
case @platform
|
252
|
-
when Platform::GCE
|
253
|
-
if @project_id.nil?
|
254
|
-
@project_id = fetch_gce_metadata('project/project-id')
|
255
|
-
end
|
256
|
-
if @zone.nil?
|
257
|
-
# this returns "projects/<number>/zones/<zone>"; we only want
|
258
|
-
# the part after the final slash.
|
259
|
-
fully_qualified_zone = fetch_gce_metadata('instance/zone')
|
260
|
-
@zone = fully_qualified_zone.rpartition('/')[2]
|
261
|
-
end
|
262
|
-
@vm_id = fetch_gce_metadata('instance/id') if @vm_id.nil?
|
263
|
-
when Platform::EC2
|
264
|
-
metadata = fetch_ec2_metadata
|
265
|
-
if @zone.nil? && metadata.key?('availabilityZone')
|
266
|
-
@zone = 'aws:' + metadata['availabilityZone']
|
267
|
-
end
|
268
|
-
if @vm_id.nil? && metadata.key?('instanceId')
|
269
|
-
@vm_id = metadata['instanceId']
|
270
|
-
end
|
271
|
-
if metadata.key?('accountId')
|
272
|
-
@resource.labels['aws_account'] = metadata['accountId']
|
273
|
-
end
|
274
|
-
when Platform::OTHER
|
275
|
-
# do nothing
|
276
|
-
else
|
277
|
-
fail Fluent::ConfigError, 'Unknown platform ' + @platform
|
278
|
-
end
|
279
286
|
|
280
|
-
#
|
281
|
-
#
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
287
|
+
# Set agent-level monitored resource. This monitored resource is initiated
|
288
|
+
# as the logging agent starts up. It will be inherited by all log entries
|
289
|
+
# processed by this agent. First try to retrieve it via Metadata Agent.
|
290
|
+
if @enable_metadata_agent
|
291
|
+
# The locally-unique key for this should be the instance id. Since this
|
292
|
+
# can be implicitly inferred by Metadata Agent, we do not need to
|
293
|
+
# explicitly send the key.
|
294
|
+
@resource = call_metadata_agent_for_monitored_resource(
|
295
|
+
IMPLICIT_MONITORED_RESOURCE_UNIQUE_KEY)
|
286
296
|
end
|
287
297
|
|
288
|
-
#
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
298
|
+
# Set required variables: @project_id, @vm_id, @vm_name and @zone.
|
299
|
+
# If any info above is included in the response from Metadata Agent, make
|
300
|
+
# use of that. Otherwise make some additional requests to metadata server.
|
301
|
+
#
|
302
|
+
# Note: Once we support metadata injection on the Logging API side, we
|
303
|
+
# might no longer need to require all these metadata in logging agent. But
|
304
|
+
# for now, they are still required.
|
305
|
+
set_required_metadata_variables
|
306
|
+
|
307
|
+
# Fail over to retrieve monitored resource via the legacy path if we fail
|
308
|
+
# to get it from Metadata Agent.
|
309
|
+
@resource ||= determine_agent_level_monitored_resource_via_legacy
|
310
|
+
|
311
|
+
# Set variables specific to CLoud Functions. This has to be called after
|
312
|
+
# we have determined the resource type. The purpose is to avoid repeated
|
313
|
+
# calls to metadata server.
|
314
|
+
@running_cloudfunctions = false
|
315
|
+
# We only support Cloud Functions logs for GKE right now.
|
316
|
+
if @resource.type == CONTAINER_CONSTANTS[:resource_type] &&
|
317
|
+
fetch_gce_metadata('instance/attributes/').split.include?('gcf_region')
|
318
|
+
# We are not setting resource type as Cloud Functions here because
|
319
|
+
# whether a log entry is truly coming from a Cloud Functions function
|
320
|
+
# depends on the log tag. Only when @running_cloudfunctions is true will
|
321
|
+
# we try to match log tags against Cloud Functions tag regexp when
|
322
|
+
# processing log entries.
|
323
|
+
@running_cloudfunctions = true
|
324
|
+
# Fetch this info and store it to avoid recurring metadata server calls.
|
325
|
+
@gcf_region = fetch_gce_metadata('instance/attributes/gcf_region')
|
296
326
|
end
|
297
327
|
|
298
|
-
#
|
299
|
-
|
300
|
-
|
301
|
-
# Default this to false; it is only overwritten if we detect Cloud
|
302
|
-
# Functions.
|
303
|
-
@running_cloudfunctions = false
|
328
|
+
# Determine the common labels that should be added to all log entries
|
329
|
+
# processed by this logging agent.
|
330
|
+
@common_labels = determine_agent_level_common_labels
|
304
331
|
|
305
|
-
#
|
306
|
-
|
307
|
-
when Platform::GCE
|
308
|
-
@resource.type = COMPUTE_CONSTANTS[:resource_type]
|
309
|
-
# TODO: introduce a new MonitoredResource-centric configuration and
|
310
|
-
# deprecate subservice-name; for now, translate known uses.
|
311
|
-
if @subservice_name
|
312
|
-
# TODO: what should we do if we encounter an unknown value?
|
313
|
-
if @subservice_name == DATAFLOW_CONSTANTS[:service]
|
314
|
-
@resource.type = DATAFLOW_CONSTANTS[:resource_type]
|
315
|
-
elsif @subservice_name == ML_CONSTANTS[:service]
|
316
|
-
@resource.type = ML_CONSTANTS[:resource_type]
|
317
|
-
end
|
318
|
-
elsif @detect_subservice
|
319
|
-
# Check for specialized GCE environments.
|
320
|
-
# TODO: Add config options for these to allow for running outside GCE?
|
321
|
-
attributes = fetch_gce_metadata('instance/attributes/').split
|
322
|
-
# Do nothing, just don't populate other service's labels.
|
323
|
-
if attributes.include?('gae_backend_name') &&
|
324
|
-
attributes.include?('gae_backend_version')
|
325
|
-
# Managed VM
|
326
|
-
@running_on_managed_vm = true
|
327
|
-
@gae_backend_name =
|
328
|
-
fetch_gce_metadata('instance/attributes/gae_backend_name')
|
329
|
-
@gae_backend_version =
|
330
|
-
fetch_gce_metadata('instance/attributes/gae_backend_version')
|
331
|
-
@resource.type = APPENGINE_CONSTANTS[:resource_type]
|
332
|
-
@resource.labels['module_id'] = @gae_backend_name
|
333
|
-
@resource.labels['version_id'] = @gae_backend_version
|
334
|
-
elsif attributes.include?('kube-env')
|
335
|
-
# Kubernetes/Container Engine
|
336
|
-
@resource.type = CONTAINER_CONSTANTS[:resource_type]
|
337
|
-
@raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
|
338
|
-
@kube_env = YAML.load(@raw_kube_env)
|
339
|
-
@resource.labels['cluster_name'] =
|
340
|
-
cluster_name_from_kube_env(@kube_env)
|
341
|
-
detect_cloudfunctions(attributes)
|
342
|
-
end
|
343
|
-
end
|
344
|
-
# Some services have the GCE instance_id and zone as MonitoredResource
|
345
|
-
# labels; for other services we send them as entry labels.
|
346
|
-
if @resource.type == COMPUTE_CONSTANTS[:resource_type] ||
|
347
|
-
@resource.type == CONTAINER_CONSTANTS[:resource_type]
|
348
|
-
@resource.labels['instance_id'] = @vm_id
|
349
|
-
@resource.labels['zone'] = @zone
|
350
|
-
else
|
351
|
-
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
|
352
|
-
common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
|
353
|
-
end
|
354
|
-
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
355
|
-
when Platform::EC2
|
356
|
-
@resource.type = EC2_CONSTANTS[:resource_type]
|
357
|
-
@resource.labels['instance_id'] = @vm_id
|
358
|
-
@resource.labels['region'] = @zone
|
359
|
-
# the aws_account label is populated above.
|
360
|
-
common_labels["#{EC2_CONSTANTS[:service]}/resource_name"] = @vm_name
|
361
|
-
when Platform::OTHER
|
362
|
-
# Use GCE as the default environment.
|
363
|
-
@resource.type = COMPUTE_CONSTANTS[:resource_type]
|
364
|
-
@resource.labels['instance_id'] = @vm_id
|
365
|
-
@resource.labels['zone'] = @zone
|
366
|
-
common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
367
|
-
end
|
332
|
+
# For each resource type, there is a list of labels that we want to report
|
333
|
+
# as monitored resource instead of metadata labels. Move them if present.
|
368
334
|
@resource.labels.merge!(
|
369
335
|
extract_resource_labels(@resource.type, common_labels))
|
370
336
|
|
@@ -376,7 +342,7 @@ module Fluent
|
|
376
342
|
|
377
343
|
# Log an informational message containing the Logs viewer URL
|
378
344
|
@log.info 'Logs viewer address: https://console.cloud.google.com/logs/',
|
379
|
-
"viewer?project=#{@project_id}&resource=#{@
|
345
|
+
"viewer?project=#{@project_id}&resource=#{@resource.type}/",
|
380
346
|
"instance_id/#{@vm_id}"
|
381
347
|
end
|
382
348
|
|
@@ -391,130 +357,6 @@ module Fluent
|
|
391
357
|
super
|
392
358
|
end
|
393
359
|
|
394
|
-
def format(tag, time, record)
|
395
|
-
[tag, time, record].to_msgpack
|
396
|
-
end
|
397
|
-
|
398
|
-
# Given a tag, returns the corresponding valid tag if possible, or nil if
|
399
|
-
# the tag should be rejected. If 'require_valid_tags' is false, non-string
|
400
|
-
# tags are converted to strings, and invalid characters are sanitized;
|
401
|
-
# otherwise such tags are rejected.
|
402
|
-
def sanitize_tag(tag)
|
403
|
-
if @require_valid_tags &&
|
404
|
-
(!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
|
405
|
-
return nil
|
406
|
-
end
|
407
|
-
tag = convert_to_utf8(tag.to_s)
|
408
|
-
tag = '_' if tag == ''
|
409
|
-
tag
|
410
|
-
end
|
411
|
-
|
412
|
-
# Compute the monitored resource and common labels shared by a collection of
|
413
|
-
# entries.
|
414
|
-
def compute_group_resource_and_labels(tag)
|
415
|
-
# Note that we assume that labels added to group_common_labels below are
|
416
|
-
# not 'service' labels (i.e. we do not call extract_resource_labels
|
417
|
-
# again).
|
418
|
-
group_resource = @resource.dup
|
419
|
-
group_common_labels = @common_labels.dup
|
420
|
-
|
421
|
-
if @running_cloudfunctions
|
422
|
-
# If the current group of entries is coming from a Cloud Functions
|
423
|
-
# function, the function name can be extracted from the tag.
|
424
|
-
match_data = @cloudfunctions_tag_regexp.match(tag)
|
425
|
-
if match_data
|
426
|
-
# Resource type is set to Cloud Functions only for logs actually
|
427
|
-
# coming from a function, otherwise we leave it as Container.
|
428
|
-
group_resource.type = CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
429
|
-
group_resource.labels['region'] = @gcf_region
|
430
|
-
group_resource.labels['function_name'] =
|
431
|
-
decode_cloudfunctions_function_name(
|
432
|
-
match_data['encoded_function_name'])
|
433
|
-
# Move GKE container labels from the MonitoredResource to the
|
434
|
-
# LogEntry.
|
435
|
-
instance_id = group_resource.labels.delete('instance_id')
|
436
|
-
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/cluster_name"] =
|
437
|
-
group_resource.labels.delete('cluster_name')
|
438
|
-
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/instance_id"] =
|
439
|
-
instance_id
|
440
|
-
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
|
441
|
-
instance_id
|
442
|
-
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] =
|
443
|
-
group_resource.labels.delete('zone')
|
444
|
-
end
|
445
|
-
end
|
446
|
-
if group_resource.type == CONTAINER_CONSTANTS[:resource_type] &&
|
447
|
-
@compiled_kubernetes_tag_regexp
|
448
|
-
# Container logs in Kubernetes are tagged based on where they came
|
449
|
-
# from, so we can extract useful metadata from the tag.
|
450
|
-
# Do this here to avoid having to repeat it for each record.
|
451
|
-
match_data = @compiled_kubernetes_tag_regexp.match(tag)
|
452
|
-
if match_data
|
453
|
-
group_resource.labels['container_name'] = match_data['container_name']
|
454
|
-
group_resource.labels['namespace_id'] = match_data['namespace_name']
|
455
|
-
group_resource.labels['pod_id'] = match_data['pod_name']
|
456
|
-
%w(namespace_name pod_name).each do |field|
|
457
|
-
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/#{field}"] =
|
458
|
-
match_data[field]
|
459
|
-
end
|
460
|
-
end
|
461
|
-
end
|
462
|
-
|
463
|
-
# Freeze the per-request state. Any further changes must be made on a
|
464
|
-
# per-entry basis.
|
465
|
-
group_resource.freeze
|
466
|
-
group_resource.labels.freeze
|
467
|
-
group_common_labels.freeze
|
468
|
-
|
469
|
-
[group_resource, group_common_labels]
|
470
|
-
end
|
471
|
-
|
472
|
-
# Extract entry resource and common labels that should be applied to
|
473
|
-
# individual entries from the group resource.
|
474
|
-
def extract_entry_labels(group_resource, record)
|
475
|
-
resource_labels = {}
|
476
|
-
common_labels = {}
|
477
|
-
|
478
|
-
if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
479
|
-
record.key?('log')
|
480
|
-
@cloudfunctions_log_match =
|
481
|
-
@cloudfunctions_log_regexp.match(record['log'])
|
482
|
-
end
|
483
|
-
|
484
|
-
if group_resource.type == CONTAINER_CONSTANTS[:resource_type]
|
485
|
-
# Move the stdout/stderr annotation from the record into a label
|
486
|
-
common_labels.merge!(
|
487
|
-
fields_to_labels(
|
488
|
-
record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
|
489
|
-
|
490
|
-
# If the record has been annotated by the kubernetes_metadata_filter
|
491
|
-
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
492
|
-
# populated at the grouped_entries level from the group's tag.
|
493
|
-
if record.key?('kubernetes')
|
494
|
-
extracted_resource_labels, extracted_common_labels = \
|
495
|
-
extract_container_metadata(record)
|
496
|
-
resource_labels.merge!(extracted_resource_labels)
|
497
|
-
common_labels.merge!(extracted_common_labels)
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
# If a field is present in the label_map, send its value as a label
|
502
|
-
# (mapping the field name to label name as specified in the config)
|
503
|
-
# and do not send that field as part of the payload.
|
504
|
-
common_labels.merge!(fields_to_labels(record, @label_map))
|
505
|
-
|
506
|
-
if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
507
|
-
@cloudfunctions_log_match &&
|
508
|
-
@cloudfunctions_log_match['execution_id']
|
509
|
-
common_labels['execution_id'] =
|
510
|
-
@cloudfunctions_log_match['execution_id']
|
511
|
-
end
|
512
|
-
resource_labels.merge!(
|
513
|
-
extract_resource_labels(group_resource.type, common_labels))
|
514
|
-
|
515
|
-
[resource_labels, common_labels]
|
516
|
-
end
|
517
|
-
|
518
360
|
def write(chunk)
|
519
361
|
# Group the entries since we have to make one call per tag.
|
520
362
|
grouped_entries = {}
|
@@ -531,20 +373,21 @@ module Fluent
|
|
531
373
|
|
532
374
|
grouped_entries.each do |tag, arr|
|
533
375
|
entries = []
|
534
|
-
group_resource, group_common_labels =
|
535
|
-
tag)
|
376
|
+
group_resource, group_common_labels =
|
377
|
+
determine_group_level_monitored_resource_and_labels(tag)
|
536
378
|
|
537
379
|
arr.each do |time, record|
|
538
380
|
next unless record.is_a?(Hash)
|
539
381
|
|
540
382
|
extracted_resource_labels, extracted_common_labels = \
|
541
|
-
|
383
|
+
determine_entry_level_labels(group_resource, record)
|
542
384
|
entry_resource = group_resource.dup
|
543
385
|
entry_resource.labels.merge!(extracted_resource_labels)
|
544
386
|
entry_common_labels = \
|
545
387
|
group_common_labels.merge(extracted_common_labels)
|
546
388
|
|
547
|
-
if
|
389
|
+
if [CONTAINER_CONSTANTS[:resource_type],
|
390
|
+
DOCKER_CONSTANTS[:resource_type]].include?(entry_resource.type)
|
548
391
|
# Save the timestamp if available, then clear it out to allow for
|
549
392
|
# determining whether we should parse the log or message field.
|
550
393
|
timestamp = record.key?('time') ? record['time'] : nil
|
@@ -777,7 +620,7 @@ module Fluent
|
|
777
620
|
end
|
778
621
|
end
|
779
622
|
rescue StandardError => e
|
780
|
-
@log.
|
623
|
+
@log.error 'Failed to access metadata service: ', error: e
|
781
624
|
end
|
782
625
|
|
783
626
|
@log.info 'Unable to determine platform'
|
@@ -792,15 +635,570 @@ module Fluent
|
|
792
635
|
metadata_path, 'Metadata-Flavor' => 'Google', &:read)
|
793
636
|
end
|
794
637
|
|
795
|
-
|
796
|
-
|
638
|
+
# EC2 Metadata server returns everything in one call. Store it after the
|
639
|
+
# first fetch to avoid making multiple calls.
|
640
|
+
def ec2_metadata
|
641
|
+
fail "Called ec2_metadata with platform=#{@platform}" unless
|
642
|
+
@platform == Platform::EC2
|
643
|
+
unless @ec2_metadata
|
644
|
+
# See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
|
645
|
+
open('http://' + METADATA_SERVICE_ADDR +
|
646
|
+
'/latest/dynamic/instance-identity/document') do |f|
|
647
|
+
contents = f.read
|
648
|
+
@ec2_metadata = JSON.parse(contents)
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
@ec2_metadata
|
653
|
+
end
|
654
|
+
|
655
|
+
# Set regexp patterns to parse tags and logs.
|
656
|
+
def set_regexp_patterns
|
657
|
+
@compiled_kubernetes_tag_regexp = nil
|
658
|
+
if @kubernetes_tag_regexp
|
659
|
+
@compiled_kubernetes_tag_regexp = Regexp.new(@kubernetes_tag_regexp)
|
660
|
+
end
|
661
|
+
|
662
|
+
@cloudfunctions_tag_regexp =
|
663
|
+
/\.(?<encoded_function_name>.+)\.\d+-[^-]+_default_worker$/
|
664
|
+
@cloudfunctions_log_regexp = /^
|
665
|
+
(?:\[(?<severity>.)\])?
|
666
|
+
\[(?<timestamp>.{24})\]
|
667
|
+
(?:\[(?<execution_id>[^\]]+)\])?
|
668
|
+
[ ](?<text>.*)$/x
|
669
|
+
|
670
|
+
@http_latency_regexp = /^\s*(?<seconds>\d+)(?<decimal>\.\d+)?\s*s\s*$/
|
671
|
+
end
|
672
|
+
|
673
|
+
# Set required variables like @project_id, @vm_id, @vm_name and @zone.
|
674
|
+
def set_required_metadata_variables
|
675
|
+
set_project_id
|
676
|
+
set_vm_id
|
677
|
+
set_vm_name
|
678
|
+
set_location
|
679
|
+
|
680
|
+
# All metadata parameters must now be set.
|
681
|
+
return if @project_id && @zone && @vm_id
|
682
|
+
missing = []
|
683
|
+
missing << 'project_id' unless @project_id
|
684
|
+
missing << 'zone' unless @zone
|
685
|
+
missing << 'vm_id' unless @vm_id
|
686
|
+
fail Fluent::ConfigError, 'Unable to obtain metadata parameters: ' +
|
687
|
+
missing.join(' ')
|
688
|
+
end
|
689
|
+
|
690
|
+
# 1. Return the value if it is explicitly set in the config already.
|
691
|
+
# 2. If not, try to retrieve it by calling metadata server directly.
|
692
|
+
# 3. If still not set, try to obtain it from the credentials.
|
693
|
+
def set_project_id
|
694
|
+
@project_id ||= fetch_gce_metadata('project/project-id') if
|
695
|
+
@platform == Platform::GCE
|
696
|
+
@project_id ||= CredentialsInfo.project_id
|
697
|
+
rescue StandardError => e
|
698
|
+
@log.error 'Failed to obtain project id: ', error: e
|
699
|
+
end
|
700
|
+
|
701
|
+
# 1. Return the value if it is explicitly set in the config already.
|
702
|
+
# 2. If not, check if the response from Metadata Agent includes this info.
|
703
|
+
# 3. If not, try to retrieve it by calling metadata servers directly.
|
704
|
+
def set_vm_id
|
705
|
+
@vm_id ||= @resource.labels['instance_id'] if
|
706
|
+
!@resource.nil? && @resource.labels.key?('instance_id')
|
707
|
+
@vm_id ||= fetch_gce_metadata('instance/id') if @platform == Platform::GCE
|
708
|
+
@vm_id ||= ec2_metadata['instanceId'] if @platform == Platform::EC2
|
709
|
+
rescue StandardError => e
|
710
|
+
@log.error 'Failed to obtain vm_id: ', error: e
|
711
|
+
end
|
712
|
+
|
713
|
+
# 1. Return the value if it is explicitly set in the config already.
|
714
|
+
# 2. If not, check if the response from Metadata Agent includes this info.
|
715
|
+
# 3. If not, try to retrieve it locally.
|
716
|
+
def set_vm_name
|
717
|
+
@vm_name ||= @resource.labels['instance_name'] if
|
718
|
+
!@resource.nil? && @resource.labels.key?('instance_name')
|
719
|
+
@vm_name ||= Socket.gethostname
|
720
|
+
rescue StandardError => e
|
721
|
+
@log.error 'Failed to obtain vm name: ', error: e
|
722
|
+
end
|
723
|
+
|
724
|
+
# 1. Return the value if it is explicitly set in the config already.
|
725
|
+
# 2. If not, check if the response from Metadata Agent includes this info.
|
726
|
+
# 3. If not, try to retrieve it locally.
|
727
|
+
def set_location
|
728
|
+
unless @resource.nil?
|
729
|
+
@zone ||= @resource.labels['location'] if
|
730
|
+
@resource.type == DOCKER_CONSTANTS[:resource_type] &&
|
731
|
+
@resource.labels.key?('location')
|
732
|
+
@zone ||= @resource.labels['zone'] if
|
733
|
+
@platform == Platform::GCE && @resource.labels.key?('zone')
|
734
|
+
@zone ||= @resource.labels['region'] if
|
735
|
+
@platform == Platform::EC2 && @resource.labels.key?('region')
|
736
|
+
end
|
737
|
+
# Response format: "projects/<number>/zones/<zone>"
|
738
|
+
@zone ||= fetch_gce_metadata('instance/zone').rpartition('/')[2] if
|
739
|
+
@platform == Platform::GCE
|
740
|
+
@zone ||= 'aws:' + ec2_metadata['availabilityZone'] if
|
741
|
+
@platform == Platform::EC2 && ec2_metadata.key?('availabilityZone')
|
742
|
+
rescue StandardError => e
|
743
|
+
@log.error 'Failed to obtain location: ', error: e
|
744
|
+
end
|
745
|
+
|
746
|
+
# Retrieve monitored resource via the legacy way.
|
747
|
+
#
|
748
|
+
# Note: This is just a failover plan if we fail to get metadata from
|
749
|
+
# Metadata agent. Thus it should be equivalent to what Metadata Agent
|
750
|
+
# returns.
|
751
|
+
def determine_agent_level_monitored_resource_via_legacy
|
752
|
+
resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
753
|
+
labels: {})
|
754
|
+
resource.type = determine_agent_level_monitored_resource_type
|
755
|
+
resource.labels = determine_agent_level_monitored_resource_labels(
|
756
|
+
resource.type)
|
757
|
+
resource
|
758
|
+
end
|
759
|
+
|
760
|
+
# Determine agent level monitored resource type.
|
761
|
+
def determine_agent_level_monitored_resource_type
|
762
|
+
# EC2 instance.
|
763
|
+
return EC2_CONSTANTS[:resource_type] if
|
797
764
|
@platform == Platform::EC2
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
765
|
+
|
766
|
+
# Unknown platform will be defaulted to GCE instance..
|
767
|
+
return COMPUTE_CONSTANTS[:resource_type] if
|
768
|
+
@platform == Platform::OTHER
|
769
|
+
|
770
|
+
# Resource types determined by @subservice_name config.
|
771
|
+
# Cloud Dataflow.
|
772
|
+
return DATAFLOW_CONSTANTS[:resource_type] if
|
773
|
+
@subservice_name == DATAFLOW_CONSTANTS[:service]
|
774
|
+
# Cloud ML.
|
775
|
+
return ML_CONSTANTS[:resource_type] if
|
776
|
+
@subservice_name == ML_CONSTANTS[:service]
|
777
|
+
# Default back to GCE if invalid value is detected.
|
778
|
+
return COMPUTE_CONSTANTS[:resource_type] if
|
779
|
+
@subservice_name
|
780
|
+
|
781
|
+
# Resource types determined by @detect_subservice config.
|
782
|
+
if @detect_subservice
|
783
|
+
begin
|
784
|
+
attributes = fetch_gce_metadata('instance/attributes/').split
|
785
|
+
rescue StandardError => e
|
786
|
+
@log.error 'Failed to detect subservice: ', error: e
|
787
|
+
end
|
788
|
+
# GAE app.
|
789
|
+
return APPENGINE_CONSTANTS[:resource_type] if
|
790
|
+
attributes.include?('gae_backend_name') &&
|
791
|
+
attributes.include?('gae_backend_version')
|
792
|
+
# GKE container.
|
793
|
+
return CONTAINER_CONSTANTS[:resource_type] if
|
794
|
+
attributes.include?('kube-env')
|
795
|
+
# Cloud Dataproc.
|
796
|
+
return DATAPROC_CONSTANTS[:resource_type] if
|
797
|
+
attributes.include?('dataproc-cluster-uuid') &&
|
798
|
+
attributes.include?('dataproc-cluster-name')
|
799
|
+
end
|
800
|
+
# GCE instance.
|
801
|
+
COMPUTE_CONSTANTS[:resource_type]
|
802
|
+
end
|
803
|
+
|
804
|
+
# Determine agent level monitored resource labels based on the resource
|
805
|
+
# type. Each resource type has its own labels that need to be filled in.
|
806
|
+
def determine_agent_level_monitored_resource_labels(type)
|
807
|
+
labels = {}
|
808
|
+
|
809
|
+
case type
|
810
|
+
|
811
|
+
# GAE app.
|
812
|
+
when APPENGINE_CONSTANTS[:resource_type]
|
813
|
+
begin
|
814
|
+
labels['module_id'] = fetch_gce_metadata(
|
815
|
+
'instance/attributes/gae_backend_name')
|
816
|
+
labels['version_id'] = fetch_gce_metadata(
|
817
|
+
'instance/attributes/gae_backend_version')
|
818
|
+
rescue StandardError => e
|
819
|
+
@log.error 'Failed to set monitored resource labels for GAE: ',
|
820
|
+
error: e
|
821
|
+
end
|
822
|
+
|
823
|
+
# GCE.
|
824
|
+
when COMPUTE_CONSTANTS[:resource_type]
|
825
|
+
labels['instance_id'] = @vm_id
|
826
|
+
labels['zone'] = @zone
|
827
|
+
|
828
|
+
# GKE container.
|
829
|
+
when CONTAINER_CONSTANTS[:resource_type]
|
830
|
+
labels['instance_id'] = @vm_id
|
831
|
+
labels['zone'] = @zone
|
832
|
+
begin
|
833
|
+
raw_kube_env = fetch_gce_metadata('instance/attributes/kube-env')
|
834
|
+
kube_env = YAML.load(raw_kube_env)
|
835
|
+
labels['cluster_name'] =
|
836
|
+
cluster_name_from_kube_env(kube_env)
|
837
|
+
rescue StandardError => e
|
838
|
+
@log.error 'Failed to set monitored resource labels for GKE: ',
|
839
|
+
error: e
|
840
|
+
end
|
841
|
+
|
842
|
+
# Cloud Dataproc.
|
843
|
+
when DATAPROC_CONSTANTS[:resource_type]
|
844
|
+
begin
|
845
|
+
labels['cluster_uuid'] =
|
846
|
+
fetch_gce_metadata('instance/attributes/dataproc-cluster-uuid')
|
847
|
+
labels['cluster_name'] =
|
848
|
+
fetch_gce_metadata('instance/attributes/dataproc-cluster-name')
|
849
|
+
labels['region'] =
|
850
|
+
fetch_gce_metadata('instance/attributes/dataproc-region')
|
851
|
+
rescue StandardError => e
|
852
|
+
@log.error 'Failed to set monitored resource labels for Cloud ' \
|
853
|
+
'Dataproc: ', error: e
|
854
|
+
end
|
855
|
+
|
856
|
+
# EC2.
|
857
|
+
when EC2_CONSTANTS[:resource_type]
|
858
|
+
labels['instance_id'] = @vm_id
|
859
|
+
labels['region'] = @zone
|
860
|
+
labels['aws_account'] = ec2_metadata['accountId'] if
|
861
|
+
ec2_metadata.key?('accountId')
|
862
|
+
end
|
863
|
+
labels
|
864
|
+
end
|
865
|
+
|
866
|
+
# Determine the common labels that should be added to all log entries
|
867
|
+
# processed by this logging agent.
|
868
|
+
def determine_agent_level_common_labels
|
869
|
+
labels = {}
|
870
|
+
# User can specify labels via config. We want to capture those as well.
|
871
|
+
# TODO: Send instance tags as labels as well?
|
872
|
+
labels.merge!(@labels) if @labels
|
873
|
+
|
874
|
+
case @resource.type
|
875
|
+
|
876
|
+
# GAE app.
|
877
|
+
when APPENGINE_CONSTANTS[:resource_type]
|
878
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
|
879
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
880
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
|
881
|
+
|
882
|
+
# GCE.
|
883
|
+
when COMPUTE_CONSTANTS[:resource_type]
|
884
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
885
|
+
|
886
|
+
# GKE container.
|
887
|
+
when CONTAINER_CONSTANTS[:resource_type]
|
888
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
889
|
+
|
890
|
+
# Cloud Dataflow and Cloud Dataproc.
|
891
|
+
when DATAFLOW_CONSTANTS[:resource_type],
|
892
|
+
DATAPROC_CONSTANTS[:resource_type]
|
893
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
|
894
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
895
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
|
896
|
+
|
897
|
+
# EC2.
|
898
|
+
when EC2_CONSTANTS[:resource_type]
|
899
|
+
labels["#{EC2_CONSTANTS[:service]}/resource_name"] = @vm_name
|
900
|
+
|
901
|
+
# Cloud ML.
|
902
|
+
when ML_CONSTANTS[:resource_type]
|
903
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] = @vm_id
|
904
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/resource_name"] = @vm_name
|
905
|
+
labels["#{COMPUTE_CONSTANTS[:service]}/zone"] = @zone
|
906
|
+
end
|
907
|
+
labels
|
908
|
+
end
|
909
|
+
|
910
|
+
# Determine the group level monitored resource and common labels shared by a
|
911
|
+
# collection of entries.
|
912
|
+
def determine_group_level_monitored_resource_and_labels(tag)
|
913
|
+
# Determine group level monitored resource type. For certain types,
|
914
|
+
# extract useful info from the tag and store those in
|
915
|
+
# matched_regexp_group.
|
916
|
+
group_resource_type, matched_regexp_group =
|
917
|
+
determine_group_level_monitored_resource_type(tag)
|
918
|
+
|
919
|
+
# Determine group level monitored resource labels and common labels.
|
920
|
+
group_resource_type, group_resource_labels, group_common_labels = \
|
921
|
+
determine_group_level_labels_and_adjust_type(
|
922
|
+
group_resource_type, matched_regexp_group)
|
923
|
+
|
924
|
+
group_resource = Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
925
|
+
type: group_resource_type,
|
926
|
+
labels: group_resource_labels.to_h
|
927
|
+
)
|
928
|
+
|
929
|
+
# Freeze the per-request state. Any further changes must be made on a
|
930
|
+
# per-entry basis.
|
931
|
+
group_resource.freeze
|
932
|
+
group_resource.labels.freeze
|
933
|
+
group_common_labels.freeze
|
934
|
+
|
935
|
+
[group_resource, group_common_labels]
|
936
|
+
end
|
937
|
+
|
938
|
+
# Determine group level monitored resource type shared by a collection of
|
939
|
+
# entries.
|
940
|
+
# Returns the resource type and tag regexp matched groups. The matched
|
941
|
+
# groups only apply to some resource types. Return nil if not applicable or
|
942
|
+
# if there is no match.
|
943
|
+
def determine_group_level_monitored_resource_type(tag)
|
944
|
+
# Match tag against Cloud Functions format.
|
945
|
+
if @running_cloudfunctions
|
946
|
+
matched_regexp_group = @cloudfunctions_tag_regexp.match(tag)
|
947
|
+
return [CLOUDFUNCTIONS_CONSTANTS[:resource_type],
|
948
|
+
matched_regexp_group] if matched_regexp_group
|
949
|
+
end
|
950
|
+
|
951
|
+
# Match tag against Docker container stderr / stdout log format and
|
952
|
+
# Docker container application log format.
|
953
|
+
matched_regexp_group =
|
954
|
+
# Format: "container.<container_id>.<container_name>"
|
955
|
+
@dockercontainer_tag_regexp.match(tag) ||
|
956
|
+
# Format: "application-container.<container_name>.<additional_tag>"
|
957
|
+
@dockercontainer_tag_with_application_regexp.match(tag)
|
958
|
+
return [DOCKER_CONSTANTS[:resource_type], matched_regexp_group] if
|
959
|
+
matched_regexp_group
|
960
|
+
|
961
|
+
# Match tag against GKE Container format.
|
962
|
+
if @resource.type == CONTAINER_CONSTANTS[:resource_type] &&
|
963
|
+
@compiled_kubernetes_tag_regexp
|
964
|
+
# Container logs in Kubernetes are tagged based on where they came from,
|
965
|
+
# so we can extract useful metadata from the tag. Do this here to avoid
|
966
|
+
# having to repeat it for each record.
|
967
|
+
matched_regexp_group = @compiled_kubernetes_tag_regexp.match(tag)
|
968
|
+
return [@resource.type, matched_regexp_group] if matched_regexp_group
|
969
|
+
end
|
970
|
+
|
971
|
+
# Otherwise, return the original type.
|
972
|
+
[@resource.type, nil]
|
973
|
+
end
|
974
|
+
|
975
|
+
# Determine group level monitored resource labels and common labels. These
|
976
|
+
# labels will be shared by a collection of entries. In certain cases, we
|
977
|
+
# might also adjust the resource type.
|
978
|
+
def determine_group_level_labels_and_adjust_type(group_resource_type,
|
979
|
+
matched_regexp_group)
|
980
|
+
group_resource_labels = @resource.labels.dup
|
981
|
+
group_common_labels = @common_labels.dup
|
982
|
+
|
983
|
+
case group_resource_type
|
984
|
+
|
985
|
+
# Cloud Functions.
|
986
|
+
when CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
987
|
+
group_resource_labels['region'] = @gcf_region
|
988
|
+
group_resource_labels['function_name'] =
|
989
|
+
decode_cloudfunctions_function_name(
|
990
|
+
matched_regexp_group['encoded_function_name'])
|
991
|
+
instance_id = group_resource_labels.delete('instance_id')
|
992
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/cluster_name"] =
|
993
|
+
group_resource_labels.delete('cluster_name')
|
994
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/instance_id"] =
|
995
|
+
instance_id
|
996
|
+
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
|
997
|
+
instance_id
|
998
|
+
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/zone"] =
|
999
|
+
group_resource_labels.delete('zone')
|
1000
|
+
|
1001
|
+
# GKE container.
|
1002
|
+
when CONTAINER_CONSTANTS[:resource_type]
|
1003
|
+
if matched_regexp_group
|
1004
|
+
group_resource_labels['container_name'] =
|
1005
|
+
matched_regexp_group['container_name']
|
1006
|
+
group_resource_labels['namespace_id'] =
|
1007
|
+
matched_regexp_group['namespace_name']
|
1008
|
+
group_resource_labels['pod_id'] =
|
1009
|
+
matched_regexp_group['pod_name']
|
1010
|
+
%w(namespace_name pod_name).each do |field|
|
1011
|
+
group_common_labels["#{CONTAINER_CONSTANTS[:service]}/#{field}"] =
|
1012
|
+
matched_regexp_group[field]
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Docker container.
|
1017
|
+
when DOCKER_CONSTANTS[:resource_type]
|
1018
|
+
# For Docker container stderr / stdout logs generated by Docker Fluentd
|
1019
|
+
# Logging Driver, tags are in the format of "container.<container_id>.
|
1020
|
+
# <container_name>", thus they include 'container_id' info.
|
1021
|
+
# For logs generated by applications running in Docker containers,
|
1022
|
+
# tags are in the format of "application-container.<container_name>.
|
1023
|
+
# <additional_tag>", thus 'container_id' info is unknown yet.
|
1024
|
+
# 'container_name' info on the other hand is always available.
|
1025
|
+
container_id = matched_regexp_group['container_id'] if
|
1026
|
+
matched_regexp_group.names.include? 'container_id'
|
1027
|
+
container_name = matched_regexp_group['container_name']
|
1028
|
+
|
1029
|
+
if @enable_metadata_agent
|
1030
|
+
# Call Metadata Agent with "container.<container_id>" or
|
1031
|
+
# "application-container.<container_name>" as the locally-unique key
|
1032
|
+
# to retrieve monitored resource. This should be different from the
|
1033
|
+
# original @resource value that got initiated when the agent starts up
|
1034
|
+
# because that one is always at the VM level.
|
1035
|
+
if container_id
|
1036
|
+
locally_unique_id = "container.#{container_id}"
|
1037
|
+
else
|
1038
|
+
locally_unique_id = "containerName.#{container_name}"
|
1039
|
+
end
|
1040
|
+
retrieved_resource = call_metadata_agent_for_monitored_resource(
|
1041
|
+
locally_unique_id)
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
if @enable_metadata_agent && !retrieved_resource.nil?
|
1045
|
+
# If we successfully get a monitored resource from Metadata Agent,
|
1046
|
+
# use this one instead of the original VM-level monitored resource.
|
1047
|
+
group_resource_labels = retrieved_resource.labels.dup
|
1048
|
+
@log.debug 'Retrieved monitored resource from Metadata Agent: ' \
|
1049
|
+
"#{retrieved_resource.inspect}."
|
1050
|
+
else
|
1051
|
+
# If Metadata Agent is not enabled, or we failed to get a monitored
|
1052
|
+
# resource, we need to have some backup plan.
|
1053
|
+
@log.debug 'Metadata agent not enabled or failed to retrieve ' \
|
1054
|
+
'docker container monitored resource from Metadata ' \
|
1055
|
+
'Agent.'
|
1056
|
+
|
1057
|
+
# 1. Check if 'container_id' is set already. It should be available
|
1058
|
+
# for stdout / stderr). If so, use that.
|
1059
|
+
# 2. If not, call Docker Remote API to calculate container id from
|
1060
|
+
# container name, but only if @call_docker_api_locally is true.
|
1061
|
+
container_id ||= retrieve_container_id_by_name_locally(
|
1062
|
+
container_name) if @call_docker_api_locally
|
1063
|
+
unless container_id
|
1064
|
+
@log.debug 'No docker container id retrieved. Falling back to
|
1065
|
+
instance monitored resource.'
|
1066
|
+
# If a container id is not available, fall back to the instance
|
1067
|
+
# monitored resource.
|
1068
|
+
return [COMPUTE_CONSTANTS[:resource_type], group_resource_labels,
|
1069
|
+
group_common_labels]
|
1070
|
+
end
|
1071
|
+
group_resource_labels['container_id'] = container_id
|
1072
|
+
# 'zone' for GCP and 'region' for EC2 must have been set at this
|
1073
|
+
# point. Rename them to 'location'.
|
1074
|
+
group_resource_labels['location'] = @zone
|
1075
|
+
if @platform == Platform::EC2
|
1076
|
+
group_resource_labels.delete('region')
|
1077
|
+
else
|
1078
|
+
group_resource_labels.delete('zone')
|
1079
|
+
end
|
1080
|
+
# vm id info should be reported as a metadata label instead.
|
1081
|
+
group_resource_labels.delete('instance_id')
|
1082
|
+
|
1083
|
+
end
|
1084
|
+
# Set metadata labels.
|
1085
|
+
group_common_labels["#{DOCKER_CONSTANTS[:service]}/container_name"] =
|
1086
|
+
matched_regexp_group['container_name']
|
1087
|
+
group_common_labels["#{COMPUTE_CONSTANTS[:service]}/resource_id"] =
|
1088
|
+
@vm_id
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
[group_resource_type, group_resource_labels, group_common_labels]
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
# Extract entry resource and common labels that should be applied to
|
1095
|
+
# individual entries from the group resource.
|
1096
|
+
def determine_entry_level_labels(group_resource, record)
|
1097
|
+
resource_labels = {}
|
1098
|
+
common_labels = {}
|
1099
|
+
|
1100
|
+
# Cloud Functions.
|
1101
|
+
if group_resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type] &&
|
1102
|
+
record.key?('log')
|
1103
|
+
@cloudfunctions_log_match =
|
1104
|
+
@cloudfunctions_log_regexp.match(record['log'])
|
1105
|
+
common_labels['execution_id'] =
|
1106
|
+
@cloudfunctions_log_match['execution_id'] if \
|
1107
|
+
@cloudfunctions_log_match &&
|
1108
|
+
@cloudfunctions_log_match['execution_id']
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
# GKE containers.
|
1112
|
+
if group_resource.type == CONTAINER_CONSTANTS[:resource_type]
|
1113
|
+
# Move the stdout/stderr annotation from the record into a label.
|
1114
|
+
common_labels.merge!(
|
1115
|
+
fields_to_labels(
|
1116
|
+
record, 'stream' => "#{CONTAINER_CONSTANTS[:service]}/stream"))
|
1117
|
+
|
1118
|
+
# If the record has been annotated by the kubernetes_metadata_filter
|
1119
|
+
# plugin, then use that metadata. Otherwise, rely on commonLabels
|
1120
|
+
# populated at the grouped_entries level from the group's tag.
|
1121
|
+
if record.key?('kubernetes')
|
1122
|
+
extracted_resource_labels, extracted_common_labels = \
|
1123
|
+
extract_container_metadata(record)
|
1124
|
+
resource_labels.merge!(extracted_resource_labels)
|
1125
|
+
common_labels.merge!(extracted_common_labels)
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
# Docker containers.
|
1130
|
+
if group_resource.type == DOCKER_CONSTANTS[:resource_type]
|
1131
|
+
# For logs coming from Docker Fluentd Logging Driver, the log record
|
1132
|
+
# has 4 fields: 'container_id', 'container_name', 'source' and 'log'.
|
1133
|
+
# Extract 'container_id', 'container_name' and 'source' from json
|
1134
|
+
# record, set corresponding labels, and remove these fields from record.
|
1135
|
+
{
|
1136
|
+
'container_name' => 'container_name',
|
1137
|
+
'source' => 'stream'
|
1138
|
+
}.each do |field_name, label_name|
|
1139
|
+
common_labels.merge!(
|
1140
|
+
fields_to_labels(
|
1141
|
+
record,
|
1142
|
+
field_name => "#{DOCKER_CONSTANTS[:service]}/#{label_name}"
|
1143
|
+
)
|
1144
|
+
)
|
1145
|
+
end
|
1146
|
+
resource_labels.merge!(
|
1147
|
+
fields_to_labels(record, 'container_id' => 'container_id'))
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
# If the name of a field in the record is present in the @label_map
|
1151
|
+
# configured by users, report its value as a label and do not send that
|
1152
|
+
# field as part of the payload.
|
1153
|
+
common_labels.merge!(fields_to_labels(record, @label_map))
|
1154
|
+
|
1155
|
+
resource_labels.merge!(
|
1156
|
+
extract_resource_labels(group_resource.type, common_labels))
|
1157
|
+
|
1158
|
+
[resource_labels, common_labels]
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
# Call Metadata Agent to get monitored resource information and parse
|
1162
|
+
# response to Google::Api::MonitoredResource.
|
1163
|
+
def call_metadata_agent_for_monitored_resource(unique_key)
|
1164
|
+
response = call_metadata_agent("monitoredResource/#{unique_key}")
|
1165
|
+
return nil if response.nil?
|
1166
|
+
begin
|
1167
|
+
resource = Google::Api::MonitoredResource.decode_json(response.to_json)
|
1168
|
+
rescue Google::Protobuf::ParseError, ArgumentError => e
|
1169
|
+
@log.error 'Error paring monitored resource from Metadata Agent. ' \
|
1170
|
+
"response: #{response.inspect}", error: e
|
1171
|
+
return nil
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
# TODO(lingshi): Use Google::Api::MonitoredResource directly after we
|
1175
|
+
# upgrade gRPC version to include the fix for the protobuf map
|
1176
|
+
# corruption issue.
|
1177
|
+
Google::Apis::LoggingV2beta1::MonitoredResource.new(
|
1178
|
+
type: resource.type,
|
1179
|
+
labels: resource.labels.to_h
|
1180
|
+
)
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
# Call Metadata Agent and parse response to json. Return nil in case of any
|
1184
|
+
# error / failure.
|
1185
|
+
def call_metadata_agent(path)
|
1186
|
+
url = "#{@metadata_agent_url}/#{path}"
|
1187
|
+
@log.debug("Calling Metadata Agent: #{url}")
|
1188
|
+
open(url) do |f|
|
1189
|
+
response = f.read
|
1190
|
+
parsed_hash = parse_json_or_nil(response)
|
1191
|
+
if parsed_hash.nil?
|
1192
|
+
@log.error 'Response from Metadata Agent is not in valid json ' \
|
1193
|
+
"format: '#{response.inspect}'."
|
1194
|
+
return nil
|
1195
|
+
end
|
1196
|
+
@log.debug "Response from Metadata Agent: #{parsed_hash}"
|
1197
|
+
return parsed_hash
|
803
1198
|
end
|
1199
|
+
rescue StandardError => e
|
1200
|
+
@log.error 'Error calling Metadata Agent.', error: e
|
1201
|
+
return nil
|
804
1202
|
end
|
805
1203
|
|
806
1204
|
# TODO: This functionality should eventually be available in another
|
@@ -838,11 +1236,28 @@ module Fluent
|
|
838
1236
|
end
|
839
1237
|
end
|
840
1238
|
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
1239
|
+
# Calling Docker Remote API to get container id by name.
|
1240
|
+
def retrieve_container_id_by_name_locally(container_name)
|
1241
|
+
response = Excon.get(
|
1242
|
+
"unix:///containers/#{container_name}/json",
|
1243
|
+
socket: @docker_remote_api_socket_path)
|
1244
|
+
@log.debug "Response from Docker API with name '#{container_name}': " \
|
1245
|
+
"#{response.inspect}."
|
1246
|
+
return parse_container_id_from_docker_api_response(response)
|
1247
|
+
rescue StandardError => e
|
1248
|
+
@log.error 'Error calling Docker API to get container id.', error: e
|
1249
|
+
return nil
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
# Parse the container id from Docker Remote API response.
|
1253
|
+
# TODO(qingling128) Add a config for Docker API version to support parsing
|
1254
|
+
# different versions of Docker Remote API when the format varies.
|
1255
|
+
def parse_container_id_from_docker_api_response(response)
|
1256
|
+
JSON.parse(response.data[:body])['Id']
|
1257
|
+
rescue StandardError => e
|
1258
|
+
@log.error 'Error parsing Docker API response to get container id.',
|
1259
|
+
error: e
|
1260
|
+
return nil
|
846
1261
|
end
|
847
1262
|
|
848
1263
|
def cluster_name_from_kube_env(kube_env)
|
@@ -915,9 +1330,14 @@ module Fluent
|
|
915
1330
|
end
|
916
1331
|
elsif record.key?('severity')
|
917
1332
|
return parse_severity(record.delete('severity'))
|
918
|
-
elsif
|
919
|
-
|
920
|
-
stream = entry_common_labels[
|
1333
|
+
elsif [CONTAINER_CONSTANTS[:resource_type],
|
1334
|
+
DOCKER_CONSTANTS[:resource_type]].include?(resource_type)
|
1335
|
+
stream = entry_common_labels[
|
1336
|
+
"#{CONTAINER_CONSTANTS[:service]}/stream"] if
|
1337
|
+
resource_type == CONTAINER_CONSTANTS[:resource_type]
|
1338
|
+
stream = entry_common_labels[
|
1339
|
+
"#{DOCKER_CONSTANTS[:service]}/stream"] if
|
1340
|
+
resource_type == DOCKER_CONSTANTS[:resource_type]
|
921
1341
|
if stream == 'stdout'
|
922
1342
|
return 'INFO'
|
923
1343
|
elsif stream == 'stderr'
|
@@ -1125,6 +1545,24 @@ module Fluent
|
|
1125
1545
|
[resource_labels, common_labels]
|
1126
1546
|
end
|
1127
1547
|
|
1548
|
+
def format(tag, time, record)
|
1549
|
+
[tag, time, record].to_msgpack
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
# Given a tag, returns the corresponding valid tag if possible, or nil if
|
1553
|
+
# the tag should be rejected. If 'require_valid_tags' is false, non-string
|
1554
|
+
# tags are converted to strings, and invalid characters are sanitized;
|
1555
|
+
# otherwise such tags are rejected.
|
1556
|
+
def sanitize_tag(tag)
|
1557
|
+
if @require_valid_tags &&
|
1558
|
+
(!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
|
1559
|
+
return nil
|
1560
|
+
end
|
1561
|
+
tag = convert_to_utf8(tag.to_s)
|
1562
|
+
tag = '_' if tag == ''
|
1563
|
+
tag
|
1564
|
+
end
|
1565
|
+
|
1128
1566
|
# For every original_label => new_label pair in the label_map, delete the
|
1129
1567
|
# original_label from the record if it exists, and extract the value to form
|
1130
1568
|
# a map with the new_label as the key.
|
@@ -1152,7 +1590,8 @@ module Fluent
|
|
1152
1590
|
entry.text_payload = record['log']
|
1153
1591
|
elsif is_json
|
1154
1592
|
entry.json_payload = record
|
1155
|
-
elsif
|
1593
|
+
elsif [CONTAINER_CONSTANTS[:resource_type],
|
1594
|
+
DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
|
1156
1595
|
record.key?('log')
|
1157
1596
|
entry.text_payload = record['log']
|
1158
1597
|
elsif record.size == 1 && record.key?('message')
|
@@ -1222,7 +1661,8 @@ module Fluent
|
|
1222
1661
|
entry.text_payload = convert_to_utf8(record['log'])
|
1223
1662
|
elsif is_json
|
1224
1663
|
entry.json_payload = struct_from_ruby(record)
|
1225
|
-
elsif
|
1664
|
+
elsif [CONTAINER_CONSTANTS[:resource_type],
|
1665
|
+
DOCKER_CONSTANTS[:resource_type]].include?(resource_type) &&
|
1226
1666
|
record.key?('log')
|
1227
1667
|
entry.text_payload = convert_to_utf8(record['log'])
|
1228
1668
|
elsif record.size == 1 && record.key?('message')
|
@@ -1235,7 +1675,7 @@ module Fluent
|
|
1235
1675
|
def log_name(tag, resource)
|
1236
1676
|
if resource.type == CLOUDFUNCTIONS_CONSTANTS[:resource_type]
|
1237
1677
|
tag = 'cloud-functions'
|
1238
|
-
elsif
|
1678
|
+
elsif resource.type == APPENGINE_CONSTANTS[:resource_type]
|
1239
1679
|
# Add a prefix to Managed VM logs to prevent namespace collisions.
|
1240
1680
|
tag = "#{APPENGINE_CONSTANTS[:service]}/#{tag}"
|
1241
1681
|
elsif resource.type == CONTAINER_CONSTANTS[:resource_type]
|
@@ -1265,6 +1705,9 @@ module Fluent
|
|
1265
1705
|
elsif resource_type == ML_CONSTANTS[:resource_type]
|
1266
1706
|
label_prefix = ML_CONSTANTS[:service]
|
1267
1707
|
labels_to_extract = %w(job_id task_name)
|
1708
|
+
elsif resource_type == DOCKER_CONSTANTS[:resource_type]
|
1709
|
+
label_prefix = DOCKER_CONSTANTS[:service]
|
1710
|
+
labels_to_extract = %w(container_id)
|
1268
1711
|
else
|
1269
1712
|
return extracted_labels
|
1270
1713
|
end
|