fluent-plugin-vadimberezniker-gcp 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING +24 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.rdoc +53 -0
- data/Rakefile +43 -0
- data/fluent-plugin-google-cloud.gemspec +43 -0
- data/fluent-plugin-vadimberezniker-gcp-0.13.2.gem +0 -0
- data/lib/fluent/plugin/common.rb +399 -0
- data/lib/fluent/plugin/filter_add_insert_ids.rb +86 -0
- data/lib/fluent/plugin/filter_analyze_config.rb +410 -0
- data/lib/fluent/plugin/in_object_space_dump.rb +62 -0
- data/lib/fluent/plugin/monitoring.rb +265 -0
- data/lib/fluent/plugin/out_google_cloud.rb +2209 -0
- data/lib/fluent/plugin/statusz.rb +124 -0
- data/test/helper.rb +46 -0
- data/test/plugin/asserts.rb +87 -0
- data/test/plugin/base_test.rb +2680 -0
- data/test/plugin/constants.rb +1114 -0
- data/test/plugin/data/c31e573fd7f62ed495c9ca3821a5a85cb036dee1-privatekey.p12 +0 -0
- data/test/plugin/data/credentials.json +7 -0
- data/test/plugin/data/google-fluentd-baseline.conf +24 -0
- data/test/plugin/data/google-fluentd-custom.conf +40 -0
- data/test/plugin/data/iam-credentials.json +11 -0
- data/test/plugin/data/invalid_credentials.json +8 -0
- data/test/plugin/data/new-style-credentials.json +12 -0
- data/test/plugin/test_driver.rb +56 -0
- data/test/plugin/test_filter_add_insert_ids.rb +137 -0
- data/test/plugin/test_filter_analyze_config.rb +257 -0
- data/test/plugin/test_out_google_cloud.rb +465 -0
- data/test/plugin/test_out_google_cloud_grpc.rb +478 -0
- data/test/plugin/utils.rb +148 -0
- metadata +347 -0
@@ -0,0 +1,410 @@
|
|
1
|
+
# Copyright 2020 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'fileutils'
|
16
|
+
require 'fluent/config'
|
17
|
+
require 'fluent/config/v1_parser'
|
18
|
+
require 'fluent/plugin_helper'
|
19
|
+
require 'googleauth'
|
20
|
+
require 'google/apis/logging_v2'
|
21
|
+
require 'open-uri'
|
22
|
+
require 'set'
|
23
|
+
|
24
|
+
require_relative 'common'
|
25
|
+
require_relative 'monitoring'
|
26
|
+
|
27
|
+
module Fluent
|
28
|
+
# Fluentd filter plugin to analyze configuration usage.
|
29
|
+
#
|
30
|
+
# For documentation on inspecting parsed configuration elements, see
|
31
|
+
# https://www.rubydoc.info/github/fluent/fluentd/Fluent/Config/Element
|
32
|
+
class AnalyzeConfigFilter < Filter
|
33
|
+
include Fluent::Config
|
34
|
+
Fluent::Plugin.register_filter('analyze_config', self)
|
35
|
+
|
36
|
+
# Required for the timer_execute method below
|
37
|
+
include PluginHelper::Mixin
|
38
|
+
helpers :timer
|
39
|
+
|
40
|
+
module Constants
|
41
|
+
PREFIX = 'agent.googleapis.com/agent/internal/logging/config'.freeze
|
42
|
+
|
43
|
+
# Built-in plugins that are ok to reference in metrics.
|
44
|
+
KNOWN_PLUGINS = {
|
45
|
+
'filter' => Set[
|
46
|
+
'geoip',
|
47
|
+
'grep',
|
48
|
+
'parser',
|
49
|
+
'record_transformer',
|
50
|
+
'stdout',
|
51
|
+
],
|
52
|
+
'match' => Set[
|
53
|
+
'copy',
|
54
|
+
'elasticsearch',
|
55
|
+
'exec',
|
56
|
+
'exec_filter',
|
57
|
+
'file',
|
58
|
+
'forward',
|
59
|
+
'http',
|
60
|
+
'kafka',
|
61
|
+
'mongo',
|
62
|
+
'mongo_replset',
|
63
|
+
'null',
|
64
|
+
'relabel',
|
65
|
+
'rewrite_tag_filter',
|
66
|
+
'roundrobin',
|
67
|
+
's3',
|
68
|
+
'secondary_file',
|
69
|
+
'stdout',
|
70
|
+
'webhdfs',
|
71
|
+
],
|
72
|
+
'source' => Set[
|
73
|
+
'dummy',
|
74
|
+
'exec',
|
75
|
+
'forward',
|
76
|
+
'http',
|
77
|
+
'monitor_agent',
|
78
|
+
'syslog',
|
79
|
+
'tail',
|
80
|
+
'tcp',
|
81
|
+
'udp',
|
82
|
+
'unix',
|
83
|
+
'windows_eventlog',
|
84
|
+
]
|
85
|
+
}.freeze
|
86
|
+
|
87
|
+
# For Google plugins, we collect metrics on the params listed here.
|
88
|
+
GOOGLE_PLUGIN_PARAMS = {
|
89
|
+
'google_cloud' => %w[
|
90
|
+
adjust_invalid_timestamps
|
91
|
+
auth_method
|
92
|
+
autoformat_stackdriver_trace
|
93
|
+
coerce_to_utf8
|
94
|
+
detect_json
|
95
|
+
enable_monitoring
|
96
|
+
gcm_service_address
|
97
|
+
grpc_compression_algorithm
|
98
|
+
http_request_key
|
99
|
+
insert_id_key
|
100
|
+
label_map
|
101
|
+
labels
|
102
|
+
labels_key
|
103
|
+
logging_api_url
|
104
|
+
monitoring_type
|
105
|
+
non_utf8_replacement_string
|
106
|
+
operation_key
|
107
|
+
private_key_email
|
108
|
+
private_key_passphrase
|
109
|
+
private_key_path
|
110
|
+
project_id
|
111
|
+
source_location_key
|
112
|
+
span_id_key
|
113
|
+
statusz_port
|
114
|
+
trace_key
|
115
|
+
trace_sampled_key
|
116
|
+
use_grpc
|
117
|
+
use_metadata_service
|
118
|
+
vm_id
|
119
|
+
vm_name
|
120
|
+
zone
|
121
|
+
],
|
122
|
+
'detect_exceptions' => %w[
|
123
|
+
languages
|
124
|
+
max_bytes
|
125
|
+
max_lines
|
126
|
+
message
|
127
|
+
multiline_flush_interval
|
128
|
+
remove_tag_prefix
|
129
|
+
stream
|
130
|
+
]
|
131
|
+
}.freeze
|
132
|
+
end
|
133
|
+
|
134
|
+
include self::Constants
|
135
|
+
|
136
|
+
# Disable this warning to conform to fluentd config_param conventions.
|
137
|
+
# rubocop:disable Style/HashSyntax
|
138
|
+
|
139
|
+
# The root configuration file of google-fluentd package.
|
140
|
+
# This only applies to Linux.
|
141
|
+
config_param :google_fluentd_config_path,
|
142
|
+
:string,
|
143
|
+
:default => '/etc/google-fluentd/google-fluentd.conf'
|
144
|
+
# Baseline configuration for comparing with local
|
145
|
+
# customizations.
|
146
|
+
config_param :google_fluentd_baseline_config_path,
|
147
|
+
:string,
|
148
|
+
:default => '/etc/google-fluentd/baseline/google-fluentd.conf'
|
149
|
+
|
150
|
+
# What system to use when collecting metrics. Possible values are:
|
151
|
+
# - 'prometheus', in this case default registry in the Prometheus
|
152
|
+
# client library is used, without actually exposing the endpoint
|
153
|
+
# to serve metrics in the Prometheus format.
|
154
|
+
# - any other value will result in the absence of metrics.
|
155
|
+
config_param :monitoring_type, :string,
|
156
|
+
:default => Monitoring::PrometheusMonitoringRegistry.name
|
157
|
+
|
158
|
+
# Override for the Google Cloud Monitoring service hostname, or
|
159
|
+
# `nil` to leave as the default.
|
160
|
+
config_param :gcm_service_address, :string, :default => nil
|
161
|
+
|
162
|
+
# rubocop:enable Style/HashSyntax
|
163
|
+
|
164
|
+
def start
|
165
|
+
super
|
166
|
+
@log = $log # rubocop:disable Style/GlobalVars
|
167
|
+
|
168
|
+
@log.info(
|
169
|
+
'analyze_config plugin: Started the plugin to analyze configuration.'
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse_config(path)
|
174
|
+
data = File.open(path, 'r', &:read)
|
175
|
+
fname = File.basename(path)
|
176
|
+
basepath = File.dirname(path)
|
177
|
+
eval_context = Kernel.binding
|
178
|
+
# Override instance_eval so that LiteralParser does not actually
|
179
|
+
# evaluate the embedded Ruby, but instead just returns the
|
180
|
+
# source string. See
|
181
|
+
# https://github.com/fluent/fluentd/blob/master/lib/fluent/config/literal_parser.rb
|
182
|
+
def eval_context.instance_eval(code)
|
183
|
+
code
|
184
|
+
end
|
185
|
+
Fluent::Config::V1Parser.parse(data, fname, basepath, eval_context)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns a name for identifying plugins we ship by default.
|
189
|
+
def default_plugin_name(conf_element)
|
190
|
+
case conf_element['@type']
|
191
|
+
when 'syslog'
|
192
|
+
"#{conf_element.name}/syslog/#{conf_element['protocol_type']}"
|
193
|
+
when 'tail'
|
194
|
+
"#{conf_element.name}/tail/#{File.basename(conf_element['pos_file'], '.pos')}"
|
195
|
+
else
|
196
|
+
"#{conf_element.name}/#{conf_element['@type']}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns a name for identifying plugins not in our default
|
201
|
+
# config. This should not contain arbitrary user-supplied data.
|
202
|
+
def custom_plugin_name(conf_element)
|
203
|
+
if KNOWN_PLUGINS.key?(conf_element.name) &&
|
204
|
+
KNOWN_PLUGINS[conf_element.name].include?(conf_element['@type'])
|
205
|
+
"#{conf_element.name}/#{conf_element['@type']}"
|
206
|
+
else
|
207
|
+
conf_element.name.to_s
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def embedded_ruby?(conf_element)
|
212
|
+
(conf_element.arg.include?('#{') ||
|
213
|
+
conf_element.any? { |_, v| v.include?('#{') } ||
|
214
|
+
conf_element.elements.any? { |e| embedded_ruby?(e) })
|
215
|
+
end
|
216
|
+
|
217
|
+
def configure(conf)
|
218
|
+
super
|
219
|
+
@log.info('analyze_config plugin: Starting to configure the plugin.')
|
220
|
+
if File.file?(@google_fluentd_config_path) &&
|
221
|
+
File.file?(@google_fluentd_baseline_config_path)
|
222
|
+
@log.info(
|
223
|
+
'analyze_config plugin: google-fluentd configuration file found at' \
|
224
|
+
" #{@google_fluentd_config_path}. " \
|
225
|
+
'google-fluentd baseline configuration file found at' \
|
226
|
+
" #{@google_fluentd_baseline_config_path}. " \
|
227
|
+
'google-fluentd Analyzing configuration.'
|
228
|
+
)
|
229
|
+
|
230
|
+
utils = Common::Utils.new(@log)
|
231
|
+
platform = utils.detect_platform(true)
|
232
|
+
project_id = utils.get_project_id(platform, nil)
|
233
|
+
vm_id = utils.get_vm_id(platform, nil)
|
234
|
+
zone = utils.get_location(platform, nil, true)
|
235
|
+
|
236
|
+
# All metadata parameters must now be set.
|
237
|
+
utils.check_required_metadata_variables(
|
238
|
+
platform, project_id, zone, vm_id
|
239
|
+
)
|
240
|
+
|
241
|
+
# Retrieve monitored resource.
|
242
|
+
# Fail over to retrieve monitored resource via the legacy path if we
|
243
|
+
# fail to get it from Metadata Agent.
|
244
|
+
resource = utils.determine_agent_level_monitored_resource_via_legacy(
|
245
|
+
platform, nil, false, vm_id, zone
|
246
|
+
)
|
247
|
+
|
248
|
+
unless Monitoring::MonitoringRegistryFactory.supports_monitoring_type(
|
249
|
+
@monitoring_type
|
250
|
+
)
|
251
|
+
@log.warn(
|
252
|
+
"analyze_config plugin: monitoring_type #{@monitoring_type} is " \
|
253
|
+
'unknown; there will be no metrics.'
|
254
|
+
)
|
255
|
+
end
|
256
|
+
|
257
|
+
@registry = Monitoring::MonitoringRegistryFactory.create(
|
258
|
+
@monitoring_type, project_id, resource, @gcm_service_address
|
259
|
+
)
|
260
|
+
# Export metrics every 60 seconds.
|
261
|
+
timer_execute(:export_config_analysis_metrics, 60) do
|
262
|
+
@registry.update_timestamps(PREFIX) if @registry.respond_to? :update_timestamps
|
263
|
+
@registry.export
|
264
|
+
end
|
265
|
+
|
266
|
+
@log.info('analyze_config plugin: Registering counters.')
|
267
|
+
enabled_plugins_counter = @registry.counter(
|
268
|
+
:enabled_plugins,
|
269
|
+
%i[plugin_name is_default_plugin has_default_config has_ruby_snippet],
|
270
|
+
'Enabled plugins',
|
271
|
+
PREFIX,
|
272
|
+
'GAUGE'
|
273
|
+
)
|
274
|
+
@log.info(
|
275
|
+
'analyze_config plugin: registered enable_plugins counter. ' \
|
276
|
+
"#{enabled_plugins_counter}"
|
277
|
+
)
|
278
|
+
plugin_config_counter = @registry.counter(
|
279
|
+
:plugin_config,
|
280
|
+
%i[plugin_name param is_present has_default_config],
|
281
|
+
'Configuration parameter usage for plugins relevant to Google Cloud.',
|
282
|
+
PREFIX,
|
283
|
+
'GAUGE'
|
284
|
+
)
|
285
|
+
@log.info('analyze_config plugin: registered plugin_config counter. ' \
|
286
|
+
"#{plugin_config_counter}")
|
287
|
+
config_bool_values_counter = @registry.counter(
|
288
|
+
:config_bool_values,
|
289
|
+
%i[plugin_name param value],
|
290
|
+
'Values for bool parameters in Google Cloud plugins',
|
291
|
+
PREFIX,
|
292
|
+
'GAUGE'
|
293
|
+
)
|
294
|
+
@log.info('analyze_config plugin: registered config_bool_values ' \
|
295
|
+
"counter. #{config_bool_values_counter}")
|
296
|
+
|
297
|
+
config = parse_config(@google_fluentd_config_path)
|
298
|
+
@log.debug(
|
299
|
+
'analyze_config plugin: successfully parsed google-fluentd' \
|
300
|
+
" configuration file at #{@google_fluentd_config_path}. #{config}"
|
301
|
+
)
|
302
|
+
baseline_config = parse_config(@google_fluentd_baseline_config_path)
|
303
|
+
@log.debug(
|
304
|
+
'analyze_config plugin: successfully parsed google-fluentd' \
|
305
|
+
' baseline configuration file at' \
|
306
|
+
" #{@google_fluentd_baseline_config_path}: #{baseline_config}"
|
307
|
+
)
|
308
|
+
|
309
|
+
# Create hash of all baseline elements by their plugin names.
|
310
|
+
baseline_elements = Hash[baseline_config.elements.collect do |e|
|
311
|
+
[default_plugin_name(e), e]
|
312
|
+
end]
|
313
|
+
baseline_google_element = baseline_config.elements.find do |e|
|
314
|
+
e['@type'] == 'google_cloud'
|
315
|
+
end
|
316
|
+
|
317
|
+
# Look at each top-level config element and see whether it
|
318
|
+
# matches the baseline value.
|
319
|
+
#
|
320
|
+
# Note on custom configurations: If the plugin has a custom
|
321
|
+
# value (e.g. if a tail plugin has pos_file
|
322
|
+
# /var/lib/google-fluentd/pos/my-custom-value.pos), then the
|
323
|
+
# default_plugin_name (e.g. source/tail/my-custom-value) won't
|
324
|
+
# be a key in baseline_elements below, so it won't be
|
325
|
+
# used. Instead it will use the custom_plugin_name
|
326
|
+
# (e.g. source/tail).
|
327
|
+
config.elements.each do |e|
|
328
|
+
plugin_name = default_plugin_name(e)
|
329
|
+
if baseline_elements.key?(plugin_name)
|
330
|
+
is_default_plugin = true
|
331
|
+
has_default_config = (baseline_elements[plugin_name] == e)
|
332
|
+
else
|
333
|
+
plugin_name = custom_plugin_name(e)
|
334
|
+
is_default_plugin = false
|
335
|
+
has_default_config = false
|
336
|
+
end
|
337
|
+
enabled_plugins_counter.increment(
|
338
|
+
labels: {
|
339
|
+
plugin_name: plugin_name,
|
340
|
+
is_default_plugin: is_default_plugin,
|
341
|
+
has_default_config: has_default_config,
|
342
|
+
has_ruby_snippet: embedded_ruby?(e)
|
343
|
+
},
|
344
|
+
by: 1
|
345
|
+
)
|
346
|
+
|
347
|
+
# Additional metric for Google plugins (google_cloud and
|
348
|
+
# detect_exceptions).
|
349
|
+
next unless GOOGLE_PLUGIN_PARAMS.key?(e['@type'])
|
350
|
+
|
351
|
+
GOOGLE_PLUGIN_PARAMS[e['@type']].each do |p|
|
352
|
+
plugin_config_counter.increment(
|
353
|
+
labels: {
|
354
|
+
plugin_name: e['@type'],
|
355
|
+
param: p,
|
356
|
+
is_present: e.key?(p),
|
357
|
+
has_default_config: (e.key?(p) &&
|
358
|
+
baseline_google_element.key?(p) &&
|
359
|
+
e[p] == baseline_google_element[p])
|
360
|
+
},
|
361
|
+
by: 1
|
362
|
+
)
|
363
|
+
next unless e.key?(p) && %w[true false].include?(e[p])
|
364
|
+
|
365
|
+
config_bool_values_counter.increment(
|
366
|
+
labels: {
|
367
|
+
plugin_name: e['@type'],
|
368
|
+
param: p,
|
369
|
+
value: e[p] == 'true'
|
370
|
+
},
|
371
|
+
by: 1
|
372
|
+
)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
@log.info(
|
376
|
+
'analyze_config plugin: Successfully finished analyzing config.'
|
377
|
+
)
|
378
|
+
else
|
379
|
+
@log.info(
|
380
|
+
'analyze_config plugin: google-fluentd configuration file does not ' \
|
381
|
+
"exist at #{@google_fluentd_config_path} or google-fluentd " \
|
382
|
+
'baseline configuration file does not exist at' \
|
383
|
+
" #{@google_fluentd_baseline_config_path}. Skipping configuration " \
|
384
|
+
'analysis.'
|
385
|
+
)
|
386
|
+
end
|
387
|
+
rescue StandardError => e
|
388
|
+
# Do not crash the agent due to configuration analysis failures.
|
389
|
+
@log.warn(
|
390
|
+
'analyze_config plugin: Failed to optionally analyze the ' \
|
391
|
+
"google-fluentd configuration file. Proceeding anyway. Error: #{e}. " \
|
392
|
+
"Trace: #{e.backtrace}"
|
393
|
+
)
|
394
|
+
end
|
395
|
+
|
396
|
+
def shutdown
|
397
|
+
super
|
398
|
+
# Export metrics on shutdown. This is a best-effort attempt, and it might
|
399
|
+
# fail, for instance if there was a recent write to the same time series.
|
400
|
+
@registry&.export
|
401
|
+
end
|
402
|
+
|
403
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
404
|
+
def filter(tag, time, record)
|
405
|
+
# Skip the actual filtering process.
|
406
|
+
record
|
407
|
+
end
|
408
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
409
|
+
end
|
410
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright 2019 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
require 'fluent/plugin/input'
|
15
|
+
require 'objspace'
|
16
|
+
|
17
|
+
module Fluent
|
18
|
+
# Dump out all live objects to json files. Each file is a snapshot of the Ruby
|
19
|
+
# heap at that time. See http://tmm1.net/ruby21-objspace/ for more details.
|
20
|
+
class ObjectSpaceDumpInput < Fluent::Plugin::Input
|
21
|
+
Fluent::Plugin.register_input('object_space_dump', self)
|
22
|
+
|
23
|
+
helpers :timer
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
super
|
27
|
+
|
28
|
+
ObjectSpace.trace_object_allocations_start
|
29
|
+
end
|
30
|
+
|
31
|
+
# Make sure you have enough disk space, because these files are large
|
32
|
+
# (roughly 50MB).
|
33
|
+
config_param :emit_interval, :time, default: 3600
|
34
|
+
|
35
|
+
def multi_workers_ready?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def start
|
40
|
+
super
|
41
|
+
|
42
|
+
# Dump during startup. The timer only fires after @emit_interval.
|
43
|
+
on_timer
|
44
|
+
timer_execute(:object_space_dump_input, @emit_interval,
|
45
|
+
&method(:on_timer))
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_timer
|
49
|
+
GC.start
|
50
|
+
# Use Tempfile.create to open the file, in order to preserve the file.
|
51
|
+
file = Tempfile.create(["heap-#{fluentd_worker_id}-", '.json'])
|
52
|
+
begin
|
53
|
+
log.info 'dumping object space to',
|
54
|
+
filepath: file.path,
|
55
|
+
worker: fluentd_worker_id
|
56
|
+
ObjectSpace.dump_all(output: file)
|
57
|
+
ensure
|
58
|
+
file.close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|