fluent-plugin-vadimberezniker-gcp 0.1.0
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 +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
|