fluentd 0.10.45 → 0.10.46

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/ChangeLog +13 -0
  4. data/Rakefile +18 -2
  5. data/fluentd.gemspec +3 -1
  6. data/lib/fluent/command/fluentd.rb +5 -0
  7. data/lib/fluent/config.rb +17 -333
  8. data/lib/fluent/config/basic_parser.rb +108 -0
  9. data/lib/fluent/config/configure_proxy.rb +145 -0
  10. data/lib/fluent/{config_dsl.rb → config/dsl.rb} +5 -1
  11. data/lib/fluent/config/element.rb +82 -0
  12. data/lib/fluent/config/error.rb +7 -0
  13. data/lib/fluent/config/literal_parser.rb +158 -0
  14. data/lib/fluent/config/parser.rb +96 -0
  15. data/lib/fluent/config/section.rb +115 -0
  16. data/lib/fluent/config/types.rb +86 -0
  17. data/lib/fluent/config/v1_parser.rb +156 -0
  18. data/lib/fluent/configurable.rb +108 -0
  19. data/lib/fluent/engine.rb +4 -3
  20. data/lib/fluent/load.rb +0 -1
  21. data/lib/fluent/parser.rb +15 -5
  22. data/lib/fluent/plugin/buf_memory.rb +13 -5
  23. data/lib/fluent/plugin/in_forward.rb +18 -5
  24. data/lib/fluent/plugin/in_http.rb +4 -2
  25. data/lib/fluent/plugin/in_tail.rb +1 -1
  26. data/lib/fluent/plugin/out_forward.rb +33 -29
  27. data/lib/fluent/registry.rb +76 -0
  28. data/lib/fluent/supervisor.rb +2 -1
  29. data/lib/fluent/test/base.rb +3 -1
  30. data/lib/fluent/version.rb +1 -1
  31. data/spec/config/config_parser_spec.rb +176 -0
  32. data/spec/config/configurable_spec.rb +373 -0
  33. data/spec/config/configure_proxy_spec.rb +96 -0
  34. data/spec/config/dsl_spec.rb +239 -0
  35. data/spec/config/helper.rb +50 -0
  36. data/spec/config/literal_parser_spec.rb +190 -0
  37. data/spec/config/section_spec.rb +97 -0
  38. data/spec/spec_helper.rb +60 -0
  39. data/test/plugin/{in_exec.rb → test_in_exec.rb} +0 -0
  40. data/test/plugin/{in_forward.rb → test_in_forward.rb} +5 -0
  41. data/test/plugin/{in_gc_stat.rb → test_in_gc_stat.rb} +0 -0
  42. data/test/plugin/{in_http.rb → test_in_http.rb} +0 -0
  43. data/test/plugin/{in_object_space.rb → test_in_object_space.rb} +0 -0
  44. data/test/plugin/{in_status.rb → test_in_status.rb} +0 -0
  45. data/test/plugin/{in_stream.rb → test_in_stream.rb} +0 -0
  46. data/test/plugin/{in_syslog.rb → test_in_syslog.rb} +0 -0
  47. data/test/plugin/{in_tail.rb → test_in_tail.rb} +0 -0
  48. data/test/plugin/{out_copy.rb → test_out_copy.rb} +0 -0
  49. data/test/plugin/{out_exec.rb → test_out_exec.rb} +0 -0
  50. data/test/plugin/{out_exec_filter.rb → test_out_exec_filter.rb} +0 -0
  51. data/test/plugin/{out_file.rb → test_out_file.rb} +0 -0
  52. data/test/plugin/{out_forward.rb → test_out_forward.rb} +15 -0
  53. data/test/plugin/{out_roundrobin.rb → test_out_roundrobin.rb} +0 -0
  54. data/test/plugin/{out_stdout.rb → test_out_stdout.rb} +0 -0
  55. data/test/plugin/{out_stream.rb → test_out_stream.rb} +0 -0
  56. data/test/scripts/fluent/plugin/parser_known.rb +3 -0
  57. data/test/{config.rb → test_config.rb} +1 -0
  58. data/test/{configdsl.rb → test_configdsl.rb} +1 -1
  59. data/test/{match.rb → test_match.rb} +0 -0
  60. data/test/{mixin.rb → test_mixin.rb} +0 -0
  61. data/test/{output.rb → test_output.rb} +0 -0
  62. data/test/{parser.rb → test_parser.rb} +22 -5
  63. metadata +114 -51
@@ -0,0 +1,108 @@
1
+ module Fluent
2
+ require 'fluent/config/configure_proxy'
3
+ require 'fluent/config/section'
4
+ require 'fluent/config/error'
5
+ require 'fluent/registry'
6
+
7
+ module Configurable
8
+ attr_reader :config
9
+
10
+ def self.included(mod)
11
+ mod.extend(ClassMethods)
12
+ end
13
+
14
+ def initialize
15
+ # to simulate implicit 'attr_accessor' by config_param / config_section and its value by config_set_default
16
+ proxy = self.class.merged_configure_proxy
17
+ proxy.params.keys.each do |name|
18
+ if proxy.defaults.has_key?(name)
19
+ instance_variable_set("@#{name}".to_sym, proxy.defaults[name])
20
+ end
21
+ end
22
+ proxy.sections.keys.each do |name|
23
+ subproxy = proxy.sections[name]
24
+ if subproxy.multi?
25
+ instance_variable_set("@#{subproxy.param_name}".to_sym, [])
26
+ else
27
+ instance_variable_set("@#{subproxy.param_name}".to_sym, nil)
28
+ end
29
+ end
30
+ end
31
+
32
+ def configure(conf)
33
+ @config = conf
34
+
35
+ logger = self.respond_to?(:log) ? log : $log
36
+ proxy = self.class.merged_configure_proxy
37
+
38
+ root = Fluent::Config::SectionGenerator.generate(proxy, conf, logger)
39
+ @config_root_section = root
40
+
41
+ root.instance_eval{ @params.keys }.each do |param_name|
42
+ varname = "@#{param_name}".to_sym
43
+ if (! root[param_name].nil?) || instance_variable_get(varname).nil?
44
+ instance_variable_set(varname, root[param_name])
45
+ end
46
+ end
47
+
48
+ self
49
+ end
50
+
51
+ CONFIG_TYPE_REGISTRY = Registry.new(:config_type, 'fluent/plugin/type_')
52
+
53
+ def self.register_type(type, callable = nil, &block)
54
+ callable ||= block
55
+ CONFIG_TYPE_REGISTRY.register(type, callable)
56
+ end
57
+
58
+ def self.lookup_type(type)
59
+ CONFIG_TYPE_REGISTRY.lookup(type)
60
+ end
61
+
62
+ module ClassMethods
63
+ def configure_proxy_map
64
+ map = {}
65
+ self.define_singleton_method(:configure_proxy_map){ map }
66
+ map
67
+ end
68
+
69
+ def configure_proxy(mod_name)
70
+ map = configure_proxy_map
71
+ unless map[mod_name]
72
+ proxy = Fluent::Config::ConfigureProxy.new(mod_name, required: true, multi: false)
73
+ map[mod_name] = proxy
74
+ end
75
+ map[mod_name]
76
+ end
77
+
78
+ def config_param(name, *args, &block)
79
+ configure_proxy(self.name).config_param(name, *args, &block)
80
+ attr_accessor name
81
+ end
82
+
83
+ def config_set_default(name, defval)
84
+ configure_proxy(self.name).config_set_default(name, defval)
85
+ end
86
+
87
+ def config_section(name, *args, &block)
88
+ configure_proxy(self.name).config_section(name, *args, &block)
89
+ attr_accessor configure_proxy(self.name).sections[name].param_name
90
+ end
91
+
92
+ def merged_configure_proxy
93
+ configurables = ancestors.reverse.select{ |a| a.respond_to?(:configure_proxy) }
94
+
95
+ # 'a.object_id.to_s' is to support anonymous class
96
+ # which created in tests to overwrite original behavior temporally
97
+ #
98
+ # p Module.new.name #=> nil
99
+ # p Class.new.name #=> nil
100
+ # p AnyGreatClass.dup.name #=> nil
101
+ configurables.map{ |a| a.configure_proxy(a.name || a.object_id.to_s) }.reduce(:merge)
102
+ end
103
+ end
104
+ end
105
+
106
+ # load default types
107
+ require 'fluent/config/types'
108
+ end
@@ -68,11 +68,12 @@ module Fluent
68
68
  }
69
69
  end
70
70
 
71
- def parse_config(io, fname, basepath=Dir.pwd)
71
+ def parse_config(io, fname, basepath = Dir.pwd, v1_config = false)
72
72
  conf = if fname =~ /\.rb$/
73
+ require 'fluent/config/dsl'
73
74
  Config::DSL::Parser.parse(io, File.join(basepath, fname))
74
75
  else
75
- Config.parse(io, fname, basepath)
76
+ Config.parse(io, fname, basepath, v1_config)
76
77
  end
77
78
  configure(conf)
78
79
  conf.check_not_fetched {|key,e|
@@ -144,7 +145,7 @@ module Fluent
144
145
  # this is not thread-safe but inconsistency doesn't
145
146
  # cause serious problems while locking causes.
146
147
  if @match_cache_keys.size >= MATCH_CACHE_SIZE
147
- @match_cache_keys.delete @match_cache_keys.shift
148
+ @match_cache.delete @match_cache_keys.shift
148
149
  end
149
150
  @match_cache[tag] = target
150
151
  @match_cache_keys << tag
@@ -23,7 +23,6 @@ require 'fluent/version'
23
23
  require 'fluent/log'
24
24
  require 'fluent/status'
25
25
  require 'fluent/config'
26
- require 'fluent/config_dsl'
27
26
  require 'fluent/engine'
28
27
  require 'fluent/mixin'
29
28
  require 'fluent/process'
@@ -16,6 +16,8 @@
16
16
  # limitations under the License.
17
17
  #
18
18
  module Fluent
19
+ require 'fluent/registry'
20
+
19
21
  class TextParser
20
22
  class TimeParser
21
23
  def initialize(time_format)
@@ -236,7 +238,11 @@ module Fluent
236
238
 
237
239
  if @time_key
238
240
  value = record.delete(@time_key)
239
- time = @mutex.synchronize { @time_parser.parse(value) }
241
+ time = if value.nil?
242
+ Engine.now
243
+ else
244
+ @mutex.synchronize { @time_parser.parse(value) }
245
+ end
240
246
  else
241
247
  time = Engine.now
242
248
  end
@@ -444,7 +450,8 @@ module Fluent
444
450
  end
445
451
  end
446
452
 
447
- TEMPLATE_FACTORIES = {
453
+ TEMPLATE_REGISTRY = Registry.new(:config_type, 'fluent/plugin/parser_')
454
+ {
448
455
  'apache' => Proc.new { RegexpParser.new(/^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/, {'time_format'=>"%d/%b/%Y:%H:%M:%S %z"}) },
449
456
  'apache2' => Proc.new { ApacheParser.new },
450
457
  'syslog' => Proc.new { RegexpParser.new(/^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?[^\:]*\: *(?<message>.*)$/, {'time_format'=>"%b %d %H:%M:%S"}) },
@@ -455,6 +462,8 @@ module Fluent
455
462
  'nginx' => Proc.new { RegexpParser.new(/^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/, {'time_format'=>"%d/%b/%Y:%H:%M:%S %z"}) },
456
463
  'none' => Proc.new { NoneParser.new },
457
464
  'multiline' => Proc.new { MultilineParser.new },
465
+ }.each { |name, factory|
466
+ TEMPLATE_REGISTRY.register(name, factory)
458
467
  }
459
468
 
460
469
  def self.register_template(name, regexp_or_proc, time_format=nil)
@@ -465,7 +474,7 @@ module Fluent
465
474
  factory = regexp_or_proc
466
475
  end
467
476
 
468
- TEMPLATE_FACTORIES[name] = factory
477
+ TEMPLATE_REGISTRY.register(name, factory)
469
478
  end
470
479
 
471
480
  def initialize
@@ -500,8 +509,9 @@ module Fluent
500
509
 
501
510
  else
502
511
  # built-in template
503
- factory = TEMPLATE_FACTORIES[format]
504
- unless factory
512
+ begin
513
+ factory = TEMPLATE_REGISTRY.lookup(format)
514
+ rescue ConfigError => e # keep same error message
505
515
  raise ConfigError, "Unknown format template '#{format}'"
506
516
  end
507
517
  @parser = factory.call
@@ -71,20 +71,28 @@ module Fluent
71
71
  super
72
72
  end
73
73
 
74
+ config_param :flush_at_shutdown, :bool, :default => true
74
75
  # Overwrite default BasicBuffer#buffer_queue_limit
75
76
  # to limit total memory usage upto 512MB.
76
77
  config_set_default :buffer_queue_limit, 64
77
78
 
78
79
  def configure(conf)
79
80
  super
81
+
82
+ unless @flush_at_shutdown
83
+ $log.warn "When flush_at_shutdown is false, buf_memory discards buffered chunks at shutdown."
84
+ $log.warn "Please confirm 'flush_at_shutdown false' configuration is correct or not."
85
+ end
80
86
  end
81
87
 
82
88
  def before_shutdown(out)
83
- synchronize do
84
- @map.each_key {|key|
85
- push(key)
86
- }
87
- while pop(out)
89
+ if @flush_at_shutdown
90
+ synchronize do
91
+ @map.each_key {|key|
92
+ push(key)
93
+ }
94
+ while pop(out)
95
+ end
88
96
  end
89
97
  end
90
98
  end
@@ -31,6 +31,8 @@ module Fluent
31
31
  config_param :backlog, :integer, :default => nil
32
32
  # SO_LINGER 0 to send RST rather than FIN to avoid lots of connections sitting in TIME_WAIT at src
33
33
  config_param :linger_timeout, :integer, :default => 0
34
+ # This option is for Cool.io's loop wait timeout to avoid loop stuck at shutdown. Almost users don't need to change this value.
35
+ config_param :blocking_timeout, :time, :default => 0.5
34
36
 
35
37
  def configure(conf)
36
38
  super
@@ -56,10 +58,12 @@ module Fluent
56
58
  @loop.watchers.each {|w| w.detach }
57
59
  @loop.stop
58
60
  @usock.close
59
- listen_address = (@bind == '0.0.0.0' ? '127.0.0.1' : @bind)
60
- # This line is for connecting listen socket to stop the event loop.
61
- # We should use more better approach, e.g. using pipe, fixing cool.io with timeout, etc.
62
- TCPSocket.open(listen_address, @port) {|sock| } # FIXME @thread.join blocks without this line
61
+ unless support_blocking_timeout?
62
+ listen_address = (@bind == '0.0.0.0' ? '127.0.0.1' : @bind)
63
+ # This line is for connecting listen socket to stop the event loop.
64
+ # We should use more better approach, e.g. using pipe, fixing cool.io with timeout, etc.
65
+ TCPSocket.open(listen_address, @port) {|sock| } # FIXME @thread.join blocks without this line
66
+ end
63
67
  @thread.join
64
68
  @lsock.close
65
69
  end
@@ -82,13 +86,22 @@ module Fluent
82
86
  #end
83
87
 
84
88
  def run
85
- @loop.run
89
+ if support_blocking_timeout?
90
+ @loop.run(@blocking_timeout)
91
+ else
92
+ @loop.run
93
+ end
86
94
  rescue => e
87
95
  log.error "unexpected error", :error => e, :error_class => e.class
88
96
  log.error_backtrace
89
97
  end
90
98
 
91
99
  protected
100
+
101
+ def support_blocking_timeout?
102
+ @loop.method(:run).arity.nonzero?
103
+ end
104
+
92
105
  # message Entry {
93
106
  # 1: long time
94
107
  # 2: object record
@@ -25,6 +25,7 @@ module Fluent
25
25
 
26
26
  def initialize
27
27
  require 'webrick/httputils'
28
+ require 'uri'
28
29
  super
29
30
  end
30
31
 
@@ -246,7 +247,8 @@ module Fluent
246
247
 
247
248
  @env['REMOTE_ADDR'] = @remote_addr if @remote_addr
248
249
 
249
- params = WEBrick::HTTPUtils.parse_query(@parser.query_string)
250
+ uri = URI.parse(@parser.request_url)
251
+ params = WEBrick::HTTPUtils.parse_query(uri.query)
250
252
 
251
253
  if @content_type =~ /^application\/x-www-form-urlencoded/
252
254
  params.update WEBrick::HTTPUtils.parse_query(@body)
@@ -256,7 +258,7 @@ module Fluent
256
258
  elsif @content_type =~ /^application\/json/
257
259
  params['json'] = @body
258
260
  end
259
- path_info = @parser.request_path
261
+ path_info = uri.path
260
262
 
261
263
  params.merge!(@env)
262
264
  @env.clear
@@ -237,7 +237,7 @@ module Fluent
237
237
  end
238
238
  rescue => e
239
239
  log.warn line.dump, :error => e.to_s
240
- log.debug_backtrace(e)
240
+ log.debug_backtrace(e.backtrace)
241
241
  end
242
242
  end
243
243
 
@@ -43,6 +43,7 @@ module Fluent
43
43
  config_param :hard_timeout, :time, :default => 60
44
44
  config_param :expire_dns_cache, :time, :default => nil # 0 means disable cache
45
45
  config_param :phi_threshold, :integer, :default => 16
46
+ config_param :phi_failure_detector, :bool, :default => true
46
47
  attr_reader :nodes
47
48
 
48
49
  # backward compatibility
@@ -82,8 +83,10 @@ module Fluent
82
83
  end
83
84
 
84
85
  failure = FailureDetector.new(@heartbeat_interval, @hard_timeout, Time.now.to_i.to_f)
85
- @nodes << Node.new(name, host, port, weight, standby, failure,
86
- @phi_threshold, recover_sample_size, @expire_dns_cache, log)
86
+
87
+ node_conf = NodeConfig.new(name, host, port, weight, standby, failure,
88
+ @phi_threshold, recover_sample_size, @expire_dns_cache, @phi_failure_detector)
89
+ @nodes << Node.new(log, node_conf)
87
90
  log.info "adding forwarding server '#{name}'", :host=>host, :port=>port, :weight=>weight
88
91
  }
89
92
  end
@@ -324,40 +327,40 @@ module Fluent
324
327
  end
325
328
  end
326
329
 
330
+ NodeConfig = Struct.new("NodeConfig", :name, :host, :port, :weight, :standby, :failure,
331
+ :phi_threshold, :recover_sample_size, :expire_dns_cache, :phi_failure_detector)
332
+
327
333
  class Node
328
- def initialize(name, host, port, weight, standby, failure,
329
- phi_threshold, recover_sample_size, expire_dns_cache, log)
330
- @name = name
331
- @host = host
332
- @port = port
333
- @weight = weight
334
- @standby = standby
335
- @failure = failure
336
- @phi_threshold = phi_threshold
337
- @recover_sample_size = recover_sample_size
338
- @expire_dns_cache = expire_dns_cache
339
- @available = true
334
+ def initialize(log, conf)
340
335
  @log = log
336
+ @conf = conf
337
+ @name = @conf.name
338
+ @host = @conf.host
339
+ @port = @conf.port
340
+ @weight = @conf.weight
341
+ @failure = @conf.failure
342
+ @available = true
341
343
 
342
344
  @resolved_host = nil
343
345
  @resolved_time = 0
344
346
  resolved_host # check dns
345
347
  end
346
348
 
349
+ attr_reader :conf
347
350
  attr_reader :name, :host, :port, :weight
348
- attr_writer :weight, :standby, :available
349
351
  attr_reader :sockaddr # used by on_heartbeat
352
+ attr_reader :failure, :available # for test
350
353
 
351
354
  def available?
352
355
  @available
353
356
  end
354
357
 
355
358
  def standby?
356
- @standby
359
+ @conf.standby
357
360
  end
358
361
 
359
362
  def resolved_host
360
- case @expire_dns_cache
363
+ case @conf.expire_dns_cache
361
364
  when 0
362
365
  # cache is disabled
363
366
  return resolve_dns!
@@ -369,7 +372,7 @@ module Fluent
369
372
  else
370
373
  now = Engine.now
371
374
  rh = @resolved_host
372
- if !rh || now - @resolved_time >= @expire_dns_cache
375
+ if !rh || now - @resolved_time >= @conf.expire_dns_cache
373
376
  rh = @resolved_host = resolve_dns!
374
377
  @resolved_time = now
375
378
  end
@@ -401,24 +404,25 @@ module Fluent
401
404
  return true
402
405
  end
403
406
 
404
- phi = @failure.phi(now)
405
- #$log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
406
- if phi > @phi_threshold
407
- @log.warn "detached forwarding server '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
408
- @available = false
409
- @resolved_host = nil # expire cached host
410
- @failure.clear
411
- return true
412
- else
413
- return false
407
+ if @conf.phi_failure_detector
408
+ phi = @failure.phi(now)
409
+ #$log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
410
+ if phi > @conf.phi_threshold
411
+ @log.warn "detached forwarding server '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
412
+ @available = false
413
+ @resolved_host = nil # expire cached host
414
+ @failure.clear
415
+ return true
416
+ end
414
417
  end
418
+ return false
415
419
  end
416
420
 
417
421
  def heartbeat(detect=true)
418
422
  now = Time.now.to_f
419
423
  @failure.add(now)
420
424
  #@log.trace "heartbeat from '#{@name}'", :host=>@host, :port=>@port, :available=>@available, :sample_size=>@failure.sample_size
421
- if detect && !@available && @failure.sample_size > @recover_sample_size
425
+ if detect && !@available && @failure.sample_size > @conf.recover_sample_size
422
426
  @available = true
423
427
  @log.warn "recovered forwarding server '#{@name}'", :host=>@host, :port=>@port
424
428
  return true
@@ -0,0 +1,76 @@
1
+ module Fluent
2
+ require 'fluent/config/error'
3
+
4
+ class Registry
5
+ def initialize(kind, search_prefix)
6
+ @kind = kind
7
+ @search_prefix = search_prefix
8
+ @map = {}
9
+ end
10
+
11
+ attr_reader :kind
12
+
13
+ def register(type, value)
14
+ type = type.to_sym
15
+ @map[type] = value
16
+ end
17
+
18
+ def lookup(type)
19
+ type = type.to_sym
20
+ if value = @map[type]
21
+ return value
22
+ end
23
+ search(type)
24
+ if value = @map[type]
25
+ return value
26
+ end
27
+ raise ConfigError, "Unknown #{@kind} plugin '#{type}'. Run 'gem search -rd fluentd-plugin' to find plugins" # TODO error class
28
+ end
29
+
30
+ def search(type)
31
+ path = "#{@search_prefix}#{type}"
32
+
33
+ # prefer LOAD_PATH than gems
34
+ files = $LOAD_PATH.map { |lp|
35
+ lpath = File.expand_path(File.join(lp, "#{path}.rb"))
36
+ File.exist?(lpath) ? lpath : nil
37
+ }.compact
38
+ unless files.empty?
39
+ # prefer newer version
40
+ require files.sort.last
41
+ return
42
+ end
43
+
44
+ # search gems
45
+ if defined?(::Gem::Specification) && ::Gem::Specification.respond_to?(:find_all)
46
+ specs = Gem::Specification.find_all { |spec|
47
+ spec.contains_requirable_file? path
48
+ }
49
+
50
+ # prefer newer version
51
+ specs = specs.sort_by { |spec| spec.version }
52
+ if spec = specs.last
53
+ spec.require_paths.each { |lib|
54
+ file = "#{spec.full_gem_path}/#{lib}/#{path}"
55
+ require file
56
+ }
57
+ end
58
+
59
+ # backward compatibility for rubygems < 1.8
60
+ elsif defined?(::Gem) && ::Gem.respond_to?(:searcher)
61
+ #files = Gem.find_files(path).sort
62
+ specs = Gem.searcher.find_all(path)
63
+
64
+ # prefer newer version
65
+ specs = specs.sort_by { |spec| spec.version }
66
+ specs.reverse_each { |spec|
67
+ files = Gem.searcher.matching_files(spec, path)
68
+ unless files.empty?
69
+ require files.first
70
+ break
71
+ end
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end