fluentd 0.10.0
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.
- data/AUTHORS +1 -0
- data/COPYING +14 -0
- data/ChangeLog +178 -0
- data/README.rdoc +57 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/bin/fluent-cat +6 -0
- data/bin/fluent-gem +10 -0
- data/bin/fluentd +6 -0
- data/fluent.conf +78 -0
- data/fluentd.gemspec +116 -0
- data/lib/fluent/buffer.rb +274 -0
- data/lib/fluent/command/cat.rb +299 -0
- data/lib/fluent/command/fluentd.rb +245 -0
- data/lib/fluent/config.rb +304 -0
- data/lib/fluent/engine.rb +224 -0
- data/lib/fluent/env.rb +6 -0
- data/lib/fluent/event.rb +159 -0
- data/lib/fluent/input.rb +41 -0
- data/lib/fluent/load.rb +23 -0
- data/lib/fluent/log.rb +277 -0
- data/lib/fluent/match.rb +189 -0
- data/lib/fluent/mixin.rb +170 -0
- data/lib/fluent/output.rb +466 -0
- data/lib/fluent/parser.rb +115 -0
- data/lib/fluent/plugin.rb +145 -0
- data/lib/fluent/plugin/buf_file.rb +181 -0
- data/lib/fluent/plugin/buf_memory.rb +97 -0
- data/lib/fluent/plugin/buf_zfile.rb +84 -0
- data/lib/fluent/plugin/in_http.rb +282 -0
- data/lib/fluent/plugin/in_stream.rb +187 -0
- data/lib/fluent/plugin/in_syslog.rb +174 -0
- data/lib/fluent/plugin/in_tail.rb +150 -0
- data/lib/fluent/plugin/out_copy.rb +72 -0
- data/lib/fluent/plugin/out_file.rb +111 -0
- data/lib/fluent/plugin/out_null.rb +44 -0
- data/lib/fluent/plugin/out_roundrobin.rb +72 -0
- data/lib/fluent/plugin/out_stdout.rb +34 -0
- data/lib/fluent/plugin/out_stream.rb +128 -0
- data/lib/fluent/plugin/out_test.rb +68 -0
- data/lib/fluent/test.rb +8 -0
- data/lib/fluent/test/base.rb +63 -0
- data/lib/fluent/test/input_test.rb +89 -0
- data/lib/fluent/test/output_test.rb +93 -0
- data/lib/fluent/version.rb +5 -0
- data/test/helper.rb +6 -0
- data/test/match.rb +115 -0
- data/test/plugin/in_http.rb +84 -0
- data/test/plugin/in_stream.rb +136 -0
- data/test/plugin/out_copy.rb +55 -0
- data/test/plugin/out_file.rb +82 -0
- data/test/plugin/out_roundrobin.rb +65 -0
- data/test/plugin/out_stream.rb +74 -0
- metadata +224 -0
@@ -0,0 +1,245 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd
|
3
|
+
#
|
4
|
+
# Copyright (C) 2011 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'optparse'
|
20
|
+
require 'fluent/log'
|
21
|
+
require 'fluent/env'
|
22
|
+
require 'fluent/version'
|
23
|
+
|
24
|
+
op = OptionParser.new
|
25
|
+
op.version = Fluent::VERSION
|
26
|
+
|
27
|
+
# default values
|
28
|
+
config_path = Fluent::DEFAULT_CONFIG_PATH
|
29
|
+
plugin_dirs = [Fluent::DEFAULT_PLUGIN_DIR]
|
30
|
+
log_level = Fluent::Log::LEVEL_INFO
|
31
|
+
log_file = nil
|
32
|
+
daemonize = false
|
33
|
+
libs = []
|
34
|
+
setup_path = nil
|
35
|
+
chuser = nil
|
36
|
+
chgroup = nil
|
37
|
+
|
38
|
+
op.on('-s', "--setup [DIR=#{File.dirname(Fluent::DEFAULT_CONFIG_PATH)}]", "install sample configuration file to the directory") {|s|
|
39
|
+
setup_path = s || File.dirname(Fluent::DEFAULT_CONFIG_PATH)
|
40
|
+
}
|
41
|
+
|
42
|
+
op.on('-c', '--config PATH', "config flie path (default: #{config_path})") {|s|
|
43
|
+
config_path = s
|
44
|
+
}
|
45
|
+
|
46
|
+
op.on('-p', '--plugin DIR', "add plugin directory") {|s|
|
47
|
+
plugin_dirs << s
|
48
|
+
}
|
49
|
+
|
50
|
+
op.on('-I PATH', "add library path") {|s|
|
51
|
+
$LOAD_PATH << s
|
52
|
+
}
|
53
|
+
|
54
|
+
op.on('-r NAME', "load library") {|s|
|
55
|
+
libs << s
|
56
|
+
}
|
57
|
+
|
58
|
+
op.on('-d', '--daemon PIDFILE', "daemonize fluent process") {|s|
|
59
|
+
daemonize = s
|
60
|
+
}
|
61
|
+
|
62
|
+
op.on('--user USER', "change user") {|s|
|
63
|
+
chuser = s
|
64
|
+
}
|
65
|
+
|
66
|
+
op.on('--group GROUP', "change group") {|s|
|
67
|
+
chgroup = s
|
68
|
+
}
|
69
|
+
|
70
|
+
op.on('-o', '--log PATH', "log file path") {|s|
|
71
|
+
log_file = s
|
72
|
+
}
|
73
|
+
|
74
|
+
op.on('-v', '--verbose', "increment verbose level (-v: debug, -vv: trace)", TrueClass) {|b|
|
75
|
+
if b
|
76
|
+
case log_level
|
77
|
+
when Fluent::Log::LEVEL_INFO
|
78
|
+
log_level = Fluent::Log::LEVEL_DEBUG
|
79
|
+
when Fluent::Log::LEVEL_DEBUG
|
80
|
+
log_level = Fluent::Log::LEVEL_TRACE
|
81
|
+
end
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
(class<<self;self;end).module_eval do
|
86
|
+
define_method(:usage) do |msg|
|
87
|
+
puts op.to_s
|
88
|
+
puts "error: #{msg}" if msg
|
89
|
+
exit 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
op.parse!(ARGV)
|
95
|
+
|
96
|
+
if ARGV.length != 0
|
97
|
+
usage nil
|
98
|
+
end
|
99
|
+
rescue
|
100
|
+
usage $!.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
if setup_path
|
105
|
+
require 'fileutils'
|
106
|
+
FileUtils.mkdir_p File.join(setup_path, "plugin")
|
107
|
+
confpath = File.join(setup_path, "fluent.conf")
|
108
|
+
if File.exist?(confpath)
|
109
|
+
puts "#{confpath} already exists."
|
110
|
+
else
|
111
|
+
File.open(confpath, "w") {|f|
|
112
|
+
conf = File.read File.join(File.dirname(__FILE__), "..", "..", "..", "fluent.conf")
|
113
|
+
f.write conf
|
114
|
+
}
|
115
|
+
puts "Installed #{confpath}."
|
116
|
+
end
|
117
|
+
exit 0
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
if log_file && log_file != "-"
|
122
|
+
log_out = File.open(log_file, "a")
|
123
|
+
else
|
124
|
+
log_out = STDOUT
|
125
|
+
end
|
126
|
+
|
127
|
+
$log = Fluent::Log.new(log_out, log_level)
|
128
|
+
|
129
|
+
$log.enable_color(false) if log_file
|
130
|
+
$log.enable_debug if log_level <= Fluent::Log::LEVEL_DEBUG
|
131
|
+
|
132
|
+
|
133
|
+
require 'fluent/load'
|
134
|
+
|
135
|
+
begin
|
136
|
+
#
|
137
|
+
# initialize
|
138
|
+
#
|
139
|
+
Fluent::Engine.init
|
140
|
+
|
141
|
+
libs.each {|lib|
|
142
|
+
require lib
|
143
|
+
}
|
144
|
+
|
145
|
+
plugin_dirs.each {|dir|
|
146
|
+
if Dir.exist?(dir)
|
147
|
+
dir = File.expand_path(dir)
|
148
|
+
Fluent::Engine.load_plugin_dir(dir)
|
149
|
+
end
|
150
|
+
}
|
151
|
+
|
152
|
+
Fluent::Engine.read_config(config_path)
|
153
|
+
|
154
|
+
|
155
|
+
#
|
156
|
+
# daemonize
|
157
|
+
#
|
158
|
+
if chgroup
|
159
|
+
chgid = chgroup.to_i
|
160
|
+
if chgid.to_s != chgroup
|
161
|
+
chgid = `id -u #{chgroup}`.to_i
|
162
|
+
if $?.to_i != 0
|
163
|
+
exit 1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
Process::GID.change_privilege(chgid)
|
167
|
+
end
|
168
|
+
|
169
|
+
if chuser
|
170
|
+
chuid = chuser.to_i
|
171
|
+
if chuid.to_s != chuser
|
172
|
+
chuid = `id -u #{chuser}`.to_i
|
173
|
+
if $?.to_i != 0
|
174
|
+
exit 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
Process::UID.change_privilege(chuid)
|
178
|
+
end
|
179
|
+
|
180
|
+
trap :INT do
|
181
|
+
Fluent::Engine.stop
|
182
|
+
end
|
183
|
+
|
184
|
+
trap :TERM do
|
185
|
+
Fluent::Engine.stop
|
186
|
+
end
|
187
|
+
|
188
|
+
trap :HUP do
|
189
|
+
if log_file
|
190
|
+
$log.reopen(log_file, "a")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
trap :USR1 do
|
195
|
+
$log.info "force flushing buffered events"
|
196
|
+
Fluent::Engine.flush!
|
197
|
+
end
|
198
|
+
|
199
|
+
if daemonize
|
200
|
+
exit!(0) if fork
|
201
|
+
Process.setsid
|
202
|
+
exit!(0) if fork
|
203
|
+
File.umask(0)
|
204
|
+
STDIN.reopen("/dev/null")
|
205
|
+
STDOUT.reopen("/dev/null", "w")
|
206
|
+
STDERR.reopen("/dev/null", "w")
|
207
|
+
File.open(daemonize, "w") {|f|
|
208
|
+
f.write Process.pid.to_s
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
#
|
214
|
+
# run
|
215
|
+
#
|
216
|
+
$log.info "running fluent-#{Fluent::VERSION}"
|
217
|
+
Fluent::Engine.run
|
218
|
+
|
219
|
+
rescue Fluent::ConfigError
|
220
|
+
$log.error "config error", :file=>config_path, :error=>$!.to_s
|
221
|
+
$log.debug_backtrace
|
222
|
+
|
223
|
+
# also STDOUT
|
224
|
+
if log_out != STDOUT
|
225
|
+
console = Fluent::Log.new(STDOUT, log_level).enable_debug
|
226
|
+
console.error "config error", :file=>config_path, :error=>$!.to_s
|
227
|
+
console.debug_backtrace
|
228
|
+
end
|
229
|
+
|
230
|
+
exit 1
|
231
|
+
|
232
|
+
rescue
|
233
|
+
$log.error "unexpected error", :error=>$!.to_s
|
234
|
+
$log.error_backtrace
|
235
|
+
|
236
|
+
# also STDOUT
|
237
|
+
if log_out != STDOUT
|
238
|
+
console = Fluent::Log.new(STDOUT, log_level).enable_debug
|
239
|
+
console.error "unexpected error", :error=>$!.to_s
|
240
|
+
console.error_backtrace
|
241
|
+
end
|
242
|
+
|
243
|
+
exit 1
|
244
|
+
end
|
245
|
+
|
@@ -0,0 +1,304 @@
|
|
1
|
+
#
|
2
|
+
# Fluent
|
3
|
+
#
|
4
|
+
# Copyright (C) 2011 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module Fluent
|
19
|
+
|
20
|
+
|
21
|
+
class ConfigError < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
class ConfigParseError < ConfigError
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module Config
|
29
|
+
class Element < Hash
|
30
|
+
def initialize(name, arg, attrs, elements, used=[])
|
31
|
+
@name = name
|
32
|
+
@arg = arg
|
33
|
+
@elements = elements
|
34
|
+
super()
|
35
|
+
attrs.each {|k,v|
|
36
|
+
self[k] = v
|
37
|
+
}
|
38
|
+
@used = used
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :name, :arg, :elements, :used
|
42
|
+
|
43
|
+
def add_element(name, arg='')
|
44
|
+
e = Element.new(name, arg, {}, [])
|
45
|
+
@elements << e
|
46
|
+
e
|
47
|
+
end
|
48
|
+
|
49
|
+
def +(o)
|
50
|
+
Element.new(@name.dup, @arg.dup, o.merge(self), @elements+o.elements, @used+o.used)
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_key?(key)
|
54
|
+
@used << key
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](key)
|
59
|
+
@used << key
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_not_fetched(&block)
|
64
|
+
each_key {|key|
|
65
|
+
unless @used.include?(key)
|
66
|
+
block.call(key, self)
|
67
|
+
end
|
68
|
+
}
|
69
|
+
@elements.each {|e|
|
70
|
+
e.check_not_fetched(&block)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s(nest = 0)
|
75
|
+
indent = " "*nest
|
76
|
+
nindent = " "*(nest+1)
|
77
|
+
out = ""
|
78
|
+
if @arg.empty?
|
79
|
+
out << "#{indent}<#{@name}>\n"
|
80
|
+
else
|
81
|
+
out << "#{indent}<#{@name} #{@name}>\n"
|
82
|
+
end
|
83
|
+
each_pair {|k,v|
|
84
|
+
out << "#{nindent}#{k} #{v}\n"
|
85
|
+
}
|
86
|
+
@elements.each {|e|
|
87
|
+
out << e.to_s(nest+1)
|
88
|
+
}
|
89
|
+
out << "#{indent}</#{@name}>\n"
|
90
|
+
out
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.read(path)
|
95
|
+
parse(File.read(path), File.basename(path))
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.parse(str, fname)
|
99
|
+
lines = str.split("\n")
|
100
|
+
i, attrs, elems = parse_element('end', lines, 0, fname)
|
101
|
+
Element.new('ROOT', '', attrs, elems)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.new(name='')
|
105
|
+
Element.new('', '', {}, [])
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.size_value(str)
|
109
|
+
case str.to_s
|
110
|
+
when /([0-9]+)k/i
|
111
|
+
$~[1].to_i * 1024
|
112
|
+
when /([0-9]+)m/i
|
113
|
+
$~[1].to_i * (1024**2)
|
114
|
+
when /([0-9]+)g/i
|
115
|
+
$~[1].to_i * (1024**3)
|
116
|
+
when /([0-9]+)t/i
|
117
|
+
$~[1].to_i * (1024**4)
|
118
|
+
else
|
119
|
+
str.to_i
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.time_value(str)
|
124
|
+
case str.to_s
|
125
|
+
when /([0-9]+)s/
|
126
|
+
$~[1].to_i
|
127
|
+
when /([0-9]+)m/
|
128
|
+
$~[1].to_i * 60
|
129
|
+
when /([0-9]+)h/
|
130
|
+
$~[1].to_i * 60*60
|
131
|
+
when /([0-9]+)d/
|
132
|
+
$~[1].to_i * 24*60*60
|
133
|
+
else
|
134
|
+
str.to_f
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.bool_value(str)
|
139
|
+
case str.to_s
|
140
|
+
when 'true', 'yes'
|
141
|
+
true
|
142
|
+
when 'false', 'no'
|
143
|
+
false
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def self.parse_element(name, lines, i, fname)
|
151
|
+
attrs = {}
|
152
|
+
elems = []
|
153
|
+
while i < lines.length
|
154
|
+
line = lines[i]
|
155
|
+
line.lstrip!
|
156
|
+
line.gsub!(/\s*(?:\#.*)?$/,'')
|
157
|
+
if line.empty?
|
158
|
+
i += 1
|
159
|
+
next
|
160
|
+
elsif m = /^\<([a-zA-Z0-9_]+)\s*(.+?)?\>$/.match(line)
|
161
|
+
e_name = m[1]
|
162
|
+
e_arg = m[2] || ""
|
163
|
+
i, e_attrs, e_elems = parse_element(e_name, lines, i+1, fname)
|
164
|
+
elems << Element.new(e_name, e_arg, e_attrs, e_elems)
|
165
|
+
elsif line == "</#{name}>"
|
166
|
+
i += 1
|
167
|
+
break
|
168
|
+
elsif m = /^([a-zA-Z0-9_]+)\s*(.+)?$/.match(line)
|
169
|
+
attrs[m[1]] = m[2] || ""
|
170
|
+
i += 1
|
171
|
+
next
|
172
|
+
else
|
173
|
+
raise ConfigParseError, "parse error at #{fname}:#{i}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
return i, attrs, elems
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
module Configurable
|
182
|
+
def self.included(mod)
|
183
|
+
mod.extend(ClassMethods)
|
184
|
+
end
|
185
|
+
|
186
|
+
def initialize
|
187
|
+
self.class.config_defaults.each_pair {|name,defval|
|
188
|
+
varname = :"@#{name}"
|
189
|
+
instance_variable_set(varname, defval)
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def configure(conf)
|
194
|
+
self.class.config_params.each_pair {|name,(block,opts)|
|
195
|
+
varname = :"@#{name}"
|
196
|
+
if val = conf[name.to_s]
|
197
|
+
val = self.instance_exec(val, opts, name, &block)
|
198
|
+
instance_variable_set(varname, val)
|
199
|
+
end
|
200
|
+
unless instance_variable_defined?(varname)
|
201
|
+
$log.error "config error in:\n#{conf}"
|
202
|
+
raise ConfigError, "'#{name}' parameter is required"
|
203
|
+
end
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
module ClassMethods
|
208
|
+
def config_param(name, *args, &block)
|
209
|
+
name = name.to_sym
|
210
|
+
|
211
|
+
opts = {}
|
212
|
+
args.each {|a|
|
213
|
+
if a.is_a?(Symbol)
|
214
|
+
opts[:type] = a
|
215
|
+
elsif a.is_a?(Hash)
|
216
|
+
opts.merge!(a)
|
217
|
+
else
|
218
|
+
raise ArgumentError, "wrong number of arguments (#{1+args.length} for #{block ? 2 : 3})"
|
219
|
+
end
|
220
|
+
}
|
221
|
+
|
222
|
+
type = opts[:type]
|
223
|
+
if block && type
|
224
|
+
raise ArgumentError, "wrong number of arguments (#{1+args.length} for #{block ? 2 : 3})"
|
225
|
+
end
|
226
|
+
|
227
|
+
block ||= case type
|
228
|
+
when :string, nil
|
229
|
+
Proc.new {|val| val }
|
230
|
+
when :integer
|
231
|
+
Proc.new {|val| val.to_i }
|
232
|
+
when :size
|
233
|
+
Proc.new {|val| Config.size_value(val) }
|
234
|
+
when :bool
|
235
|
+
Proc.new {|val| Config.bool_value(val) }
|
236
|
+
when :time
|
237
|
+
Proc.new {|val| Config.time_value(val) }
|
238
|
+
else
|
239
|
+
raise ArgumentError, "unknown config_param type `#{type}'"
|
240
|
+
end
|
241
|
+
|
242
|
+
params = config_params_set
|
243
|
+
params.delete(name)
|
244
|
+
params[name] = [block, opts]
|
245
|
+
|
246
|
+
if opts.has_key?(:default)
|
247
|
+
config_set_default(name, opts[:default])
|
248
|
+
end
|
249
|
+
|
250
|
+
attr_accessor name
|
251
|
+
end
|
252
|
+
|
253
|
+
def config_set_default(name, defval)
|
254
|
+
name = name.to_sym
|
255
|
+
|
256
|
+
defaults = config_defaults_set
|
257
|
+
defaults.delete(name)
|
258
|
+
defaults[name] = defval
|
259
|
+
|
260
|
+
nil
|
261
|
+
end
|
262
|
+
|
263
|
+
def config_params
|
264
|
+
singleton_value(:_config_params)
|
265
|
+
end
|
266
|
+
|
267
|
+
def config_defaults
|
268
|
+
singleton_value(:_config_defaults)
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
def config_params_set
|
273
|
+
singleton_value_set(:_config_params)
|
274
|
+
end
|
275
|
+
|
276
|
+
def config_defaults_set
|
277
|
+
singleton_value_set(:_config_defaults)
|
278
|
+
end
|
279
|
+
|
280
|
+
def singleton_value_set(name)
|
281
|
+
if methods(false).include?(name)
|
282
|
+
__send__(name)
|
283
|
+
else
|
284
|
+
val = {}
|
285
|
+
define_singleton_method(name) { val }
|
286
|
+
val
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def singleton_value(name)
|
291
|
+
val = {}
|
292
|
+
ancestors.reverse_each {|c|
|
293
|
+
if c.methods(false).include?(name)
|
294
|
+
val.merge!(c.__send__(name))
|
295
|
+
end
|
296
|
+
}
|
297
|
+
val
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
end
|
304
|
+
|