fluent-plugin-vadimberezniker-gcp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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