fluent-plugin-kafka 0.2.2 → 0.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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