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 +4 -4
- data/Gemfile.lock +21 -21
- data/fluent-plugin-google-cloud.gemspec +5 -4
- data/lib/fluent/plugin/out_google_cloud.rb +812 -360
- data/test/plugin/base_test.rb +389 -77
- data/test/plugin/constants.rb +163 -0
- metadata +31 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b1a26eab783c87890f78500848a29048b4ab615
|
4
|
+
data.tar.gz: 81711bc2e00bcf222b7249bde80d78c6bab34957
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
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 (
|
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.
|
22
|
+
cool.io (1.5.1)
|
22
23
|
crack (0.4.3)
|
23
24
|
safe_yaml (~> 1.0.0)
|
24
|
-
|
25
|
+
excon (0.57.1)
|
26
|
+
faraday (0.12.2)
|
25
27
|
multipart-post (>= 1.2, < 3)
|
26
|
-
fluentd (0.14.
|
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.
|
50
|
+
google-cloud-logging (0.24.1)
|
48
51
|
google-cloud-core (~> 0.21.1)
|
49
|
-
google-gax (~> 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.
|
56
|
-
|
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.
|
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 (
|
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
|
156
|
+
webmock (~> 2.3.1)
|
157
157
|
|
158
158
|
BUNDLED WITH
|
159
|
-
1.15.
|
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.
|
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', '
|
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
|
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
|
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.
|
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
|
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
|
-
|
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
|
-
#
|
319
|
-
#
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
-
#
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
#
|
337
|
-
@
|
338
|
-
|
339
|
-
#
|
340
|
-
#
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
#
|
348
|
-
#
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
417
|
-
|
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=#{@
|
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 =
|
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
|
-
|
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
|
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.
|
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
|
-
|
860
|
-
|
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
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
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
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
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
|
983
|
-
|
984
|
-
stream = entry_common_labels[
|
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
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
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
|
-
|
1185
|
-
|
1186
|
-
|
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
|
1194
|
-
# a map with the new_label as the key.
|
1195
|
-
def
|
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
|
-
|
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
|
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
|
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
|
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.
|