fluent-plugin-kafka 0.2.2 → 0.3.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 95430357052a2b7305214ffa1f5c0ad426e52914
4
- data.tar.gz: 57736e74a0bc9b65441bc41086f7b9875e72a590
3
+ metadata.gz: 64803e0c72fc4c81fb01fb053436a944c768ba6a
4
+ data.tar.gz: 602f630699406dfeef4712584cbdb95f1f06483f
5
5
  SHA512:
6
- metadata.gz: a7f89337ddc1f28e8762de822738d467d16ef454a0bf64570f8bfbd13420dbbc53a2477b4cce836c8234d7aee96726a13d0fbf12ac9e33216f6cf82eea1ee174
7
- data.tar.gz: 5568aec86102a4c40aae4e1e8b43c5e1829811e9014589e45981491fc2b1fece473f0042f1b7188e3b09d678eff8cb77e0ac63de7a00d09fe34f7b9bfe80b12f
6
+ metadata.gz: 9e80898dbac167495ceaf99ef48c8c077974190da51173e97a87c69e7064da9213781d2b9762a68f12f4fd87ba603a5b9fca92f562c2880a109e13104445c890
7
+ data.tar.gz: 88c81bc3ca6e27bead1010ff6112cf1d80fcd8de665e46c030eada336704d0ea33effc2bc101aa96622463b069bdc8aeffff2c3222395f6321b380bff0e9a8e3
@@ -12,12 +12,11 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "fluent-plugin-kafka"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = '0.2.2'
15
+ gem.version = '0.3.0.rc1'
16
16
  gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
17
- gem.add_dependency 'poseidon_cluster'
18
17
  gem.add_dependency 'ltsv'
19
18
  gem.add_dependency 'zookeeper'
20
- gem.add_dependency 'ruby-kafka', '~> 0.3.9'
19
+ gem.add_dependency 'ruby-kafka', '~> 0.3.11'
21
20
  gem.add_development_dependency "rake", ">= 0.9.2"
22
21
  gem.add_development_dependency "test-unit", ">= 3.0.8"
23
22
  end
@@ -1,4 +1,6 @@
1
1
  require 'fluent/input'
2
+ require 'fluent/plugin/kafka_plugin_util'
3
+
2
4
  module Fluent
3
5
 
4
6
  class KafkaInput < Input
@@ -8,10 +10,12 @@ class KafkaInput < Input
8
10
  :desc => "Supported format: (json|text|ltsv|msgpack)"
9
11
  config_param :message_key, :string, :default => 'message',
10
12
  :desc => "For 'text' format only."
11
- config_param :host, :string, :default => 'localhost',
13
+ config_param :host, :string, :default => nil,
12
14
  :desc => "Broker host"
13
- config_param :port, :integer, :default => 9092,
15
+ config_param :port, :integer, :default => nil,
14
16
  :desc => "Broker port"
17
+ config_param :brokers, :string, :default => 'localhost:9092',
18
+ :desc => "List of broker-host:port, separate with comma, must set."
15
19
  config_param :interval, :integer, :default => 1, # seconds
16
20
  :desc => "Interval (Unit: seconds)"
17
21
  config_param :topics, :string, :default => nil,
@@ -30,24 +34,25 @@ class KafkaInput < Input
30
34
  config_param :offset_zookeeper, :string, :default => nil
31
35
  config_param :offset_zk_root_node, :string, :default => '/fluent-plugin-kafka'
32
36
 
33
- # poseidon PartitionConsumer options
37
+ # Kafka#fetch_messages options
34
38
  config_param :max_bytes, :integer, :default => nil,
35
39
  :desc => "Maximum number of bytes to fetch."
36
- config_param :max_wait_ms, :integer, :default => nil,
40
+ config_param :max_wait_time, :integer, :default => nil,
37
41
  :desc => "How long to block until the server sends us data."
38
42
  config_param :min_bytes, :integer, :default => nil,
39
43
  :desc => "Smallest amount of data the server should send us."
40
44
  config_param :socket_timeout_ms, :integer, :default => nil,
41
45
  :desc => "How long to wait for reply from server. Should be higher than max_wait_ms."
42
46
 
47
+ include KafkaPluginUtil::SSLSettings
48
+
43
49
  unless method_defined?(:router)
44
50
  define_method("router") { Fluent::Engine }
45
51
  end
46
52
 
47
53
  def initialize
48
54
  super
49
- require 'poseidon'
50
- require 'zookeeper'
55
+ require 'kafka'
51
56
  end
52
57
 
53
58
  def configure(conf)
@@ -73,38 +78,90 @@ class KafkaInput < Input
73
78
  raise ConfigError, "kafka: 'topics' or 'topic element' is a require parameter"
74
79
  end
75
80
 
81
+ # For backward compatibility
82
+ @brokers = case
83
+ when @host && @port
84
+ ["#{@host}:#{@port}"]
85
+ when @host
86
+ ["#{@host}:9092"]
87
+ when @port
88
+ ["localhost:#{@port}"]
89
+ else
90
+ @brokers
91
+ end
92
+
93
+ if conf['max_wait_ms']
94
+ log.warn "'max_wait_ms' parameter is deprecated. Use second unit 'max_wait_time' instead"
95
+ @max_wait_time = conf['max_wait_ms'].to_i / 1000
96
+ end
97
+
98
+ @max_wait_time = @interval if @max_wait_time.nil?
99
+
100
+ require 'zookeeper' if @offset_zookeeper
101
+
102
+ @parser_proc = setup_parser
103
+ end
104
+
105
+ def setup_parser
76
106
  case @format
77
107
  when 'json'
78
108
  require 'yajl'
109
+ Proc.new { |msg, te|
110
+ r = Yajl::Parser.parse(msg.value)
111
+ add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
112
+ r
113
+ }
79
114
  when 'ltsv'
80
115
  require 'ltsv'
116
+ Proc.new { |msg, te|
117
+ r = LTSV.parse(msg.value).first
118
+ add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
119
+ r
120
+ }
81
121
  when 'msgpack'
82
122
  require 'msgpack'
123
+ Proc.new { |msg, te|
124
+ r = MessagePack.unpack(msg.value)
125
+ add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
126
+ r
127
+ }
128
+ when 'text'
129
+ Proc.new { |msg, te|
130
+ r = {@message_key => msg.value}
131
+ add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
132
+ r
133
+ }
83
134
  end
84
135
  end
85
136
 
137
+ def add_offset_in_hash(hash, te, offset)
138
+ hash['kafka_topic'.freeze] = te.topic
139
+ hash['kafka_partition'.freeze] = te.partition
140
+ hash['kafka_offset'.freeze] = offset
141
+ end
142
+
86
143
  def start
87
144
  super
145
+
88
146
  @loop = Coolio::Loop.new
89
147
  opt = {}
90
148
  opt[:max_bytes] = @max_bytes if @max_bytes
91
- opt[:max_wait_ms] = @max_wait_ms if @max_wait_ms
149
+ opt[:max_wait_time] = @max_wait_time if @max_wait_time
92
150
  opt[:min_bytes] = @min_bytes if @min_bytes
93
- opt[:socket_timeout_ms] = @socket_timeout_ms if @socket_timeout_ms
94
151
 
152
+ @kafka = Kafka.new(seed_brokers: @brokers, client_id: @client_id,
153
+ ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
154
+ ssl_client_cert: read_ssl_file(@ssl_client_cert),
155
+ ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key))
95
156
  @zookeeper = Zookeeper.new(@offset_zookeeper) if @offset_zookeeper
96
157
 
97
158
  @topic_watchers = @topic_list.map {|topic_entry|
98
159
  offset_manager = OffsetManager.new(topic_entry, @zookeeper, @offset_zk_root_node) if @offset_zookeeper
99
160
  TopicWatcher.new(
100
161
  topic_entry,
101
- @host,
102
- @port,
103
- @client_id,
162
+ @kafka,
104
163
  interval,
105
- @format,
106
- @message_key,
107
- @add_offset_in_record,
164
+ @parser_proc,
108
165
  @add_prefix,
109
166
  @add_suffix,
110
167
  offset_manager,
@@ -120,26 +177,24 @@ class KafkaInput < Input
120
177
  def shutdown
121
178
  @loop.stop
122
179
  @zookeeper.close! if @zookeeper
180
+ @thread.join
181
+ @kafka.close
123
182
  super
124
183
  end
125
184
 
126
185
  def run
127
186
  @loop.run
128
- rescue
129
- $log.error "unexpected error", :error=>$!.to_s
187
+ rescue => e
188
+ $log.error "unexpected error", :error => e.to_s
130
189
  $log.error_backtrace
131
190
  end
132
191
 
133
192
  class TopicWatcher < Coolio::TimerWatcher
134
- def initialize(topic_entry, host, port, client_id, interval, format, message_key, add_offset_in_record, add_prefix, add_suffix, offset_manager, router, options={})
193
+ def initialize(topic_entry, kafka, interval, parser, add_prefix, add_suffix, offset_manager, router, options={})
135
194
  @topic_entry = topic_entry
136
- @host = host
137
- @port = port
138
- @client_id = client_id
195
+ @kafka = kafka
139
196
  @callback = method(:consume)
140
- @format = format
141
- @message_key = message_key
142
- @add_offset_in_record = add_offset_in_record
197
+ @parser = parser
143
198
  @add_prefix = add_prefix
144
199
  @add_suffix = add_suffix
145
200
  @options = options
@@ -150,98 +205,53 @@ class KafkaInput < Input
150
205
  if @topic_entry.offset == -1 && offset_manager
151
206
  @next_offset = offset_manager.next_offset
152
207
  end
153
- @consumer = create_consumer(@next_offset)
208
+ @fetch_args = {
209
+ topic: @topic_entry.topic,
210
+ partition: @topic_entry.partition,
211
+ }.merge(@options)
154
212
 
155
213
  super(interval, true)
156
214
  end
157
215
 
158
216
  def on_timer
159
217
  @callback.call
160
- rescue
161
- # TODO log?
162
- $log.error $!.to_s
163
- $log.error_backtrace
218
+ rescue => e
219
+ # TODO log?
220
+ $log.error e.to_s
221
+ $log.error_backtrace
164
222
  end
165
223
 
166
224
  def consume
225
+ offset = @next_offset
226
+ @fetch_args[:offset] = offset
227
+ messages = @kafka.fetch_messages(@fetch_args)
228
+
229
+ return if messages.size.zero?
230
+
167
231
  es = MultiEventStream.new
168
232
  tag = @topic_entry.topic
169
233
  tag = @add_prefix + "." + tag if @add_prefix
170
234
  tag = tag + "." + @add_suffix if @add_suffix
171
235
 
172
- if @offset_manager && @consumer.next_offset != @next_offset
173
- @consumer = create_consumer(@next_offset)
174
- end
175
-
176
- @consumer.fetch.each { |msg|
236
+ messages.each { |msg|
177
237
  begin
178
- msg_record = parse_line(msg.value)
179
- msg_record = decorate_offset(msg_record, msg.offset) if @add_offset_in_record
180
- es.add(Engine.now, msg_record)
181
- rescue
182
- $log.warn msg_record.to_s, :error=>$!.to_s
238
+ es.add(Engine.now, @parser.call(msg, @topic_entry))
239
+ rescue => e
240
+ $log.warn "parser error in #{@topic_entry.topic}/#{@topic_entry.partition}", :error => e.to_s, :value => msg.value, :offset => msg.offset
183
241
  $log.debug_backtrace
184
242
  end
185
243
  }
244
+ offset = messages.last.offset + 1
186
245
 
187
246
  unless es.empty?
188
247
  @router.emit_stream(tag, es)
189
248
 
190
249
  if @offset_manager
191
- next_offset = @consumer.next_offset
192
- @offset_manager.save_offset(next_offset)
193
- @next_offset = next_offset
250
+ @offset_manager.save_offset(offset)
194
251
  end
252
+ @next_offset = offset
195
253
  end
196
254
  end
197
-
198
- def create_consumer(offset)
199
- @consumer.close if @consumer
200
- Poseidon::PartitionConsumer.new(
201
- @client_id, # client_id
202
- @host, # host
203
- @port, # port
204
- @topic_entry.topic, # topic
205
- @topic_entry.partition, # partition
206
- offset, # offset
207
- @options # options
208
- )
209
- end
210
-
211
- def parse_line(record)
212
- case @format
213
- when 'json'
214
- Yajl::Parser.parse(record)
215
- when 'ltsv'
216
- LTSV.parse(record)
217
- when 'msgpack'
218
- MessagePack.unpack(record)
219
- when 'text'
220
- {@message_key => record}
221
- end
222
- end
223
-
224
- def decorate_offset(record, offset)
225
- case @format
226
- when 'json'
227
- add_offset_in_hash(record, @topic_entry.topic, @topic_entry.partition, offset)
228
- when 'ltsv'
229
- record.each { |line|
230
- add_offset_in_hash(line, @topic_entry.topic, @topic_entry.partition, offset)
231
- }
232
- when 'msgpack'
233
- add_offset_in_hash(record, @topic_entry.topic, @topic_entry.partition, offset)
234
- when 'text'
235
- add_offset_in_hash(record, @topic_entry.topic, @topic_entry.partition, offset)
236
- end
237
- record
238
- end
239
-
240
- def add_offset_in_hash(hash, topic, partition, offset)
241
- hash['kafka_topic'] = topic
242
- hash['kafka_partition'] = partition
243
- hash['kafka_offset'] = offset
244
- end
245
255
  end
246
256
 
247
257
  class TopicEntry
@@ -1,19 +1,17 @@
1
1
  require 'fluent/input'
2
+ require 'fluent/plugin/kafka_plugin_util'
3
+
2
4
  module Fluent
3
5
 
4
6
  class KafkaGroupInput < Input
5
7
  Plugin.register_input('kafka_group', self)
6
8
 
7
- config_param :brokers, :string,
8
- :desc => "List of broker-host:port, separate with comma, must set."
9
- config_param :zookeepers, :string,
9
+ config_param :brokers, :string, :default => 'localhost:9092',
10
10
  :desc => "List of broker-host:port, separate with comma, must set."
11
- config_param :consumer_group, :string, :default => nil,
11
+ config_param :consumer_group, :string,
12
12
  :desc => "Consumer group name, must set."
13
13
  config_param :topics, :string,
14
14
  :desc => "Listening topics(separate with comma',')."
15
- config_param :interval, :integer, :default => 1, # seconds
16
- :desc => "Interval (Unit: seconds)"
17
15
  config_param :format, :string, :default => 'json',
18
16
  :desc => "Supported format: (json|text|ltsv|msgpack)"
19
17
  config_param :message_key, :string, :default => 'message',
@@ -23,15 +21,21 @@ class KafkaGroupInput < Input
23
21
  config_param :add_suffix, :string, :default => nil,
24
22
  :desc => "Tag suffix (Optional)"
25
23
 
26
- # poseidon PartitionConsumer options
27
- config_param :max_bytes, :integer, :default => nil,
28
- :desc => "Maximum number of bytes to fetch."
29
- config_param :max_wait_ms, :integer, :default => nil,
24
+ # Kafka consumer options
25
+ config_param :max_wait_time, :integer, :default => nil,
30
26
  :desc => "How long to block until the server sends us data."
31
27
  config_param :min_bytes, :integer, :default => nil,
32
28
  :desc => "Smallest amount of data the server should send us."
33
- config_param :socket_timeout_ms, :integer, :default => nil,
34
- :desc => "How long to wait for reply from server. Should be higher than max_wait_ms."
29
+ config_param :session_timeout, :integer, :default => nil,
30
+ :desc => "The number of seconds after which, if a client hasn't contacted the Kafka cluster"
31
+ config_param :offset_commit_interval, :integer, :default => nil,
32
+ :desc => "The interval between offset commits, in seconds"
33
+ config_param :offset_commit_threshold, :integer, :default => nil,
34
+ :desc => "The number of messages that can be processed before their offsets are committed"
35
+ config_param :start_from_beginning, :bool, :default => true,
36
+ :desc => "Whether to start from the beginning of the topic or just subscribe to new messages being produced"
37
+
38
+ include KafkaPluginUtil::SSLSettings
35
39
 
36
40
  unless method_defined?(:router)
37
41
  define_method("router") { Fluent::Engine }
@@ -39,7 +43,7 @@ class KafkaGroupInput < Input
39
43
 
40
44
  def initialize
41
45
  super
42
- require 'poseidon_cluster'
46
+ require 'kafka'
43
47
  end
44
48
 
45
49
  def _config_to_array(config)
@@ -54,125 +58,89 @@ class KafkaGroupInput < Input
54
58
 
55
59
  def configure(conf)
56
60
  super
57
- @broker_list = _config_to_array(@brokers)
58
- @zookeeper_list = _config_to_array(@zookeepers)
59
- @topic_list = _config_to_array(@topics)
60
61
 
61
- unless @consumer_group
62
- raise ConfigError, "kafka_group: 'consumer_group' is a required parameter"
62
+ $log.info "Will watch for topics #{@topics} at brokers " \
63
+ "#{@brokers} and '#{@consumer_group}' group"
64
+
65
+ @topics = _config_to_array(@topics)
66
+
67
+ if conf['max_wait_ms']
68
+ log.warn "'max_wait_ms' parameter is deprecated. Use second unit 'max_wait_time' instead"
69
+ @max_wait_time = conf['max_wait_ms'].to_i / 1000
63
70
  end
64
- $log.info "Will watch for topics #{@topic_list} at brokers " \
65
- "#{@broker_list}, zookeepers #{@zookeeper_list} and group " \
66
- "'#{@consumer_group}'"
67
71
 
72
+ @parser_proc = setup_parser
73
+ end
74
+
75
+ def setup_parser
68
76
  case @format
69
77
  when 'json'
70
78
  require 'yajl'
79
+ Proc.new { |msg| Yajl::Parser.parse(msg.value) }
71
80
  when 'ltsv'
72
81
  require 'ltsv'
82
+ Proc.new { |msg| LTSV.parse(msg.value).first }
73
83
  when 'msgpack'
74
84
  require 'msgpack'
85
+ Proc.new { |msg| MessagePack.unpack(msg.value) }
86
+ when 'text'
87
+ Proc.new { |msg| {@message_key => msg.value} }
75
88
  end
76
89
  end
77
90
 
78
91
  def start
79
92
  super
80
- @loop = Coolio::Loop.new
81
- opt = {}
82
- opt[:max_bytes] = @max_bytes if @max_bytes
83
- opt[:max_wait_ms] = @max_wait_ms if @max_wait_ms
84
- opt[:min_bytes] = @min_bytes if @min_bytes
85
- opt[:socket_timeout_ms] = @socket_timeout_ms if @socket_timeout_ms
86
-
87
- @topic_watchers = @topic_list.map {|topic|
88
- TopicWatcher.new(topic, @broker_list, @zookeeper_list, @consumer_group,
89
- interval, @format, @message_key, @add_prefix,
90
- @add_suffix, router, opt)
91
- }
92
- @topic_watchers.each {|tw|
93
- tw.attach(@loop)
93
+
94
+ consumer_opts = {:group_id => @consumer_group}
95
+ consumer_opts[:session_timeout] = @session_timeout if @session_timeout
96
+ consumer_opts[:offset_commit_interval] = @offset_commit_interval if @offset_commit_interval
97
+ consumer_opts[:offset_commit_threshold] = @offset_commit_threshold if @offset_commit_threshold
98
+
99
+ @fetch_opts = {}
100
+ @fetch_opts[:max_wait_time] = @max_wait_time if @max_wait_time
101
+ @fetch_opts[:min_bytes] = @min_bytes if @min_bytes
102
+
103
+ @kafka = Kafka.new(seed_brokers: @brokers,
104
+ ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
105
+ ssl_client_cert: read_ssl_file(@ssl_client_cert),
106
+ ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key))
107
+ @consumer = @kafka.consumer(consumer_opts)
108
+ @topics.each { |topic|
109
+ @consumer.subscribe(topic, start_from_beginning: @start_from_beginning)
94
110
  }
95
111
  @thread = Thread.new(&method(:run))
96
112
  end
97
113
 
98
114
  def shutdown
99
- @loop.stop
115
+ @consumer.stop
116
+ @thread.join
117
+ @kafka.close
100
118
  super
101
119
  end
102
120
 
103
121
  def run
104
- @loop.run
105
- rescue
106
- $log.error "unexpected error", :error=>$!.to_s
107
- $log.error_backtrace
108
- end
109
-
110
- class TopicWatcher < Coolio::TimerWatcher
111
- def initialize(topic, broker_list, zookeeper_list, consumer_group,
112
- interval, format, message_key, add_prefix, add_suffix,
113
- router, options)
114
- @topic = topic
115
- @callback = method(:consume)
116
- @format = format
117
- @message_key = message_key
118
- @add_prefix = add_prefix
119
- @add_suffix = add_suffix
120
- @router = router
121
-
122
- @consumer = Poseidon::ConsumerGroup.new(
123
- consumer_group,
124
- broker_list,
125
- zookeeper_list,
126
- topic,
127
- options
128
- )
129
-
130
- super(interval, true)
131
- end
132
-
133
- def on_timer
134
- @callback.call
135
- rescue
136
- # TODO log?
137
- $log.error $!.to_s
138
- $log.error_backtrace
139
- end
140
-
141
- def consume
122
+ @consumer.each_batch(@fetch_opts) { |batch|
142
123
  es = MultiEventStream.new
143
- tag = @topic
124
+ tag = batch.topic
144
125
  tag = @add_prefix + "." + tag if @add_prefix
145
126
  tag = tag + "." + @add_suffix if @add_suffix
146
127
 
147
- @consumer.fetch do |partition, bulk|
148
- bulk.each do |msg|
149
- begin
150
- msg_record = parse_line(msg.value)
151
- es.add(Engine.now, msg_record)
152
- rescue
153
- $log.warn msg_record.to_s, :error=>$!.to_s
154
- $log.debug_backtrace
155
- end
128
+ batch.messages.each { |msg|
129
+ begin
130
+ es.add(Engine.now, @parser_proc.call(msg))
131
+ rescue => e
132
+ $log.warn "parser error in #{batch.topic}/#{batch.partition}", :error => e.to_s, :value => msg.value, :offset => msg.offset
133
+ $log.debug_backtrace
156
134
  end
157
- end
135
+ }
158
136
 
159
137
  unless es.empty?
160
- @router.emit_stream(tag, es)
138
+ router.emit_stream(tag, es)
161
139
  end
162
- end
163
-
164
- def parse_line(record)
165
- case @format
166
- when 'json'
167
- Yajl::Parser.parse(record)
168
- when 'ltsv'
169
- LTSV.parse(record)
170
- when 'msgpack'
171
- MessagePack.unpack(record)
172
- when 'text'
173
- {@message_key => record}
174
- end
175
- end
140
+ }
141
+ rescue => e
142
+ $log.error "unexpected error", :error => e.to_s
143
+ $log.error_backtrace
176
144
  end
177
145
  end
178
146
 
@@ -0,0 +1,21 @@
1
+ module Fluent
2
+ module KafkaPluginUtil
3
+ module SSLSettings
4
+ def self.included(klass)
5
+ klass.instance_eval {
6
+ config_param :ssl_ca_cert, :string, :default => nil,
7
+ :desc => "a PEM encoded CA cert to use with and SSL connection."
8
+ config_param :ssl_client_cert, :string, :default => nil,
9
+ :desc => "a PEM encoded client cert to use with and SSL connection. Must be used in combination with ssl_client_cert_key."
10
+ config_param :ssl_client_cert_key, :string, :default => nil,
11
+ :desc => "a PEM encoded client cert key to use with and SSL connection. Must be used in combination with ssl_client_cert."
12
+ }
13
+ end
14
+
15
+ def read_ssl_file(path)
16
+ return nil if path.nil?
17
+ File.read(path)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,7 +3,10 @@ class Fluent::KafkaOutput < Fluent::Output
3
3
 
4
4
  def initialize
5
5
  super
6
- require 'poseidon'
6
+
7
+ require 'kafka'
8
+
9
+ @kafka = nil
7
10
  end
8
11
 
9
12
  config_param :brokers, :string, :default => 'localhost:9092',
@@ -25,14 +28,22 @@ DESC
25
28
  config_param :output_include_tag, :bool, :default => false
26
29
  config_param :output_include_time, :bool, :default => false
27
30
 
31
+ # https://github.com/zendesk/ruby-kafka#encryption-and-authentication-using-ssl
32
+ config_param :ssl_ca_cert, :string, :default => nil,
33
+ :desc => "a PEM encoded CA cert to use with and SSL connection."
34
+ config_param :ssl_client_cert, :string, :default => nil,
35
+ :desc => "a PEM encoded client cert to use with and SSL connection. Must be used in combination with ssl_client_cert_key."
36
+ config_param :ssl_client_cert_key, :string, :default => nil,
37
+ :desc => "a PEM encoded client cert key to use with and SSL connection. Must be used in combination with ssl_client_cert."
38
+
28
39
  # poseidon producer options
29
- config_param :max_send_retries, :integer, :default => 3,
40
+ config_param :max_send_retries, :integer, :default => 1,
30
41
  :desc => "Number of times to retry sending of messages to a leader."
31
42
  config_param :required_acks, :integer, :default => 0,
32
43
  :desc => "The number of acks required per request."
33
- config_param :ack_timeout_ms, :integer, :default => 1500,
44
+ config_param :ack_timeout, :integer, :default => nil,
34
45
  :desc => "How long the producer waits for acks."
35
- config_param :compression_codec, :string, :default => 'none',
46
+ config_param :compression_codec, :string, :default => nil,
36
47
  :desc => "The codec the producer uses to compress messages."
37
48
 
38
49
  config_param :time_format, :string, :default => nil
@@ -40,13 +51,11 @@ DESC
40
51
  attr_accessor :output_data_type
41
52
  attr_accessor :field_separator
42
53
 
43
- @seed_brokers = []
44
-
45
54
  unless method_defined?(:log)
46
55
  define_method("log") { $log }
47
56
  end
48
57
 
49
- def refresh_producer()
58
+ def refresh_client
50
59
  if @zookeeper
51
60
  @seed_brokers = []
52
61
  z = Zookeeper.new(@zookeeper)
@@ -59,8 +68,9 @@ DESC
59
68
  end
60
69
  begin
61
70
  if @seed_brokers.length > 0
62
- @producer = Poseidon::Producer.new(@seed_brokers, @client_id, :max_send_retries => @max_send_retries, :required_acks => @required_acks, :ack_timeout_ms => @ack_timeout_ms, :compression_codec => @compression_codec.to_sym)
63
- log.info "initialized producer #{@client_id}"
71
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
72
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key))
73
+ log.info "initialized kafka producer: #{@client_id}"
64
74
  else
65
75
  log.warn "No brokers found on Zookeeper"
66
76
  end
@@ -69,25 +79,24 @@ DESC
69
79
  end
70
80
  end
71
81
 
82
+ def read_ssl_file(path)
83
+ return nil if path.nil?
84
+ File.read(path)
85
+ end
86
+
72
87
  def configure(conf)
73
88
  super
89
+
74
90
  if @zookeeper
75
91
  require 'zookeeper'
76
- require 'yajl'
77
92
  else
78
93
  @seed_brokers = @brokers.match(",").nil? ? [@brokers] : @brokers.split(",")
79
94
  log.info "brokers has been set directly: #{@seed_brokers}"
80
95
  end
81
- if @compression_codec == 'snappy'
82
- require 'snappy'
83
- end
84
- case @output_data_type
85
- when 'json'
86
- require 'yajl'
87
- when 'ltsv'
88
- require 'ltsv'
89
- when 'msgpack'
90
- require 'msgpack'
96
+
97
+ if conf['ack_timeout_ms']
98
+ log.warn "'ack_timeout_ms' parameter is deprecated. Use second unit 'ack_timeout' instead"
99
+ @ack_timeout = conf['ack_timeout_ms'].to_i / 1000
91
100
  end
92
101
 
93
102
  @f_separator = case @field_separator
@@ -97,56 +106,57 @@ DESC
97
106
  else "\t"
98
107
  end
99
108
 
100
- @custom_attributes = if @output_data_type == 'json'
101
- nil
102
- elsif @output_data_type == 'ltsv'
103
- nil
104
- elsif @output_data_type == 'msgpack'
105
- nil
106
- elsif @output_data_type =~ /^attr:(.*)$/
107
- $1.split(',').map(&:strip).reject(&:empty?)
108
- else
109
- @formatter = Fluent::Plugin.new_formatter(@output_data_type)
110
- @formatter.configure(conf)
111
- nil
112
- end
109
+ @formatter_proc = setup_formatter(conf)
113
110
 
111
+ @producer_opts = {max_retries: @max_send_retries, required_acks: @required_acks}
112
+ @producer_opts[:ack_timeout] = @ack_timeout if @ack_timeout
113
+ @producer_opts[:compression_codec] = @compression_codec.to_sym if @compression_codec
114
114
  end
115
115
 
116
116
  def start
117
117
  super
118
- refresh_producer()
118
+ refresh_client
119
119
  end
120
120
 
121
121
  def shutdown
122
122
  super
123
+ @kafka = nil
123
124
  end
124
125
 
125
- def parse_record(record)
126
- if @custom_attributes.nil?
127
- case @output_data_type
128
- when 'json'
129
- Yajl::Encoder.encode(record)
130
- when 'ltsv'
131
- LTSV.dump(record)
132
- when 'msgpack'
133
- record.to_msgpack
134
- else
135
- record.to_s
136
- end
137
- else
126
+ def setup_formatter(conf)
127
+ if @output_data_type == 'json'
128
+ require 'yajl'
129
+ Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
130
+ elsif @output_data_type == 'ltsv'
131
+ require 'ltsv'
132
+ Proc.new { |tag, time, record| LTSV.dump(record) }
133
+ elsif @output_data_type == 'msgpack'
134
+ require 'msgpack'
135
+ Proc.new { |tag, time, record| record.to_msgpack }
136
+ elsif @output_data_type =~ /^attr:(.*)$/
137
+ @custom_attributes = $1.split(',').map(&:strip).reject(&:empty?)
138
138
  @custom_attributes.unshift('time') if @output_include_time
139
139
  @custom_attributes.unshift('tag') if @output_include_tag
140
- @custom_attributes.map { |attr|
141
- record[attr].nil? ? '' : record[attr].to_s
142
- }.join(@f_separator)
140
+ Proc.new { |tag, time, record|
141
+ @custom_attributes.map { |attr|
142
+ record[attr].nil? ? '' : record[attr].to_s
143
+ }.join(@f_separator)
144
+ }
145
+ else
146
+ @formatter = Fluent::Plugin.new_formatter(@output_data_type)
147
+ @formatter.configure(conf)
148
+ @formatter.method(:format)
143
149
  end
144
150
  end
145
151
 
146
152
  def emit(tag, es, chain)
147
153
  begin
148
154
  chain.next
149
- es.each do |time,record|
155
+
156
+ # out_kafka is mainly for testing so don't need the performance unlike out_kafka_buffered.
157
+ producer = @kafka.producer(@producer_opts)
158
+
159
+ es.each do |time, record|
150
160
  if @output_include_time
151
161
  if @time_format
152
162
  record['time'] = Time.at(time).strftime(@time_format)
@@ -155,17 +165,20 @@ DESC
155
165
  end
156
166
  end
157
167
  record['tag'] = tag if @output_include_tag
158
- topic = record['topic'] || self.default_topic || tag
168
+ topic = record['topic'] || @default_topic || tag
159
169
  partition_key = record['partition_key'] || @default_partition_key
160
- value = @formatter.nil? ? parse_record(record) : @formatter.format(tag, time, record)
161
- log.trace("message send to #{topic} with key: #{partition_key} and value: #{value}.")
162
- message = Poseidon::MessageToSend.new(topic, value, partition_key)
163
- @producer.send_messages([message])
170
+ value = @formatter_proc.call(tag, time, record)
171
+
172
+ log.on_trace { log.trace("message send to #{topic} with key: #{partition_key} and value: #{value}.") }
173
+ producer.produce(value, topic: topic, partition_key: partition_key)
164
174
  end
175
+
176
+ producer.deliver_messages
177
+ producer.shutdown
165
178
  rescue Exception => e
166
- log.warn("Send exception occurred: #{e}")
167
- @producer.close if @producer
168
- refresh_producer()
179
+ log.warn "Send exception occurred: #{e}"
180
+ producer.shutdown if producer
181
+ refresh_client
169
182
  raise e
170
183
  end
171
184
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hidemasa Togashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-21 00:00:00.000000000 Z
11
+ date: 2016-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -30,20 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2'
33
- - !ruby/object:Gem::Dependency
34
- name: poseidon_cluster
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
33
  - !ruby/object:Gem::Dependency
48
34
  name: ltsv
49
35
  requirement: !ruby/object:Gem::Requirement
@@ -78,14 +64,14 @@ dependencies:
78
64
  requirements:
79
65
  - - "~>"
80
66
  - !ruby/object:Gem::Version
81
- version: 0.3.9
67
+ version: 0.3.11
82
68
  type: :runtime
83
69
  prerelease: false
84
70
  version_requirements: !ruby/object:Gem::Requirement
85
71
  requirements:
86
72
  - - "~>"
87
73
  - !ruby/object:Gem::Version
88
- version: 0.3.9
74
+ version: 0.3.11
89
75
  - !ruby/object:Gem::Dependency
90
76
  name: rake
91
77
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +116,7 @@ files:
130
116
  - fluent-plugin-kafka.gemspec
131
117
  - lib/fluent/plugin/in_kafka.rb
132
118
  - lib/fluent/plugin/in_kafka_group.rb
119
+ - lib/fluent/plugin/kafka_plugin_util.rb
133
120
  - lib/fluent/plugin/kafka_producer_ext.rb
134
121
  - lib/fluent/plugin/out_kafka.rb
135
122
  - lib/fluent/plugin/out_kafka_buffered.rb
@@ -149,9 +136,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
136
  version: '0'
150
137
  required_rubygems_version: !ruby/object:Gem::Requirement
151
138
  requirements:
152
- - - ">="
139
+ - - ">"
153
140
  - !ruby/object:Gem::Version
154
- version: '0'
141
+ version: 1.3.1
155
142
  requirements: []
156
143
  rubyforge_project:
157
144
  rubygems_version: 2.5.1