fluentd 0.10.35 → 0.10.36
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/.travis.yml +13 -0
- data/ChangeLog +9 -0
- data/fluentd.gemspec +1 -1
- data/lib/fluent/buffer.rb +210 -214
- data/lib/fluent/command/fluentd.rb +4 -0
- data/lib/fluent/config.rb +1 -0
- data/lib/fluent/engine.rb +10 -10
- data/lib/fluent/output.rb +404 -406
- data/lib/fluent/plugin/buf_file.rb +146 -151
- data/lib/fluent/plugin/buf_memory.rb +62 -67
- data/lib/fluent/plugin/in_debug_agent.rb +27 -31
- data/lib/fluent/plugin/in_exec.rb +86 -90
- data/lib/fluent/plugin/in_forward.rb +171 -171
- data/lib/fluent/plugin/in_gc_stat.rb +43 -47
- data/lib/fluent/plugin/in_http.rb +214 -216
- data/lib/fluent/plugin/in_monitor_agent.rb +212 -214
- data/lib/fluent/plugin/in_object_space.rb +75 -79
- data/lib/fluent/plugin/in_status.rb +44 -50
- data/lib/fluent/plugin/in_stream.rb +159 -160
- data/lib/fluent/plugin/in_syslog.rb +149 -153
- data/lib/fluent/plugin/in_tail.rb +382 -387
- data/lib/fluent/plugin/out_copy.rb +40 -45
- data/lib/fluent/plugin/out_exec.rb +52 -57
- data/lib/fluent/plugin/out_exec_filter.rb +327 -331
- data/lib/fluent/plugin/out_file.rb +78 -74
- data/lib/fluent/plugin/out_forward.rb +410 -414
- data/lib/fluent/plugin/out_null.rb +15 -19
- data/lib/fluent/plugin/out_roundrobin.rb +63 -68
- data/lib/fluent/plugin/out_stdout.rb +9 -14
- data/lib/fluent/plugin/out_stream.rb +83 -90
- data/lib/fluent/plugin/out_test.rb +42 -46
- data/lib/fluent/supervisor.rb +15 -0
- data/lib/fluent/version.rb +1 -1
- data/test/plugin/in_stream.rb +2 -0
- data/test/plugin/out_file.rb +19 -1
- metadata +6 -5
@@ -16,189 +16,185 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
module Fluent
|
19
|
+
class SyslogInput < Input
|
20
|
+
Plugin.register_input('syslog', self)
|
21
|
+
|
22
|
+
SYSLOG_REGEXP = /^\<([0-9]+)\>(.*)/
|
23
|
+
SYSLOG_ALL_REGEXP = /^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?[^\:]*\: *(?<message>.*)$/
|
24
|
+
TIME_FORMAT = "%b %d %H:%M:%S"
|
25
|
+
|
26
|
+
FACILITY_MAP = {
|
27
|
+
0 => 'kern',
|
28
|
+
1 => 'user',
|
29
|
+
2 => 'mail',
|
30
|
+
3 => 'daemon',
|
31
|
+
4 => 'auth',
|
32
|
+
5 => 'syslog',
|
33
|
+
6 => 'lpr',
|
34
|
+
7 => 'news',
|
35
|
+
8 => 'uucp',
|
36
|
+
9 => 'cron',
|
37
|
+
10 => 'authpriv',
|
38
|
+
11 => 'ftp',
|
39
|
+
12 => 'ntp',
|
40
|
+
13 => 'audit',
|
41
|
+
14 => 'alert',
|
42
|
+
15 => 'at',
|
43
|
+
16 => 'local0',
|
44
|
+
17 => 'local1',
|
45
|
+
18 => 'local2',
|
46
|
+
19 => 'local3',
|
47
|
+
20 => 'local4',
|
48
|
+
21 => 'local5',
|
49
|
+
22 => 'local6',
|
50
|
+
23 => 'local7'
|
51
|
+
}
|
19
52
|
|
53
|
+
PRIORITY_MAP = {
|
54
|
+
0 => 'emerg',
|
55
|
+
1 => 'alert',
|
56
|
+
2 => 'crit',
|
57
|
+
3 => 'err',
|
58
|
+
4 => 'warn',
|
59
|
+
5 => 'notice',
|
60
|
+
6 => 'info',
|
61
|
+
7 => 'debug'
|
62
|
+
}
|
20
63
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
SYSLOG_ALL_REGEXP = /^\<(?<pri>[0-9]+)\>(?<time>[^ ]* {1,2}[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?[^\:]*\: *(?<message>.*)$/
|
26
|
-
TIME_FORMAT = "%b %d %H:%M:%S"
|
27
|
-
|
28
|
-
FACILITY_MAP = {
|
29
|
-
0 => 'kern',
|
30
|
-
1 => 'user',
|
31
|
-
2 => 'mail',
|
32
|
-
3 => 'daemon',
|
33
|
-
4 => 'auth',
|
34
|
-
5 => 'syslog',
|
35
|
-
6 => 'lpr',
|
36
|
-
7 => 'news',
|
37
|
-
8 => 'uucp',
|
38
|
-
9 => 'cron',
|
39
|
-
10 => 'authpriv',
|
40
|
-
11 => 'ftp',
|
41
|
-
12 => 'ntp',
|
42
|
-
13 => 'audit',
|
43
|
-
14 => 'alert',
|
44
|
-
15 => 'at',
|
45
|
-
16 => 'local0',
|
46
|
-
17 => 'local1',
|
47
|
-
18 => 'local2',
|
48
|
-
19 => 'local3',
|
49
|
-
20 => 'local4',
|
50
|
-
21 => 'local5',
|
51
|
-
22 => 'local6',
|
52
|
-
23 => 'local7'
|
53
|
-
}
|
54
|
-
|
55
|
-
PRIORITY_MAP = {
|
56
|
-
0 => 'emerg',
|
57
|
-
1 => 'alert',
|
58
|
-
2 => 'crit',
|
59
|
-
3 => 'err',
|
60
|
-
4 => 'warn',
|
61
|
-
5 => 'notice',
|
62
|
-
6 => 'info',
|
63
|
-
7 => 'debug'
|
64
|
-
}
|
65
|
-
|
66
|
-
def initialize
|
67
|
-
super
|
68
|
-
require 'fluent/plugin/socket_util'
|
69
|
-
end
|
64
|
+
def initialize
|
65
|
+
super
|
66
|
+
require 'fluent/plugin/socket_util'
|
67
|
+
end
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
config_param :port, :integer, :default => 5140
|
70
|
+
config_param :bind, :string, :default => '0.0.0.0'
|
71
|
+
config_param :tag, :string
|
74
72
|
|
75
|
-
|
76
|
-
|
73
|
+
def configure(conf)
|
74
|
+
super
|
77
75
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
def start
|
87
|
-
if @parser
|
88
|
-
callback = method(:receive_data_parser)
|
89
|
-
else
|
90
|
-
callback = method(:receive_data)
|
76
|
+
parser = TextParser.new
|
77
|
+
if parser.configure(conf, false)
|
78
|
+
@parser = parser
|
79
|
+
else
|
80
|
+
@parser = nil
|
81
|
+
end
|
91
82
|
end
|
92
83
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
84
|
+
def start
|
85
|
+
if @parser
|
86
|
+
callback = method(:receive_data_parser)
|
87
|
+
else
|
88
|
+
callback = method(:receive_data)
|
89
|
+
end
|
98
90
|
|
99
|
-
|
100
|
-
@loop.attach(@handler)
|
91
|
+
@loop = Coolio::Loop.new
|
101
92
|
|
102
|
-
|
103
|
-
|
93
|
+
$log.debug "listening syslog socket on #{@bind}:#{@port}"
|
94
|
+
@usock = SocketUtil.create_udp_socket(@bind)
|
95
|
+
@usock.bind(@bind, @port)
|
104
96
|
|
105
|
-
|
106
|
-
|
107
|
-
@loop.stop
|
108
|
-
@handler.close
|
109
|
-
@thread.join
|
110
|
-
end
|
97
|
+
@handler = UdpHandler.new(@usock, callback)
|
98
|
+
@loop.attach(@handler)
|
111
99
|
|
112
|
-
|
113
|
-
|
114
|
-
rescue
|
115
|
-
$log.error "unexpected error", :error=>$!.to_s
|
116
|
-
$log.error_backtrace
|
117
|
-
end
|
100
|
+
@thread = Thread.new(&method(:run))
|
101
|
+
end
|
118
102
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
return
|
103
|
+
def shutdown
|
104
|
+
@loop.watchers.each {|w| w.detach }
|
105
|
+
@loop.stop
|
106
|
+
@handler.close
|
107
|
+
@thread.join
|
125
108
|
end
|
126
|
-
pri = m[1].to_i
|
127
|
-
text = m[2]
|
128
109
|
|
129
|
-
|
130
|
-
|
131
|
-
|
110
|
+
def run
|
111
|
+
@loop.run
|
112
|
+
rescue
|
113
|
+
$log.error "unexpected error", :error=>$!.to_s
|
114
|
+
$log.error_backtrace
|
132
115
|
end
|
133
116
|
|
134
|
-
|
117
|
+
protected
|
118
|
+
def receive_data_parser(data)
|
119
|
+
m = SYSLOG_REGEXP.match(data)
|
120
|
+
unless m
|
121
|
+
$log.debug "invalid syslog message: #{data.dump}"
|
122
|
+
return
|
123
|
+
end
|
124
|
+
pri = m[1].to_i
|
125
|
+
text = m[2]
|
135
126
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
127
|
+
time, record = @parser.parse(text)
|
128
|
+
unless time && record
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
emit(pri, time, record)
|
140
133
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
$log.debug "invalid syslog message", :data=>data
|
145
|
-
return
|
134
|
+
rescue
|
135
|
+
$log.warn data.dump, :error=>$!.to_s
|
136
|
+
$log.debug_backtrace
|
146
137
|
end
|
147
138
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
if value = m[name]
|
154
|
-
case name
|
155
|
-
when "pri"
|
156
|
-
pri = value.to_i
|
157
|
-
when "time"
|
158
|
-
time = Time.strptime(value.gsub(/ +/, ' '), TIME_FORMAT).to_i
|
159
|
-
else
|
160
|
-
record[name] = value
|
161
|
-
end
|
139
|
+
def receive_data(data)
|
140
|
+
m = SYSLOG_ALL_REGEXP.match(data)
|
141
|
+
unless m
|
142
|
+
$log.debug "invalid syslog message", :data=>data
|
143
|
+
return
|
162
144
|
end
|
163
|
-
}
|
164
145
|
|
165
|
-
|
146
|
+
pri = nil
|
147
|
+
time = nil
|
148
|
+
record = {}
|
149
|
+
|
150
|
+
m.names.each {|name|
|
151
|
+
if value = m[name]
|
152
|
+
case name
|
153
|
+
when "pri"
|
154
|
+
pri = value.to_i
|
155
|
+
when "time"
|
156
|
+
time = Time.strptime(value.gsub(/ +/, ' '), TIME_FORMAT).to_i
|
157
|
+
else
|
158
|
+
record[name] = value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
}
|
166
162
|
|
167
|
-
|
163
|
+
time ||= Engine.now
|
168
164
|
|
169
|
-
|
170
|
-
$log.warn data.dump, :error=>$!.to_s
|
171
|
-
$log.debug_backtrace
|
172
|
-
end
|
165
|
+
emit(pri, time, record)
|
173
166
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
167
|
+
rescue
|
168
|
+
$log.warn data.dump, :error=>$!.to_s
|
169
|
+
$log.debug_backtrace
|
170
|
+
end
|
178
171
|
|
179
|
-
|
172
|
+
private
|
173
|
+
def emit(pri, time, record)
|
174
|
+
facility = FACILITY_MAP[pri >> 3]
|
175
|
+
priority = PRIORITY_MAP[pri & 0b111]
|
180
176
|
|
181
|
-
|
182
|
-
end
|
177
|
+
tag = "#{@tag}.#{facility}.#{priority}"
|
183
178
|
|
184
|
-
|
185
|
-
def initialize(io, callback)
|
186
|
-
super(io)
|
187
|
-
@io = io
|
188
|
-
@callback = callback
|
179
|
+
Engine.emit(tag, time, record)
|
189
180
|
end
|
190
181
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
182
|
+
class UdpHandler < Coolio::IO
|
183
|
+
def initialize(io, callback)
|
184
|
+
super(io)
|
185
|
+
@io = io
|
186
|
+
@callback = callback
|
187
|
+
end
|
188
|
+
|
189
|
+
def on_readable
|
190
|
+
msg, addr = @io.recvfrom_nonblock(2048)
|
191
|
+
#host = addr[3]
|
192
|
+
#port = addr[1]
|
193
|
+
#@callback.call(host, port, msg)
|
194
|
+
@callback.call(msg)
|
195
|
+
rescue
|
196
|
+
# TODO log?
|
197
|
+
end
|
199
198
|
end
|
200
199
|
end
|
201
200
|
end
|
202
|
-
|
203
|
-
|
204
|
-
end
|
@@ -16,485 +16,480 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
module Fluent
|
19
|
+
class TailInput < Input
|
20
|
+
Plugin.register_input('tail', self)
|
19
21
|
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
@paths = []
|
25
|
+
end
|
20
26
|
|
21
|
-
|
22
|
-
|
27
|
+
config_param :path, :string
|
28
|
+
config_param :tag, :string
|
29
|
+
config_param :rotate_wait, :time, :default => 5
|
30
|
+
config_param :pos_file, :string, :default => nil
|
23
31
|
|
24
|
-
|
25
|
-
super
|
26
|
-
@paths = []
|
27
|
-
end
|
32
|
+
attr_reader :paths
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
config_param :rotate_wait, :time, :default => 5
|
32
|
-
config_param :pos_file, :string, :default => nil
|
34
|
+
def configure(conf)
|
35
|
+
super
|
33
36
|
|
34
|
-
|
37
|
+
@paths = @path.split(',').map {|path| path.strip }
|
38
|
+
if @paths.empty?
|
39
|
+
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
40
|
+
end
|
35
41
|
|
36
|
-
|
37
|
-
|
42
|
+
if @pos_file
|
43
|
+
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
|
44
|
+
@pf_file.sync = true
|
45
|
+
@pf = PositionFile.parse(@pf_file)
|
46
|
+
else
|
47
|
+
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
48
|
+
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
49
|
+
end
|
38
50
|
|
39
|
-
|
40
|
-
if @paths.empty?
|
41
|
-
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
51
|
+
configure_parser(conf)
|
42
52
|
end
|
43
53
|
|
44
|
-
|
45
|
-
@
|
46
|
-
@
|
47
|
-
@pf = PositionFile.parse(@pf_file)
|
48
|
-
else
|
49
|
-
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
50
|
-
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
54
|
+
def configure_parser(conf)
|
55
|
+
@parser = TextParser.new
|
56
|
+
@parser.configure(conf)
|
51
57
|
end
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
pe = @pf ? @pf[path] : MemoryPositionEntry.new
|
65
|
-
TailWatcher.new(path, @rotate_wait, pe, &method(:receive_lines))
|
66
|
-
}
|
67
|
-
@tails.each {|tail|
|
68
|
-
tail.attach(@loop)
|
69
|
-
}
|
70
|
-
@thread = Thread.new(&method(:run))
|
71
|
-
end
|
59
|
+
def start
|
60
|
+
@loop = Coolio::Loop.new
|
61
|
+
@tails = @paths.map {|path|
|
62
|
+
pe = @pf ? @pf[path] : MemoryPositionEntry.new
|
63
|
+
TailWatcher.new(path, @rotate_wait, pe, &method(:receive_lines))
|
64
|
+
}
|
65
|
+
@tails.each {|tail|
|
66
|
+
tail.attach(@loop)
|
67
|
+
}
|
68
|
+
@thread = Thread.new(&method(:run))
|
69
|
+
end
|
72
70
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
def shutdown
|
72
|
+
@tails.each {|tail|
|
73
|
+
tail.close
|
74
|
+
}
|
75
|
+
@loop.stop
|
76
|
+
@thread.join
|
77
|
+
@pf_file.close if @pf_file
|
78
|
+
end
|
81
79
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
def run
|
81
|
+
@loop.run
|
82
|
+
rescue
|
83
|
+
$log.error "unexpected error", :error=>$!.to_s
|
84
|
+
$log.error_backtrace
|
85
|
+
end
|
88
86
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
87
|
+
def receive_lines(lines)
|
88
|
+
es = MultiEventStream.new
|
89
|
+
lines.each {|line|
|
90
|
+
begin
|
91
|
+
line.chomp! # remove \n
|
92
|
+
time, record = parse_line(line)
|
93
|
+
if time && record
|
94
|
+
es.add(time, record)
|
95
|
+
end
|
96
|
+
rescue
|
97
|
+
$log.warn line.dump, :error=>$!.to_s
|
98
|
+
$log.debug_backtrace
|
97
99
|
end
|
98
|
-
|
99
|
-
$log.warn line.dump, :error=>$!.to_s
|
100
|
-
$log.debug_backtrace
|
101
|
-
end
|
102
|
-
}
|
100
|
+
}
|
103
101
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
102
|
+
unless es.empty?
|
103
|
+
begin
|
104
|
+
Engine.emit_stream(@tag, es)
|
105
|
+
rescue
|
106
|
+
# ignore errors. Engine shows logs and backtraces.
|
107
|
+
end
|
109
108
|
end
|
110
109
|
end
|
111
|
-
end
|
112
110
|
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
def parse_line(line)
|
112
|
+
return @parser.parse(line)
|
113
|
+
end
|
116
114
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
115
|
+
class TailWatcher
|
116
|
+
def initialize(path, rotate_wait, pe, &receive_lines)
|
117
|
+
@path = path
|
118
|
+
@rotate_wait = rotate_wait
|
119
|
+
@pe = pe || MemoryPositionEntry.new
|
120
|
+
@receive_lines = receive_lines
|
123
121
|
|
124
|
-
|
122
|
+
@rotate_queue = []
|
125
123
|
|
126
|
-
|
127
|
-
|
124
|
+
@timer_trigger = TimerWatcher.new(1, true, &method(:on_notify))
|
125
|
+
@stat_trigger = StatWatcher.new(path, &method(:on_notify))
|
128
126
|
|
129
|
-
|
130
|
-
|
131
|
-
|
127
|
+
@rotate_handler = RotateHandler.new(path, &method(:on_rotate))
|
128
|
+
@io_handler = nil
|
129
|
+
end
|
132
130
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
131
|
+
def attach(loop)
|
132
|
+
@timer_trigger.attach(loop)
|
133
|
+
@stat_trigger.attach(loop)
|
134
|
+
on_notify
|
135
|
+
end
|
138
136
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
137
|
+
def detach
|
138
|
+
@timer_trigger.detach if @timer_trigger.attached?
|
139
|
+
@stat_trigger.detach if @stat_trigger.attached?
|
140
|
+
end
|
143
141
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
142
|
+
def close
|
143
|
+
@rotate_queue.reject! {|req|
|
144
|
+
req.io.close
|
145
|
+
true
|
146
|
+
}
|
147
|
+
detach
|
148
|
+
end
|
151
149
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
150
|
+
def on_notify
|
151
|
+
@rotate_handler.on_notify
|
152
|
+
return unless @io_handler
|
153
|
+
@io_handler.on_notify
|
154
|
+
|
155
|
+
# proceeds rotate queue
|
156
|
+
return if @rotate_queue.empty?
|
157
|
+
@rotate_queue.first.tick
|
158
|
+
|
159
|
+
while @rotate_queue.first.ready?
|
160
|
+
if io = @rotate_queue.first.io
|
161
|
+
stat = io.stat
|
162
|
+
inode = stat.ino
|
163
|
+
if inode == @pe.read_inode
|
164
|
+
# rotated file has the same inode number with the last file.
|
165
|
+
# assuming following situation:
|
166
|
+
# a) file was once renamed and backed, or
|
167
|
+
# b) symlink or hardlink to the same file is recreated
|
168
|
+
# in either case, seek to the saved position
|
169
|
+
pos = @pe.read_pos
|
170
|
+
else
|
171
|
+
pos = io.pos
|
172
|
+
end
|
173
|
+
@pe.update(inode, pos)
|
174
|
+
io_handler = IOHandler.new(io, @pe, &@receive_lines)
|
172
175
|
else
|
173
|
-
|
176
|
+
io_handler = NullIOHandler.new
|
174
177
|
end
|
175
|
-
@
|
176
|
-
io_handler =
|
177
|
-
|
178
|
-
|
178
|
+
@io_handler.close
|
179
|
+
@io_handler = io_handler
|
180
|
+
@rotate_queue.shift
|
181
|
+
break if @rotate_queue.empty?
|
179
182
|
end
|
180
|
-
@io_handler.close
|
181
|
-
@io_handler = io_handler
|
182
|
-
@rotate_queue.shift
|
183
|
-
break if @rotate_queue.empty?
|
184
183
|
end
|
185
|
-
end
|
186
184
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
185
|
+
def on_rotate(io)
|
186
|
+
if @io_handler == nil
|
187
|
+
if io
|
188
|
+
# first time
|
189
|
+
stat = io.stat
|
190
|
+
fsize = stat.size
|
191
|
+
inode = stat.ino
|
192
|
+
|
193
|
+
last_inode = @pe.read_inode
|
194
|
+
if inode == last_inode
|
195
|
+
# seek to the saved position
|
196
|
+
pos = @pe.read_pos
|
197
|
+
elsif last_inode != 0
|
198
|
+
# this is FilePositionEntry and fluentd once started.
|
199
|
+
# read data from the head of the rotated file.
|
200
|
+
# logs never duplicate because this file is a rotated new file.
|
201
|
+
pos = 0
|
202
|
+
@pe.update(inode, pos)
|
203
|
+
else
|
204
|
+
# this is MemoryPositionEntry or this is the first time fluentd started.
|
205
|
+
# seek to the end of the any files.
|
206
|
+
# logs may duplicate without this seek because it's not sure the file is
|
207
|
+
# existent file or rotated new file.
|
208
|
+
pos = fsize
|
209
|
+
@pe.update(inode, pos)
|
210
|
+
end
|
211
|
+
io.seek(pos)
|
212
|
+
|
213
|
+
@io_handler = IOHandler.new(io, @pe, &@receive_lines)
|
205
214
|
else
|
206
|
-
|
207
|
-
# seek to the end of the any files.
|
208
|
-
# logs may duplicate without this seek because it's not sure the file is
|
209
|
-
# existent file or rotated new file.
|
210
|
-
pos = fsize
|
211
|
-
@pe.update(inode, pos)
|
215
|
+
@io_handler = NullIOHandler.new
|
212
216
|
end
|
213
|
-
io.seek(pos)
|
214
217
|
|
215
|
-
@io_handler = IOHandler.new(io, @pe, &@receive_lines)
|
216
218
|
else
|
217
|
-
@
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
wait = @rotate_wait
|
232
|
-
wait -= @rotate_queue.first.wait unless @rotate_queue.empty?
|
219
|
+
if io && @rotate_queue.find {|req| req.io == io }
|
220
|
+
return
|
221
|
+
end
|
222
|
+
last_io = @rotate_queue.empty? ? @io_handler.io : @rotate_queue.last.io
|
223
|
+
if last_io == nil
|
224
|
+
$log.info "detected rotation of #{@path}"
|
225
|
+
# rotate imeediately if previous file is nil
|
226
|
+
wait = 0
|
227
|
+
else
|
228
|
+
$log.info "detected rotation of #{@path}; waiting #{@rotate_wait} seconds"
|
229
|
+
wait = @rotate_wait
|
230
|
+
wait -= @rotate_queue.first.wait unless @rotate_queue.empty?
|
231
|
+
end
|
232
|
+
@rotate_queue << RotationRequest.new(io, wait)
|
233
233
|
end
|
234
|
-
@rotate_queue << RotationRequest.new(io, wait)
|
235
234
|
end
|
236
|
-
end
|
237
235
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
236
|
+
class TimerWatcher < Coolio::TimerWatcher
|
237
|
+
def initialize(interval, repeat, &callback)
|
238
|
+
@callback = callback
|
239
|
+
super(interval, repeat)
|
240
|
+
end
|
243
241
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
242
|
+
def on_timer
|
243
|
+
@callback.call
|
244
|
+
rescue
|
245
|
+
# TODO log?
|
246
|
+
$log.error $!.to_s
|
247
|
+
$log.error_backtrace
|
248
|
+
end
|
250
249
|
end
|
251
|
-
end
|
252
250
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
251
|
+
class StatWatcher < Coolio::StatWatcher
|
252
|
+
def initialize(path, &callback)
|
253
|
+
@callback = callback
|
254
|
+
super(path)
|
255
|
+
end
|
258
256
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
257
|
+
def on_change(prev, cur)
|
258
|
+
@callback.call
|
259
|
+
rescue
|
260
|
+
# TODO log?
|
261
|
+
$log.error $!.to_s
|
262
|
+
$log.error_backtrace
|
263
|
+
end
|
265
264
|
end
|
266
|
-
end
|
267
265
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
266
|
+
class RotationRequest
|
267
|
+
def initialize(io, wait)
|
268
|
+
@io = io
|
269
|
+
@wait = wait
|
270
|
+
end
|
273
271
|
|
274
|
-
|
272
|
+
attr_reader :io, :wait
|
275
273
|
|
276
|
-
|
277
|
-
|
278
|
-
|
274
|
+
def tick
|
275
|
+
@wait -= 1
|
276
|
+
end
|
279
277
|
|
280
|
-
|
281
|
-
|
278
|
+
def ready?
|
279
|
+
@wait <= 0
|
280
|
+
end
|
282
281
|
end
|
283
|
-
end
|
284
282
|
|
285
|
-
|
286
|
-
|
287
|
-
class IOHandler
|
288
|
-
def initialize(io, pe, &receive_lines)
|
289
|
-
$log.info "following tail of #{io.path}"
|
290
|
-
@io = io
|
291
|
-
@pe = pe
|
292
|
-
@receive_lines = receive_lines
|
293
|
-
@buffer = ''.force_encoding('ASCII-8BIT')
|
294
|
-
@iobuf = ''.force_encoding('ASCII-8BIT')
|
295
|
-
end
|
283
|
+
MAX_LINES_AT_ONCE = 1000
|
296
284
|
|
297
|
-
|
285
|
+
class IOHandler
|
286
|
+
def initialize(io, pe, &receive_lines)
|
287
|
+
$log.info "following tail of #{io.path}"
|
288
|
+
@io = io
|
289
|
+
@pe = pe
|
290
|
+
@receive_lines = receive_lines
|
291
|
+
@buffer = ''.force_encoding('ASCII-8BIT')
|
292
|
+
@iobuf = ''.force_encoding('ASCII-8BIT')
|
293
|
+
end
|
298
294
|
|
299
|
-
|
300
|
-
begin
|
301
|
-
lines = []
|
302
|
-
read_more = false
|
295
|
+
attr_reader :io
|
303
296
|
|
297
|
+
def on_notify
|
304
298
|
begin
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
299
|
+
lines = []
|
300
|
+
read_more = false
|
301
|
+
|
302
|
+
begin
|
303
|
+
while true
|
304
|
+
if @buffer.empty?
|
305
|
+
@io.read_nonblock(2048, @buffer)
|
306
|
+
else
|
307
|
+
@buffer << @io.read_nonblock(2048, @iobuf)
|
308
|
+
end
|
309
|
+
while line = @buffer.slice!(/.*?\n/m)
|
310
|
+
lines << line
|
311
|
+
end
|
312
|
+
if lines.size >= MAX_LINES_AT_ONCE
|
313
|
+
# not to use too much memory in case the file is very large
|
314
|
+
read_more = true
|
315
|
+
break
|
316
|
+
end
|
318
317
|
end
|
318
|
+
rescue EOFError
|
319
319
|
end
|
320
|
-
rescue EOFError
|
321
|
-
end
|
322
320
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
321
|
+
unless lines.empty?
|
322
|
+
@receive_lines.call(lines)
|
323
|
+
@pe.update_pos(@io.pos - @buffer.bytesize)
|
324
|
+
end
|
327
325
|
|
328
|
-
|
326
|
+
end while read_more
|
329
327
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
def close
|
337
|
-
@io.close unless @io.closed?
|
338
|
-
end
|
339
|
-
end
|
328
|
+
rescue
|
329
|
+
$log.error $!.to_s
|
330
|
+
$log.error_backtrace
|
331
|
+
close
|
332
|
+
end
|
340
333
|
|
341
|
-
|
342
|
-
|
334
|
+
def close
|
335
|
+
@io.close unless @io.closed?
|
336
|
+
end
|
343
337
|
end
|
344
338
|
|
345
|
-
|
346
|
-
|
339
|
+
class NullIOHandler
|
340
|
+
def initialize
|
341
|
+
end
|
347
342
|
|
348
|
-
|
349
|
-
|
343
|
+
def io
|
344
|
+
end
|
350
345
|
|
351
|
-
|
352
|
-
|
353
|
-
end
|
346
|
+
def on_notify
|
347
|
+
end
|
354
348
|
|
355
|
-
|
356
|
-
|
357
|
-
@path = path
|
358
|
-
@inode = nil
|
359
|
-
@fsize = -1 # first
|
360
|
-
@on_rotate = on_rotate
|
361
|
-
@path = path
|
349
|
+
def close
|
350
|
+
end
|
362
351
|
end
|
363
352
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
# moved or deleted
|
372
|
-
inode = nil
|
373
|
-
fsize = 0
|
353
|
+
class RotateHandler
|
354
|
+
def initialize(path, &on_rotate)
|
355
|
+
@path = path
|
356
|
+
@inode = nil
|
357
|
+
@fsize = -1 # first
|
358
|
+
@on_rotate = on_rotate
|
359
|
+
@path = path
|
374
360
|
end
|
375
361
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
362
|
+
def on_notify
|
363
|
+
begin
|
364
|
+
io = File.open(@path)
|
365
|
+
stat = io.stat
|
366
|
+
inode = stat.ino
|
367
|
+
fsize = stat.size
|
368
|
+
rescue Errno::ENOENT
|
369
|
+
# moved or deleted
|
370
|
+
inode = nil
|
371
|
+
fsize = 0
|
381
372
|
end
|
382
373
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
374
|
+
begin
|
375
|
+
if @inode != inode || fsize < @fsize
|
376
|
+
# rotated or truncated
|
377
|
+
@on_rotate.call(io)
|
378
|
+
io = nil
|
379
|
+
end
|
380
|
+
|
381
|
+
@inode = inode
|
382
|
+
@fsize = fsize
|
383
|
+
ensure
|
384
|
+
io.close if io
|
385
|
+
end
|
388
386
|
|
389
|
-
|
390
|
-
|
391
|
-
|
387
|
+
rescue
|
388
|
+
$log.error $!.to_s
|
389
|
+
$log.error_backtrace
|
390
|
+
end
|
392
391
|
end
|
393
392
|
end
|
394
|
-
end
|
395
393
|
|
396
394
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
end
|
403
|
-
|
404
|
-
def [](path)
|
405
|
-
if m = @map[path]
|
406
|
-
return m
|
395
|
+
class PositionFile
|
396
|
+
def initialize(file, map, last_pos)
|
397
|
+
@file = file
|
398
|
+
@map = map
|
399
|
+
@last_pos = last_pos
|
407
400
|
end
|
408
401
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
@file.write "0000000000000000\t00000000\n"
|
414
|
-
@last_pos = @file.pos
|
402
|
+
def [](path)
|
403
|
+
if m = @map[path]
|
404
|
+
return m
|
405
|
+
end
|
415
406
|
|
416
|
-
|
417
|
-
|
407
|
+
@file.pos = @last_pos
|
408
|
+
@file.write path
|
409
|
+
@file.write "\t"
|
410
|
+
seek = @file.pos
|
411
|
+
@file.write "0000000000000000\t00000000\n"
|
412
|
+
@last_pos = @file.pos
|
418
413
|
|
419
|
-
|
420
|
-
|
421
|
-
file.pos = 0
|
422
|
-
file.each_line {|line|
|
423
|
-
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
424
|
-
next unless m
|
425
|
-
path = m[1]
|
426
|
-
pos = m[2].to_i(16)
|
427
|
-
ino = m[3].to_i(16)
|
428
|
-
seek = file.pos - line.bytesize + path.bytesize + 1
|
429
|
-
map[path] = FilePositionEntry.new(file, seek)
|
430
|
-
}
|
431
|
-
new(file, map, file.pos)
|
432
|
-
end
|
433
|
-
end
|
414
|
+
@map[path] = FilePositionEntry.new(@file, seek)
|
415
|
+
end
|
434
416
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
417
|
+
def self.parse(file)
|
418
|
+
map = {}
|
419
|
+
file.pos = 0
|
420
|
+
file.each_line {|line|
|
421
|
+
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
422
|
+
next unless m
|
423
|
+
path = m[1]
|
424
|
+
pos = m[2].to_i(16)
|
425
|
+
ino = m[3].to_i(16)
|
426
|
+
seek = file.pos - line.bytesize + path.bytesize + 1
|
427
|
+
map[path] = FilePositionEntry.new(file, seek)
|
428
|
+
}
|
429
|
+
new(file, map, file.pos)
|
430
|
+
end
|
447
431
|
end
|
448
432
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
433
|
+
# pos inode
|
434
|
+
# ffffffffffffffff\tffffffff\n
|
435
|
+
class FilePositionEntry
|
436
|
+
POS_SIZE = 16
|
437
|
+
INO_OFFSET = 17
|
438
|
+
INO_SIZE = 8
|
439
|
+
LN_OFFSET = 25
|
440
|
+
SIZE = 26
|
441
|
+
|
442
|
+
def initialize(file, seek)
|
443
|
+
@file = file
|
444
|
+
@seek = seek
|
445
|
+
end
|
454
446
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
447
|
+
def update(ino, pos)
|
448
|
+
@file.pos = @seek
|
449
|
+
@file.write "%016x\t%08x" % [pos, ino]
|
450
|
+
@inode = ino
|
451
|
+
end
|
459
452
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
end
|
453
|
+
def update_pos(pos)
|
454
|
+
@file.pos = @seek
|
455
|
+
@file.write "%016x" % pos
|
456
|
+
end
|
465
457
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
end
|
458
|
+
def read_inode
|
459
|
+
@file.pos = @seek + INO_OFFSET
|
460
|
+
raw = @file.read(8)
|
461
|
+
raw ? raw.to_i(16) : 0
|
462
|
+
end
|
472
463
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
464
|
+
def read_pos
|
465
|
+
@file.pos = @seek
|
466
|
+
raw = @file.read(16)
|
467
|
+
raw ? raw.to_i(16) : 0
|
468
|
+
end
|
477
469
|
end
|
478
470
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
471
|
+
class MemoryPositionEntry
|
472
|
+
def initialize
|
473
|
+
@pos = 0
|
474
|
+
@inode = 0
|
475
|
+
end
|
483
476
|
|
484
|
-
|
485
|
-
|
486
|
-
|
477
|
+
def update(ino, pos)
|
478
|
+
@inode = ino
|
479
|
+
@pos = pos
|
480
|
+
end
|
487
481
|
|
488
|
-
|
489
|
-
|
490
|
-
|
482
|
+
def update_pos(pos)
|
483
|
+
@pos = pos
|
484
|
+
end
|
491
485
|
|
492
|
-
|
493
|
-
|
486
|
+
def read_pos
|
487
|
+
@pos
|
488
|
+
end
|
489
|
+
|
490
|
+
def read_inode
|
491
|
+
@inode
|
492
|
+
end
|
494
493
|
end
|
495
494
|
end
|
496
495
|
end
|
497
|
-
|
498
|
-
|
499
|
-
end
|
500
|
-
|