logstash-core 2.2.4.snapshot2-java → 2.3.0-java
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.
Potentially problematic release.
This version of logstash-core might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/logstash-core/version.rb +1 -1
- data/lib/logstash/agent.rb +248 -140
- data/lib/logstash/config/defaults.rb +8 -0
- data/lib/logstash/config/loader.rb +90 -0
- data/lib/logstash/config/mixin.rb +44 -15
- data/lib/logstash/output_delegator.rb +1 -1
- data/lib/logstash/pipeline.rb +29 -28
- data/lib/logstash/runner.rb +5 -0
- data/lib/logstash/shutdown_watcher.rb +3 -2
- data/lib/logstash/special_agent.rb +8 -0
- data/lib/logstash/version.rb +1 -1
- data/locales/en.yml +16 -6
- data/logstash-core.gemspec +2 -2
- data/spec/logstash/agent_spec.rb +278 -34
- data/spec/logstash/config/loader_spec.rb +36 -0
- data/spec/logstash/config/mixin_spec.rb +76 -4
- data/spec/logstash/json_spec.rb +15 -0
- data/spec/logstash/pipeline_spec.rb +2 -2
- data/spec/logstash/plugin_spec.rb +1 -1
- data/spec/logstash/runner_spec.rb +13 -22
- data/spec/logstash/shutdown_watcher_spec.rb +4 -0
- metadata +36 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62ad94033e414179752f4aa8997e1453d707292c
|
4
|
+
data.tar.gz: 4d3dbe8a9096eed9fc2cf873b4115eb00d26adc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27671a2f16ee352e488edad4842c8ef8f563f7ccd1fbff9363aaac89c255960865536eacb3d825c74a60e820e9412406467e6270e3eae8444370fca983ab9f6a
|
7
|
+
data.tar.gz: 560cdb36d4971df6eb6a3f1b1359ac0599ce89887f68834762fb6ecd5e49e2f5e8e197236af2f1e1cb5b94c279aab1bf477c369572b5f90970e0570d7048c003
|
data/lib/logstash/agent.rb
CHANGED
@@ -3,13 +3,16 @@ require "clamp" # gem 'clamp'
|
|
3
3
|
require "logstash/environment"
|
4
4
|
require "logstash/errors"
|
5
5
|
require "logstash/config/cpu_core_strategy"
|
6
|
+
require "stud/trap"
|
7
|
+
require "logstash/config/loader"
|
6
8
|
require "uri"
|
7
9
|
require "net/http"
|
8
10
|
require "logstash/pipeline"
|
9
|
-
LogStash::Environment.load_locale!
|
10
11
|
|
11
12
|
class LogStash::Agent < Clamp::Command
|
12
13
|
|
14
|
+
attr_reader :pipelines
|
15
|
+
|
13
16
|
DEFAULT_INPUT = "input { stdin { type => stdin } }"
|
14
17
|
DEFAULT_OUTPUT = "output { stdout { codec => rubydebug } }"
|
15
18
|
|
@@ -27,6 +30,7 @@ class LogStash::Agent < Clamp::Command
|
|
27
30
|
:attribute_name => :pipeline_workers,
|
28
31
|
:default => LogStash::Pipeline::DEFAULT_SETTINGS[:default_pipeline_workers]
|
29
32
|
|
33
|
+
|
30
34
|
option ["-b", "--pipeline-batch-size"], "SIZE",
|
31
35
|
I18n.t("logstash.agent.flag.pipeline-batch-size"),
|
32
36
|
:attribute_name => :pipeline_batch_size,
|
@@ -54,10 +58,6 @@ class LogStash::Agent < Clamp::Command
|
|
54
58
|
option "--verbose", :flag, I18n.t("logstash.agent.flag.verbose")
|
55
59
|
option "--debug", :flag, I18n.t("logstash.agent.flag.debug")
|
56
60
|
|
57
|
-
option "--debug-config", :flag,
|
58
|
-
I18n.t("logstash.runner.flag.debug_config"),
|
59
|
-
:attribute_name => :debug_config, :default => false
|
60
|
-
|
61
61
|
option ["-V", "--version"], :flag,
|
62
62
|
I18n.t("logstash.agent.flag.version")
|
63
63
|
|
@@ -75,9 +75,21 @@ class LogStash::Agent < Clamp::Command
|
|
75
75
|
:attribute_name => :unsafe_shutdown,
|
76
76
|
:default => false
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
option ["-r", "--[no-]auto-reload"], :flag,
|
79
|
+
I18n.t("logstash.agent.flag.auto_reload"),
|
80
|
+
:attribute_name => :auto_reload, :default => false
|
81
|
+
|
82
|
+
option ["--reload-interval"], "RELOAD_INTERVAL",
|
83
|
+
I18n.t("logstash.agent.flag.reload_interval"),
|
84
|
+
:attribute_name => :reload_interval, :default => 3, &:to_i
|
85
|
+
|
86
|
+
def initialize(*params)
|
87
|
+
super(*params)
|
88
|
+
@logger = Cabin::Channel.get(LogStash)
|
89
|
+
@pipelines = {}
|
90
|
+
@pipeline_settings ||= { :pipeline_id => "main" }
|
91
|
+
@upgrade_mutex = Mutex.new
|
92
|
+
@config_loader = LogStash::Config::Loader.new(@logger)
|
81
93
|
end
|
82
94
|
|
83
95
|
def pipeline_workers=(pipeline_workers_value)
|
@@ -107,7 +119,6 @@ class LogStash::Agent < Clamp::Command
|
|
107
119
|
raise LogStash::ConfigurationError, message
|
108
120
|
end # def warn
|
109
121
|
|
110
|
-
# Emit a failure message and abort.
|
111
122
|
def fail(message)
|
112
123
|
raise LogStash::ConfigurationError, message
|
113
124
|
end # def fail
|
@@ -118,7 +129,6 @@ class LogStash::Agent < Clamp::Command
|
|
118
129
|
require "logstash/pipeline"
|
119
130
|
require "cabin" # gem 'cabin'
|
120
131
|
require "logstash/plugin"
|
121
|
-
@logger = Cabin::Channel.get(LogStash)
|
122
132
|
|
123
133
|
LogStash::ShutdownWatcher.unsafe_shutdown = unsafe_shutdown?
|
124
134
|
LogStash::ShutdownWatcher.logger = @logger
|
@@ -144,70 +154,63 @@ class LogStash::Agent < Clamp::Command
|
|
144
154
|
end
|
145
155
|
|
146
156
|
# You must specify a config_string or config_path
|
147
|
-
if
|
148
|
-
fail(
|
157
|
+
if config_string.nil? && config_path.nil?
|
158
|
+
fail(I18n.t("logstash.agent.missing-configuration"))
|
149
159
|
end
|
150
160
|
|
151
|
-
|
161
|
+
if auto_reload? && config_path.nil?
|
162
|
+
# there's nothing to reload
|
163
|
+
fail(I18n.t("logstash.agent.reload-without-config-path"))
|
164
|
+
end
|
152
165
|
|
153
|
-
if
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
@
|
162
|
-
|
163
|
-
# include a default stdout output if no outputs given
|
164
|
-
if @config_string !~ /output *{/
|
165
|
-
@config_string += DEFAULT_OUTPUT
|
166
|
+
if config_test?
|
167
|
+
config_loader = LogStash::Config::Loader.new(@logger)
|
168
|
+
config_str = config_loader.format_config(config_path, config_string)
|
169
|
+
config_error = LogStash::Pipeline.config_valid?(config_str)
|
170
|
+
if config_error == true
|
171
|
+
@logger.terminal "Configuration OK"
|
172
|
+
return 0
|
173
|
+
else
|
174
|
+
@logger.fatal I18n.t("logstash.error", :error => config_error)
|
175
|
+
return 1
|
166
176
|
end
|
167
177
|
end
|
168
178
|
|
179
|
+
register_pipeline("main", @pipeline_settings.merge({
|
180
|
+
:config_string => config_string,
|
181
|
+
:config_path => config_path
|
182
|
+
}))
|
169
183
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
fail("Configuration problem.")
|
174
|
-
end
|
184
|
+
sigint_id = trap_sigint()
|
185
|
+
sigterm_id = trap_sigterm()
|
186
|
+
sighup_id = trap_sighup()
|
175
187
|
|
176
|
-
|
177
|
-
sigint_id = Stud::trap("INT") do
|
188
|
+
@logger.unsubscribe(stdout_logs) if show_startup_errors
|
178
189
|
|
179
|
-
|
180
|
-
@logger.fatal(I18n.t("logstash.agent.forced_sigint"))
|
181
|
-
exit
|
182
|
-
else
|
183
|
-
@logger.warn(I18n.t("logstash.agent.sigint"))
|
184
|
-
Thread.new(@logger) {|logger| sleep 5; logger.warn(I18n.t("logstash.agent.slow_shutdown")) }
|
185
|
-
@interrupted_once = true
|
186
|
-
shutdown(pipeline)
|
187
|
-
end
|
188
|
-
end
|
190
|
+
@logger.info("starting agent")
|
189
191
|
|
190
|
-
|
191
|
-
sigterm_id = Stud::trap("TERM") do
|
192
|
-
@logger.warn(I18n.t("logstash.agent.sigterm"))
|
193
|
-
shutdown(pipeline)
|
194
|
-
end
|
192
|
+
start_pipelines
|
195
193
|
|
196
|
-
|
197
|
-
@logger.info(I18n.t("logstash.agent.sighup"))
|
198
|
-
configure_logging(log_file)
|
199
|
-
end
|
194
|
+
return 1 if clean_state?
|
200
195
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
196
|
+
@thread = Thread.current # this var is implicilty used by Stud.stop?
|
197
|
+
|
198
|
+
Stud.stoppable_sleep(reload_interval) # sleep before looping
|
199
|
+
|
200
|
+
if auto_reload?
|
201
|
+
Stud.interval(reload_interval) { reload_state! }
|
202
|
+
else
|
203
|
+
while !Stud.stop?
|
204
|
+
if clean_state? || running_pipelines?
|
205
|
+
sleep 0.5
|
206
|
+
else
|
207
|
+
break
|
208
|
+
end
|
209
|
+
end
|
205
210
|
end
|
206
211
|
|
207
|
-
|
212
|
+
shutdown
|
208
213
|
|
209
|
-
# TODO(sissel): Get pipeline completion status.
|
210
|
-
pipeline.run
|
211
214
|
return 0
|
212
215
|
rescue LogStash::ConfigurationError => e
|
213
216
|
@logger.unsubscribe(stdout_logs) if show_startup_errors
|
@@ -224,51 +227,14 @@ class LogStash::Agent < Clamp::Command
|
|
224
227
|
@log_fd.close if @log_fd
|
225
228
|
Stud::untrap("INT", sigint_id) unless sigint_id.nil?
|
226
229
|
Stud::untrap("TERM", sigterm_id) unless sigterm_id.nil?
|
230
|
+
Stud::untrap("HUP", sighup_id) unless sighup_id.nil?
|
227
231
|
end # def execute
|
228
232
|
|
229
|
-
def shutdown(pipeline)
|
230
|
-
pipeline.shutdown do
|
231
|
-
::LogStash::ShutdownWatcher.start(pipeline)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
def show_version
|
236
|
-
show_version_logstash
|
237
|
-
|
238
|
-
if [:info, :debug].include?(verbosity?) || debug? || verbose?
|
239
|
-
show_version_ruby
|
240
|
-
show_version_java if LogStash::Environment.jruby?
|
241
|
-
show_gems if [:debug].include?(verbosity?) || debug?
|
242
|
-
end
|
243
|
-
end # def show_version
|
244
|
-
|
245
|
-
def show_version_logstash
|
246
|
-
require "logstash/version"
|
247
|
-
puts "logstash #{LOGSTASH_VERSION}"
|
248
|
-
end # def show_version_logstash
|
249
|
-
|
250
|
-
def show_version_ruby
|
251
|
-
puts RUBY_DESCRIPTION
|
252
|
-
end # def show_version_ruby
|
253
|
-
|
254
|
-
def show_version_java
|
255
|
-
properties = java.lang.System.getProperties
|
256
|
-
puts "java #{properties["java.version"]} (#{properties["java.vendor"]})"
|
257
|
-
puts "jvm #{properties["java.vm.name"]} / #{properties["java.vm.version"]}"
|
258
|
-
end # def show_version_java
|
259
|
-
|
260
|
-
def show_gems
|
261
|
-
require "rubygems"
|
262
|
-
Gem::Specification.each do |spec|
|
263
|
-
puts "gem #{spec.name} #{spec.version}"
|
264
|
-
end
|
265
|
-
end # def show_gems
|
266
233
|
|
267
234
|
# Do any start-time configuration.
|
268
235
|
#
|
269
236
|
# Log file stuff, plugin path checking, etc.
|
270
237
|
def configure
|
271
|
-
@pipeline_settings[:debug_config] = debug_config?
|
272
238
|
configure_logging(log_file)
|
273
239
|
configure_plugin_paths(plugin_paths)
|
274
240
|
end # def configure
|
@@ -329,63 +295,205 @@ class LogStash::Agent < Clamp::Command
|
|
329
295
|
end
|
330
296
|
end
|
331
297
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
local_config(path)
|
339
|
-
when /http/ then
|
340
|
-
fetch_config(uri)
|
341
|
-
when "file" then
|
342
|
-
local_config(uri.path)
|
298
|
+
## Signal Trapping ##
|
299
|
+
def trap_sigint
|
300
|
+
Stud::trap("INT") do
|
301
|
+
if @interrupted_once
|
302
|
+
@logger.fatal(I18n.t("logstash.agent.forced_sigint"))
|
303
|
+
exit
|
343
304
|
else
|
344
|
-
|
305
|
+
@logger.warn(I18n.t("logstash.agent.sigint"))
|
306
|
+
Thread.new(@logger) {|logger| sleep 5; logger.warn(I18n.t("logstash.agent.slow_shutdown")) }
|
307
|
+
@interrupted_once = true
|
308
|
+
Stud.stop!(@thread)
|
345
309
|
end
|
346
|
-
rescue URI::InvalidURIError
|
347
|
-
# fallback for windows.
|
348
|
-
# if the parsing of the file failed we assume we can reach it locally.
|
349
|
-
# some relative path on windows arent parsed correctly (.\logstash.conf)
|
350
|
-
local_config(path)
|
351
310
|
end
|
352
311
|
end
|
353
312
|
|
354
|
-
def
|
355
|
-
|
356
|
-
|
313
|
+
def trap_sigterm
|
314
|
+
Stud::trap("TERM") do
|
315
|
+
@logger.warn(I18n.t("logstash.agent.sigterm"))
|
316
|
+
Stud.stop!(@thread)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def trap_sighup
|
321
|
+
Stud::trap("HUP") do
|
322
|
+
@logger.warn(I18n.t("logstash.agent.sighup"))
|
323
|
+
reload_state!
|
324
|
+
end
|
325
|
+
end
|
357
326
|
|
358
|
-
|
359
|
-
|
327
|
+
## Pipeline CRUD ##
|
328
|
+
def shutdown(pipeline)
|
329
|
+
pipeline.shutdown do
|
330
|
+
::LogStash::ShutdownWatcher.start(pipeline)
|
360
331
|
end
|
332
|
+
end
|
333
|
+
#
|
334
|
+
# register_pipeline - adds a pipeline to the agent's state
|
335
|
+
# @param pipeline_id [String] pipeline string identifier
|
336
|
+
# @param settings [Hash] settings that will be passed when creating the pipeline.
|
337
|
+
# keys should be symbols such as :pipeline_workers and :pipeline_batch_delay
|
338
|
+
def register_pipeline(pipeline_id, settings)
|
339
|
+
pipeline = create_pipeline(settings.merge(:pipeline_id => pipeline_id))
|
340
|
+
return unless pipeline.is_a?(LogStash::Pipeline)
|
341
|
+
if @auto_reload && pipeline.non_reloadable_plugins.any?
|
342
|
+
@logger.error(I18n.t("logstash.agent.non_reloadable_config_register"),
|
343
|
+
:pipeline_id => pipeline_id,
|
344
|
+
:plugins => pipeline.non_reloadable_plugins.map(&:class))
|
345
|
+
return
|
346
|
+
end
|
347
|
+
@pipelines[pipeline_id] = pipeline
|
348
|
+
end
|
361
349
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
@logger.debug("Reading config file", :file => file)
|
371
|
-
cfg = File.read(file)
|
372
|
-
if !cfg.ascii_only? && !cfg.valid_encoding?
|
373
|
-
encoding_issue_files << file
|
350
|
+
def reload_state!
|
351
|
+
@upgrade_mutex.synchronize do
|
352
|
+
@pipelines.each do |pipeline_id, _|
|
353
|
+
begin
|
354
|
+
reload_pipeline!(pipeline_id)
|
355
|
+
rescue => e
|
356
|
+
@logger.error(I18n.t("oops"), :error => e, :backtrace => e.backtrace)
|
357
|
+
end
|
374
358
|
end
|
375
|
-
config << cfg + "\n"
|
376
359
|
end
|
377
|
-
|
378
|
-
|
360
|
+
end
|
361
|
+
|
362
|
+
def create_pipeline(settings)
|
363
|
+
begin
|
364
|
+
config = fetch_config(settings)
|
365
|
+
rescue => e
|
366
|
+
@logger.error("failed to fetch pipeline configuration", :message => e.message)
|
367
|
+
return
|
379
368
|
end
|
380
|
-
return config
|
381
|
-
end # def load_config
|
382
369
|
|
383
|
-
def fetch_config(uri)
|
384
370
|
begin
|
385
|
-
|
386
|
-
rescue
|
387
|
-
|
371
|
+
LogStash::Pipeline.new(config, settings)
|
372
|
+
rescue => e
|
373
|
+
@logger.error("fetched an invalid config", :config => config, :reason => e.message)
|
374
|
+
return
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def start_pipelines
|
379
|
+
@pipelines.each { |id, _| start_pipeline(id) }
|
380
|
+
end
|
381
|
+
|
382
|
+
def shutdown
|
383
|
+
shutdown_pipelines
|
384
|
+
end
|
385
|
+
|
386
|
+
def shutdown_pipelines
|
387
|
+
@pipelines.each { |id, _| stop_pipeline(id) }
|
388
|
+
end
|
389
|
+
|
390
|
+
def stop_pipeline(id)
|
391
|
+
pipeline = @pipelines[id]
|
392
|
+
return unless pipeline
|
393
|
+
@logger.log("stopping pipeline", :id => id)
|
394
|
+
pipeline.shutdown { LogStash::ShutdownWatcher.start(pipeline) }
|
395
|
+
@pipelines[id].thread.join
|
396
|
+
end
|
397
|
+
|
398
|
+
def running_pipelines?
|
399
|
+
@upgrade_mutex.synchronize do
|
400
|
+
@pipelines.select {|pipeline_id, _| running_pipeline?(pipeline_id) }.any?
|
388
401
|
end
|
389
402
|
end
|
390
403
|
|
404
|
+
def running_pipeline?(pipeline_id)
|
405
|
+
thread = @pipelines[pipeline_id].thread
|
406
|
+
thread.is_a?(Thread) && thread.alive?
|
407
|
+
end
|
408
|
+
|
409
|
+
def upgrade_pipeline(pipeline_id, new_pipeline)
|
410
|
+
stop_pipeline(pipeline_id)
|
411
|
+
@pipelines[pipeline_id] = new_pipeline
|
412
|
+
start_pipeline(pipeline_id)
|
413
|
+
end
|
414
|
+
|
415
|
+
def clean_state?
|
416
|
+
@pipelines.empty?
|
417
|
+
end
|
418
|
+
|
419
|
+
# since this method modifies the @pipelines hash it is
|
420
|
+
# wrapped in @upgrade_mutex in the parent call `reload_state!`
|
421
|
+
def reload_pipeline!(id)
|
422
|
+
old_pipeline = @pipelines[id]
|
423
|
+
new_pipeline = create_pipeline(old_pipeline.original_settings)
|
424
|
+
return if new_pipeline.nil?
|
425
|
+
|
426
|
+
if old_pipeline.config_str == new_pipeline.config_str
|
427
|
+
@logger.debug("no configuration change for pipeline",
|
428
|
+
:pipeline => id, :config => old_pipeline.config_str)
|
429
|
+
elsif new_pipeline.non_reloadable_plugins.any?
|
430
|
+
@logger.error(I18n.t("logstash.agent.non_reloadable_config_reload"),
|
431
|
+
:pipeline_id => id,
|
432
|
+
:plugins => new_pipeline.non_reloadable_plugins.map(&:class))
|
433
|
+
else
|
434
|
+
@logger.log("fetched new config for pipeline. upgrading..",
|
435
|
+
:pipeline => id, :config => new_pipeline.config_str)
|
436
|
+
upgrade_pipeline(id, new_pipeline)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def start_pipeline(id)
|
441
|
+
pipeline = @pipelines[id]
|
442
|
+
return unless pipeline.is_a?(LogStash::Pipeline)
|
443
|
+
return if pipeline.ready?
|
444
|
+
@logger.info("starting pipeline", :id => id)
|
445
|
+
Thread.new do
|
446
|
+
LogStash::Util.set_thread_name("pipeline.#{id}")
|
447
|
+
begin
|
448
|
+
pipeline.run
|
449
|
+
rescue => e
|
450
|
+
@logger.error("Pipeline aborted due to error", :exception => e, :backtrace => e.backtrace)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
sleep 0.01 until pipeline.ready?
|
454
|
+
end
|
455
|
+
|
456
|
+
## Pipeline Aux methods ##
|
457
|
+
def fetch_config(settings)
|
458
|
+
@config_loader.format_config(settings[:config_path], settings[:config_string])
|
459
|
+
end
|
460
|
+
|
461
|
+
private
|
462
|
+
def node_uuid
|
463
|
+
@node_uuid ||= SecureRandom.uuid
|
464
|
+
end
|
465
|
+
|
466
|
+
### Version actions ###
|
467
|
+
def show_version
|
468
|
+
show_version_logstash
|
469
|
+
|
470
|
+
if [:info, :debug].include?(verbosity?) || debug? || verbose?
|
471
|
+
show_version_ruby
|
472
|
+
show_version_java if LogStash::Environment.jruby?
|
473
|
+
show_gems if [:debug].include?(verbosity?) || debug?
|
474
|
+
end
|
475
|
+
end # def show_version
|
476
|
+
|
477
|
+
def show_version_logstash
|
478
|
+
require "logstash/version"
|
479
|
+
puts "logstash #{LOGSTASH_VERSION}"
|
480
|
+
end # def show_version_logstash
|
481
|
+
|
482
|
+
def show_version_ruby
|
483
|
+
puts RUBY_DESCRIPTION
|
484
|
+
end # def show_version_ruby
|
485
|
+
|
486
|
+
def show_version_java
|
487
|
+
properties = java.lang.System.getProperties
|
488
|
+
puts "java #{properties["java.version"]} (#{properties["java.vendor"]})"
|
489
|
+
puts "jvm #{properties["java.vm.name"]} / #{properties["java.vm.version"]}"
|
490
|
+
end # def show_version_java
|
491
|
+
|
492
|
+
def show_gems
|
493
|
+
require "rubygems"
|
494
|
+
Gem::Specification.each do |spec|
|
495
|
+
puts "gem #{spec.name} #{spec.version}"
|
496
|
+
end
|
497
|
+
end # def show_gems
|
498
|
+
|
391
499
|
end # class LogStash::Agent
|