fluentd 0.14.2 → 0.14.5

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +77 -1131
  3. data/Rakefile +15 -0
  4. data/appveyor.yml +2 -2
  5. data/example/in_forward.conf +3 -0
  6. data/example/in_forward_client.conf +37 -0
  7. data/example/in_forward_shared_key.conf +15 -0
  8. data/example/in_forward_users.conf +24 -0
  9. data/example/multi_filters.conf +61 -0
  10. data/example/out_forward.conf +13 -13
  11. data/example/out_forward_client.conf +109 -0
  12. data/example/out_forward_shared_key.conf +36 -0
  13. data/example/out_forward_users.conf +65 -0
  14. data/example/{out_buffered_null.conf → out_null.conf} +10 -6
  15. data/example/secondary_file.conf +41 -0
  16. data/fluentd.gemspec +19 -16
  17. data/lib/fluent/agent.rb +5 -0
  18. data/lib/fluent/command/debug.rb +4 -4
  19. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +2 -0
  20. data/lib/fluent/compat/output.rb +5 -2
  21. data/lib/fluent/config/configure_proxy.rb +26 -5
  22. data/lib/fluent/config/error.rb +3 -0
  23. data/lib/fluent/config/section.rb +15 -0
  24. data/lib/fluent/event_router.rb +77 -4
  25. data/lib/fluent/plugin/base.rb +12 -3
  26. data/lib/fluent/plugin/buffer.rb +5 -1
  27. data/lib/fluent/plugin/filter.rb +48 -6
  28. data/lib/fluent/plugin/filter_record_transformer.rb +3 -1
  29. data/lib/fluent/plugin/filter_stdout.rb +0 -4
  30. data/lib/fluent/plugin/in_debug_agent.rb +5 -5
  31. data/lib/fluent/plugin/in_dummy.rb +9 -1
  32. data/lib/fluent/plugin/in_forward.rb +330 -62
  33. data/lib/fluent/plugin/in_monitor_agent.rb +31 -77
  34. data/lib/fluent/plugin/in_tail.rb +76 -92
  35. data/lib/fluent/plugin/multi_output.rb +4 -0
  36. data/lib/fluent/plugin/out_forward.rb +328 -222
  37. data/lib/fluent/plugin/out_null.rb +37 -0
  38. data/lib/fluent/plugin/out_secondary_file.rb +128 -0
  39. data/lib/fluent/plugin/out_stdout.rb +38 -2
  40. data/lib/fluent/plugin/output.rb +29 -6
  41. data/lib/fluent/plugin/storage_local.rb +16 -0
  42. data/lib/fluent/plugin_helper/timer.rb +6 -1
  43. data/lib/fluent/root_agent.rb +4 -1
  44. data/lib/fluent/supervisor.rb +62 -9
  45. data/lib/fluent/test/base.rb +3 -0
  46. data/lib/fluent/test/driver/base.rb +5 -0
  47. data/lib/fluent/test/formatter_test.rb +2 -0
  48. data/lib/fluent/test/parser_test.rb +2 -0
  49. data/lib/fluent/test/startup_shutdown.rb +33 -0
  50. data/lib/fluent/version.rb +1 -1
  51. data/lib/fluent/winsvc.rb +1 -2
  52. data/test/compat/test_calls_super.rb +2 -0
  53. data/test/config/test_configurable.rb +88 -0
  54. data/test/config/test_types.rb +7 -3
  55. data/test/plugin/test_filter.rb +121 -23
  56. data/test/plugin/test_filter_record_transformer.rb +22 -6
  57. data/test/plugin/test_in_debug_agent.rb +2 -2
  58. data/test/plugin/test_in_forward.rb +920 -407
  59. data/test/plugin/test_in_monitor_agent.rb +333 -0
  60. data/test/plugin/test_in_tail.rb +686 -595
  61. data/test/plugin/test_out_forward.rb +150 -207
  62. data/test/plugin/test_out_null.rb +85 -9
  63. data/test/plugin/test_out_secondary_file.rb +432 -0
  64. data/test/plugin/test_out_stdout.rb +143 -45
  65. data/test/plugin/test_output.rb +53 -0
  66. data/test/plugin/test_output_as_buffered.rb +13 -0
  67. data/test/plugin/test_output_as_buffered_overflow.rb +3 -0
  68. data/test/plugin/test_output_as_buffered_retries.rb +11 -0
  69. data/test/plugin/test_output_as_buffered_secondary.rb +12 -0
  70. data/test/plugin/test_output_as_standard.rb +12 -0
  71. data/test/plugin_helper/test_compat_parameters.rb +2 -0
  72. data/test/plugin_helper/test_timer.rb +31 -0
  73. data/test/test_event_router.rb +87 -0
  74. data/test/test_filter.rb +48 -6
  75. data/test/test_log.rb +5 -2
  76. data/test/test_output.rb +41 -1
  77. data/test/test_plugin_classes.rb +17 -9
  78. data/test/test_root_agent.rb +188 -0
  79. metadata +62 -68
  80. data/lib/fluent/plugin/out_buffered_null.rb +0 -59
  81. data/lib/fluent/plugin/out_buffered_stdout.rb +0 -70
  82. data/test/plugin/test_out_buffered_null.rb +0 -79
  83. data/test/plugin/test_out_buffered_stdout.rb +0 -122
@@ -42,6 +42,8 @@ module Fluent
42
42
  config_param :linger_timeout, :integer, default: 0
43
43
  # 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.
44
44
  config_param :blocking_timeout, :time, default: 0.5
45
+ desc 'Connections will be disconnected right after receiving first message if this value is true.'
46
+ config_param :deny_keepalive, :bool, default: false
45
47
 
46
48
  desc 'Log warning if received chunk size is larger than this value.'
47
49
  config_param :chunk_size_warn_limit, :size, default: nil
@@ -49,9 +51,80 @@ module Fluent
49
51
  config_param :chunk_size_limit, :size, default: nil
50
52
  desc 'Skip an event if incoming event is invalid.'
51
53
  config_param :skip_invalid_event, :bool, default: false
54
+ desc "The field name of the client's hostname."
55
+ config_param :source_hostname_key, :string, default: nil
56
+
57
+ config_section :security, required: false, multi: false do
58
+ desc 'The hostname'
59
+ config_param :self_hostname, :string
60
+ desc 'Shared key for authentication'
61
+ config_param :shared_key, :string
62
+ desc 'If true, use user based authentication'
63
+ config_param :user_auth, :bool, default: false
64
+ desc 'Allow anonymous source. <client> sections required if disabled.'
65
+ config_param :allow_anonymous_source, :bool, default: true
66
+
67
+ ### User based authentication
68
+ config_section :user, param_name: :users, required: false, multi: true do
69
+ desc 'The username for authentication'
70
+ config_param :username, :string
71
+ desc 'The password for authentication'
72
+ config_param :password, :string
73
+ end
74
+
75
+ ### Client ip/network authentication & per_host shared key
76
+ config_section :client, param_name: :clients, required: false, multi: true do
77
+ desc 'The IP address or host name of the client'
78
+ config_param :host, :string, default: nil
79
+ desc 'Network address specification'
80
+ config_param :network, :string, default: nil
81
+ desc 'Shared key per client'
82
+ config_param :shared_key, :string, default: nil
83
+ desc 'Array of username.'
84
+ config_param :users, :array, default: []
85
+ end
86
+ end
52
87
 
53
88
  def configure(conf)
54
89
  super
90
+
91
+ if @security
92
+ if @security.user_auth && @security.users.empty?
93
+ raise Fluent::ConfigError, "<user> sections required if user_auth enabled"
94
+ end
95
+ if !@security.allow_anonymous_source && @security.clients.empty?
96
+ raise Fluent::ConfigError, "<client> sections required if allow_anonymous_source disabled"
97
+ end
98
+
99
+ @nodes = []
100
+
101
+ @security.clients.each do |client|
102
+ if client.host && client.network
103
+ raise Fluent::ConfigError, "both of 'host' and 'network' are specified for client"
104
+ end
105
+ if !client.host && !client.network
106
+ raise Fluent::ConfigError, "Either of 'host' and 'network' must be specified for client"
107
+ end
108
+ source = nil
109
+ if client.host
110
+ begin
111
+ source = IPSocket.getaddress(client.host)
112
+ rescue SocketError => e
113
+ raise Fluent::ConfigError, "host '#{client.host}' cannot be resolved"
114
+ end
115
+ end
116
+ source_addr = begin
117
+ IPAddr.new(source || client.network)
118
+ rescue ArgumentError => e
119
+ raise Fluent::ConfigError, "network '#{client.network}' address format is invalid"
120
+ end
121
+ @nodes.push({
122
+ address: source_addr,
123
+ shared_key: (client.shared_key || @security.shared_key),
124
+ users: client.users
125
+ })
126
+ end
127
+ end
55
128
  end
56
129
 
57
130
  def start
@@ -98,7 +171,7 @@ module Fluent
98
171
  def listen(client)
99
172
  log.info "listening fluent socket on #{@bind}:#{@port}"
100
173
  sock = client.listen_tcp(@bind, @port)
101
- s = Coolio::TCPServer.new(sock, nil, Handler, @linger_timeout, log, method(:on_message))
174
+ s = Coolio::TCPServer.new(sock, nil, Handler, @linger_timeout, log, method(:handle_connection))
102
175
  s.listen(@backlog) unless @backlog.nil?
103
176
  s
104
177
  end
@@ -122,6 +195,99 @@ module Fluent
122
195
 
123
196
  private
124
197
 
198
+ def handle_connection(conn)
199
+ send_data = ->(serializer, data){ conn.write serializer.call(data) }
200
+
201
+ log.trace "connected fluent socket", address: conn.remote_addr, port: conn.remote_port
202
+ state = :established
203
+ nonce = nil
204
+ user_auth_salt = nil
205
+
206
+ if @security
207
+ # security enabled session MUST use MessagePack as serialization format
208
+ state = :helo
209
+ nonce = generate_salt
210
+ user_auth_salt = generate_salt
211
+ send_data.call(:to_msgpack.to_proc, generate_helo(nonce, user_auth_salt))
212
+ state = :pingpong
213
+ end
214
+
215
+ log.trace "accepted fluent socket", address: conn.remote_addr, port: conn.remote_port
216
+
217
+ read_messages(conn) do |msg, chunk_size, serializer|
218
+ case state
219
+ when :pingpong
220
+ success, reason_or_salt, shared_key = check_ping(msg, conn.remote_addr, user_auth_salt, nonce)
221
+ unless success
222
+ send_data.call(serializer, generate_pong(false, reason_or_salt, nonce, shared_key))
223
+ conn.close
224
+ next
225
+ end
226
+ send_data.call(serializer, generate_pong(true, reason_or_salt, nonce, shared_key))
227
+
228
+ log.debug "connection established", address: conn.remote_addr, port: conn.remote_port
229
+ state = :established
230
+ when :established
231
+ options = on_message(msg, chunk_size, conn.remote_addr)
232
+ if options && r = response(options)
233
+ send_data.call(serializer, r)
234
+ log.trace "sent response to fluent socket", address: conn.remote_addr, response: r
235
+ if @deny_keepalive
236
+ conn.on_write_complete do
237
+ conn.close
238
+ end
239
+ end
240
+ else
241
+ if @deny_keepalive
242
+ conn.close
243
+ end
244
+ end
245
+ else
246
+ raise "BUG: unknown session state: #{state}"
247
+ end
248
+ end
249
+ end
250
+
251
+ def read_messages(conn, &block)
252
+ feeder = nil
253
+ serializer = nil
254
+ bytes = 0
255
+ conn.on_data do |data|
256
+ # only for first call of callback
257
+ unless feeder
258
+ first = data[0]
259
+ if first == '{' || first == '[' # json
260
+ parser = Yajl::Parser.new
261
+ parser.on_parse_complete = ->(obj){
262
+ block.call(obj, bytes, serializer)
263
+ bytes = 0
264
+ }
265
+ serializer = :to_json.to_proc
266
+ feeder = ->(d){ parser << d }
267
+ else # msgpack
268
+ parser = Fluent::Engine.msgpack_factory.unpacker
269
+ serializer = :to_msgpack.to_proc
270
+ feeder = ->(d){
271
+ parser.feed_each(d){|obj|
272
+ block.call(obj, bytes, serializer)
273
+ bytes = 0
274
+ }
275
+ }
276
+ end
277
+ end
278
+
279
+ bytes += data.bytesize
280
+ feeder.call(data)
281
+ end
282
+ end
283
+
284
+ def response(option)
285
+ if option && option['chunk']
286
+ return { 'ack' => option['chunk'] }
287
+ end
288
+ nil
289
+ end
290
+
125
291
  # message Entry {
126
292
  # 1: long time
127
293
  # 2: object record
@@ -145,7 +311,7 @@ module Fluent
145
311
  # 3: object record
146
312
  # 4: object option (optional)
147
313
  # }
148
- def on_message(msg, chunk_size, source)
314
+ def on_message(msg, chunk_size, peeraddr)
149
315
  if msg.nil?
150
316
  # for future TCP heartbeat_request
151
317
  return
@@ -153,7 +319,7 @@ module Fluent
153
319
 
154
320
  # TODO: raise an exception if broken chunk is generated by recoverable situation
155
321
  unless msg.is_a?(Array)
156
- log.warn "incoming chunk is broken:", source: source, msg: msg
322
+ log.warn "incoming chunk is broken:", source: source_message(peeraddr), msg: msg
157
323
  return
158
324
  end
159
325
 
@@ -161,24 +327,26 @@ module Fluent
161
327
  entries = msg[1]
162
328
 
163
329
  if @chunk_size_limit && (chunk_size > @chunk_size_limit)
164
- log.warn "Input chunk size is larger than 'chunk_size_limit', dropped:", tag: tag, source: source, limit: @chunk_size_limit, size: chunk_size
330
+ log.warn "Input chunk size is larger than 'chunk_size_limit', dropped:", tag: tag, source: source_message(peeraddr), limit: @chunk_size_limit, size: chunk_size
165
331
  return
166
332
  elsif @chunk_size_warn_limit && (chunk_size > @chunk_size_warn_limit)
167
- log.warn "Input chunk size is larger than 'chunk_size_warn_limit':", tag: tag, source: source, limit: @chunk_size_warn_limit, size: chunk_size
333
+ log.warn "Input chunk size is larger than 'chunk_size_warn_limit':", tag: tag, source: source_message(peeraddr), limit: @chunk_size_warn_limit, size: chunk_size
168
334
  end
169
335
 
170
- if entries.class == String
336
+ case entries
337
+ when String
171
338
  # PackedForward
172
339
  option = msg[2]
173
340
  size = (option && option['size']) || 0
174
341
  es = MessagePackEventStream.new(entries, nil, size.to_i)
175
- es = check_and_skip_invalid_event(tag, es, source) if @skip_invalid_event
342
+ es = check_and_skip_invalid_event(tag, es, peeraddr) if @skip_invalid_event
343
+ es = add_source_host(es, peeraddr[2]) if @source_hostname_key
176
344
  router.emit_stream(tag, es)
177
345
 
178
- elsif entries.class == Array
346
+ when Array
179
347
  # Forward
180
348
  es = if @skip_invalid_event
181
- check_and_skip_invalid_event(tag, entries, source)
349
+ check_and_skip_invalid_event(tag, entries, peeraddr)
182
350
  else
183
351
  es = MultiEventStream.new
184
352
  entries.each { |e|
@@ -190,6 +358,7 @@ module Fluent
190
358
  }
191
359
  es
192
360
  end
361
+ es = add_source_host(es, peeraddr[2]) if @source_hostname_key
193
362
  router.emit_stream(tag, es)
194
363
  option = msg[2]
195
364
 
@@ -198,11 +367,12 @@ module Fluent
198
367
  time = msg[1]
199
368
  record = msg[2]
200
369
  if @skip_invalid_event && invalid_event?(tag, time, record)
201
- log.warn "got invalid event and drop it:", source: source, tag: tag, time: time, record: record
370
+ log.warn "got invalid event and drop it:", source: source_message(peeraddr), tag: tag, time: time, record: record
202
371
  return msg[3] # retry never succeeded so return ack and drop incoming event.
203
372
  end
204
373
  return if record.nil?
205
374
  time = Engine.now if time.to_i == 0
375
+ record[@source_hostname_key] = peeraddr[2] if @source_hostname_key
206
376
  router.emit(tag, time, record)
207
377
  option = msg[3]
208
378
  end
@@ -215,11 +385,11 @@ module Fluent
215
385
  !((time.is_a?(Integer) || time.is_a?(::Fluent::EventTime)) && record.is_a?(Hash) && tag.is_a?(String))
216
386
  end
217
387
 
218
- def check_and_skip_invalid_event(tag, es, source)
388
+ def check_and_skip_invalid_event(tag, es, peeraddr)
219
389
  new_es = MultiEventStream.new
220
390
  es.each { |time, record|
221
391
  if invalid_event?(tag, time, record)
222
- log.warn "skip invalid event:", source: source, tag: tag, time: time, record: record
392
+ log.warn "skip invalid event:", source: source_message(peeraddr), tag: tag, time: time, record: record
223
393
  next
224
394
  end
225
395
  new_es.add(time, record)
@@ -227,22 +397,143 @@ module Fluent
227
397
  new_es
228
398
  end
229
399
 
400
+ def add_source_host(es, host)
401
+ new_es = MultiEventStream.new
402
+ es.each { |time, record|
403
+ record[@source_hostname_key] = host
404
+ new_es.add(time, record)
405
+ }
406
+ new_es
407
+ end
408
+
409
+ def source_message(peeraddr)
410
+ _, port, host, addr = peeraddr
411
+ "host: #{host}, addr: #{addr}, port: #{port}"
412
+ end
413
+
414
+ def select_authenticate_users(node, username)
415
+ if node.nil? || node[:users].empty?
416
+ @security.users.select{|u| u.username == username}
417
+ else
418
+ @security.users.select{|u| node[:users].include?(u.username) && u.username == username}
419
+ end
420
+ end
421
+
422
+ def generate_salt
423
+ OpenSSL::Random.random_bytes(16)
424
+ end
425
+
426
+ def generate_helo(nonce, user_auth_salt)
427
+ log.debug "generating helo"
428
+ # ['HELO', options(hash)]
429
+ ['HELO', {'nonce' => nonce, 'auth' => (@security ? user_auth_salt : ''), 'keepalive' => !@deny_keepalive}]
430
+ end
431
+
432
+ ##### Authentication Handshake
433
+ #
434
+ # 1. (client) connect to server
435
+ # * Socket handshake, checks certificate and its significate (in client, if using SSL)
436
+ # 2. (server)
437
+ # * check network/domain acl (if enabled)
438
+ # * disconnect when failed
439
+ # 3. (server) send HELO
440
+ # * ['HELO', options(hash)]
441
+ # * options:
442
+ # * nonce: string (required)
443
+ # * auth: string or blank_string (string: authentication required, and its salt is this value)
444
+ # 4. (client) send PING
445
+ # * ['PING', selfhostname, sharedkey_salt, sha512_hex(sharedkey_salt + selfhostname + nonce + sharedkey), username || '', sha512_hex(auth_salt + username + password) || '']
446
+ # 5. (server) check PING
447
+ # * check sharedkey
448
+ # * check username / password (if required)
449
+ # * send PONG FAILURE if failed
450
+ # * ['PONG', false, 'reason of authentication failure', '', '']
451
+ # 6. (server) send PONG
452
+ # * ['PONG', bool(authentication result), 'reason if authentication failed', selfhostname, sha512_hex(salt + selfhostname + nonce + sharedkey)]
453
+ # 7. (client) check PONG
454
+ # * check sharedkey
455
+ # * disconnect when failed
456
+ # 8. connection established
457
+ # * send data from client
458
+
459
+ def check_ping(message, remote_addr, user_auth_salt, nonce)
460
+ log.debug "checking ping"
461
+ # ['PING', self_hostname, shared_key_salt, sha512_hex(shared_key_salt + self_hostname + nonce + shared_key), username || '', sha512_hex(auth_salt + username + password) || '']
462
+ unless message.size == 6 && message[0] == 'PING'
463
+ return false, 'invalid ping message'
464
+ end
465
+ _ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
466
+
467
+ node = @nodes.select{|n| n[:address].include?(remote_addr) rescue false }.first
468
+ if !node && !@security.allow_anonymous_source
469
+ log.warn "Anonymous client disallowed", address: remote_addr, hostname: hostname
470
+ return false, "anonymous source host '#{remote_addr}' denied", nil
471
+ end
472
+
473
+ shared_key = node ? node[:shared_key] : @security.shared_key
474
+ serverside = Digest::SHA512.new.update(shared_key_salt).update(hostname).update(nonce).update(shared_key).hexdigest
475
+ if shared_key_hexdigest != serverside
476
+ log.warn "Shared key mismatch", address: remote_addr, hostname: hostname
477
+ return false, 'shared_key mismatch', nil
478
+ end
479
+
480
+ if @security.user_auth
481
+ users = select_authenticate_users(node, username)
482
+ success = false
483
+ users.each do |user|
484
+ passhash = Digest::SHA512.new.update(user_auth_salt).update(username).update(user[:password]).hexdigest
485
+ success ||= (passhash == password_digest)
486
+ end
487
+ unless success
488
+ log.warn "Authentication failed", address: remote_addr, hostname: hostname, username: username
489
+ return false, 'username/password mismatch', nil
490
+ end
491
+ end
492
+
493
+ return true, shared_key_salt, shared_key
494
+ end
495
+
496
+ def generate_pong(auth_result, reason_or_salt, nonce, shared_key)
497
+ log.debug "generating pong"
498
+ # ['PONG', bool(authentication result), 'reason if authentication failed', self_hostname, sha512_hex(salt + self_hostname + nonce + sharedkey)]
499
+ unless auth_result
500
+ return ['PONG', false, reason_or_salt, '', '']
501
+ end
502
+
503
+ shared_key_digest_hex = Digest::SHA512.new.update(reason_or_salt).update(@security.self_hostname).update(nonce).update(shared_key).hexdigest
504
+ ['PONG', true, '', @security.self_hostname, shared_key_digest_hex]
505
+ end
506
+
230
507
  class Handler < Coolio::Socket
508
+ attr_reader :protocol, :remote_port, :remote_addr, :remote_host
509
+
231
510
  PEERADDR_FAILED = ["?", "?", "name resolusion failed", "?"]
232
511
 
233
- def initialize(io, linger_timeout, log, on_message)
512
+ def initialize(io, linger_timeout, log, on_connect_callback)
234
513
  super(io)
235
514
 
515
+ @peeraddr = nil
236
516
  if io.is_a?(TCPSocket) # for unix domain socket support in the future
237
- _proto, port, host, addr = ( io.peeraddr rescue PEERADDR_FAILED )
238
- @source = "host: #{host}, addr: #{addr}, port: #{port}"
239
-
517
+ @peeraddr = (io.peeraddr rescue PEERADDR_FAILED)
240
518
  opt = [1, linger_timeout].pack('I!I!') # { int l_onoff; int l_linger; }
241
519
  io.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
242
520
  end
243
521
 
522
+ ### TODO: disabling name rev resolv
523
+ proto, port, host, addr = ( io.peeraddr rescue PEERADDR_FAILED )
524
+ if addr == '?'
525
+ port, addr = *Socket.unpack_sockaddr_in(io.getpeername) rescue nil
526
+ end
527
+ @protocol = proto
528
+ @remote_port = port
529
+ @remote_addr = addr
530
+ @remote_host = host
531
+ @writing = false
532
+ @closing = false
533
+ @mutex = Mutex.new
534
+
244
535
  @chunk_counter = 0
245
- @on_message = on_message
536
+ @on_connect_callback = on_connect_callback
246
537
  @log = log
247
538
  @log.trace {
248
539
  begin
@@ -251,69 +542,46 @@ module Fluent
251
542
  remote_port = nil
252
543
  remote_addr = nil
253
544
  end
254
- "accepted fluent socket from '#{remote_addr}:#{remote_port}': object_id=#{self.object_id}"
545
+ [ "accepted fluent socket", {address: remote_addr, port: remote_port, instance: self.object_id} ]
255
546
  }
256
547
  end
257
548
 
258
549
  def on_connect
550
+ @on_connect_callback.call(self)
259
551
  end
260
552
 
261
- def on_read(data)
262
- first = data[0]
263
- if first == '{' || first == '['
264
- m = method(:on_read_json)
265
- @serializer = :to_json.to_proc
266
- @y = Yajl::Parser.new
267
- @y.on_parse_complete = lambda { |obj|
268
- option = @on_message.call(obj, @chunk_counter, @source)
269
- respond option
270
- @chunk_counter = 0
271
- }
272
- else
273
- m = method(:on_read_msgpack)
274
- @serializer = :to_msgpack.to_proc
275
- @u = Fluent::Engine.msgpack_factory.unpacker
276
- end
277
-
278
- (class << self; self; end).module_eval do
279
- define_method(:on_read, m)
280
- end
281
- m.call(data)
553
+ # API to register callback for data arrival
554
+ def on_data(&callback)
555
+ @on_read_callback = callback
282
556
  end
283
557
 
284
- def on_read_json(data)
285
- @chunk_counter += data.bytesize
286
- @y << data
558
+ def on_read(data)
559
+ @on_read_callback.call(data)
287
560
  rescue => e
288
- @log.error "forward error", error: e, error_class: e.class
561
+ @log.error "unexpected error on reading data from client", address: @remote_addr, error: e
289
562
  @log.error_backtrace
290
563
  close
291
564
  end
292
565
 
293
- def on_read_msgpack(data)
294
- @chunk_counter += data.bytesize
295
- @u.feed_each(data) do |obj|
296
- option = @on_message.call(obj, @chunk_counter, @source)
297
- respond option if option
298
- @chunk_counter = 0
566
+ def on_write_complete
567
+ closing = @mutex.synchronize {
568
+ @writing = false
569
+ @closing
570
+ }
571
+ if closing
572
+ close
299
573
  end
300
- rescue => e
301
- @log.error "forward error", error: e, error_class: e.class
302
- @log.error_backtrace
303
- close
304
574
  end
305
575
 
306
- def respond(option)
307
- if option && option['chunk']
308
- res = { 'ack' => option['chunk'] }
309
- write @serializer.call(res)
310
- @log.trace { "sent response to fluent socket" }
576
+ def close
577
+ writing = @mutex.synchronize {
578
+ @closing = true
579
+ @writing
580
+ }
581
+ unless writing
582
+ super
311
583
  end
312
584
  end
313
-
314
- def on_close
315
- @log.trace { "closed socket" }
316
- end
317
585
  end
318
586
 
319
587
  class HeartbeatRequestHandler < Coolio::IO