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.
@@ -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