fluent-plugin-kafka 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e7ea6df9ab262c3d1a76fd1bf8084dcacfed5e13
4
+ data.tar.gz: a4e75c5d1c30425d35a7cc57a802256dd28901fb
5
+ SHA512:
6
+ metadata.gz: 12b5a1579ffd62f45279983cbbbc3c13c4659a0c736b29b6f8e40c765b839ed3a0e6424be27e12917b674f6f62bff076c5879de3cfe133dc9b8b269f8aebc489
7
+ data.tar.gz: 963e4236af8e79c47eecfdfd0b58f73c6a9e68f3161b1c984d48691ae025c64d3d5e57bb18f8a20ce91430d11ad4007ed6893c7e20aa31efded4ad7d9dd24192
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-kafka-poseidon.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 htgc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Fluent::Plugin::Kafka
2
+
3
+ TODO: Write a gem description
4
+ TODO: Also, I need to write tests
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'fluent-plugin-kafka'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install fluent-plugin-kafka
19
+
20
+ ## Usage
21
+
22
+ ### Input plugin
23
+
24
+ <source>
25
+ type kafka
26
+ host <broker host>
27
+ port <broker port: default=9092>
28
+ topics <listening topics(separate with comma',')>
29
+ format <input text type (text|json)>
30
+ add_prefix <tag prefix (Optional)>
31
+ add_suffix <tag suffix (Optional)>
32
+ </source>
33
+
34
+ ### Output plugin (non-buffered)
35
+
36
+ <match *.**>
37
+ type kafka
38
+ brokers <broker1_host>:<broker1_ip>,<broker2_host>:<broker2_ip>,..
39
+ default_topic <output topic>
40
+ output_data_type (json|ltsv|attr:<record name>)
41
+ </match>
42
+
43
+ ### Buffered output plugin
44
+
45
+ <match *.**>
46
+ type kafka_buffered
47
+ brokers <broker1_host>:<broker1_ip>,<broker2_host>:<broker2_ip>,..
48
+ default_topic <output topic>
49
+ flush_interval <flush interval (sec) :default => 60>
50
+ buffer_type (file|memory)
51
+ output_data_type (json|ltsv|attr:<record name>)
52
+ </match>
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Hidemasa Togashi"]
5
+ gem.email = ["togachiro@gmail.com"]
6
+ gem.description = %q{Fluentd plugin for Apache Kafka > 0.8}
7
+ gem.summary = %q{Fluentd plugin for Apache Kafka > 0.8}
8
+ gem.homepage = "https://github.com/htgc/fluent-plugin-kafka"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "fluent-plugin-kafka"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = '0.0.5'
16
+ gem.add_dependency 'fluentd'
17
+ gem.add_dependency 'poseidon'
18
+ gem.add_dependency 'ltsv'
19
+ gem.add_dependency 'json'
20
+ end
@@ -0,0 +1,117 @@
1
+ module Fluent
2
+
3
+ class KafkaInput < Input
4
+ Plugin.register_input('kafka', self)
5
+
6
+ config_param :format, :string, :default => 'json' # (json|text)
7
+ config_param :host, :string, :default => 'localhost'
8
+ config_param :port, :integer, :default => 2181
9
+ config_param :interval, :integer, :default => 1 # seconds
10
+ config_param :topics, :string
11
+ config_param :client_id, :string, :default => 'kafka'
12
+ config_param :partition, :integer, :default => 0
13
+ config_param :offset, :integer, :default => -1
14
+ config_param :add_prefix, :string, :default => nil
15
+ config_param :add_suffix, :string, :default => nil
16
+
17
+ def initialize
18
+ super
19
+ require 'poseidon'
20
+ end
21
+
22
+ def configure(conf)
23
+ super
24
+ @topic_list = @topics.split(',').map {|topic| topic.strip }
25
+ if @topic_list.empty?
26
+ raise ConfigError, "kafka: 'topics' is a require parameter"
27
+ end
28
+
29
+ case @format
30
+ when 'json'
31
+ require 'json'
32
+ end
33
+ end
34
+
35
+ def start
36
+ @loop = Coolio::Loop.new
37
+ @topic_watchers = @topic_list.map {|topic|
38
+ TopicWatcher.new(topic, @host, @port, @client_id, @partition, @offset, interval, @format, @add_prefix, @add_suffix)
39
+ }
40
+ @topic_watchers.each {|tw|
41
+ tw.attach(@loop)
42
+ }
43
+ @thread = Thread.new(&method(:run))
44
+ end
45
+
46
+ def shutdown
47
+ @loop.stop
48
+ end
49
+
50
+ def run
51
+ @loop.run
52
+ rescue
53
+ $log.error "unexpected error", :error=>$!.to_s
54
+ $log.error_backtrace
55
+ end
56
+
57
+ class TopicWatcher < Coolio::TimerWatcher
58
+ def initialize(topic, host, port, client_id, partition, offset, interval, format, add_prefix, add_suffix)
59
+ @topic = topic
60
+ @callback = method(:consume)
61
+ @format = format
62
+ @add_prefix = add_prefix
63
+ @add_suffix = add_suffix
64
+ @consumer = Poseidon::PartitionConsumer.new(
65
+ client_id, # client_id
66
+ host, # host
67
+ port, # port
68
+ topic, # topic
69
+ partition, # partition
70
+ offset # offset
71
+ )
72
+
73
+ super(interval, true)
74
+ end
75
+
76
+ def on_timer
77
+ @callback.call
78
+ rescue
79
+ # TODO log?
80
+ $log.error $!.to_s
81
+ $log.error_backtrace
82
+ end
83
+
84
+ def consume
85
+ es = MultiEventStream.new
86
+ tag = @topic
87
+ tag = @add_prefix + "." + tag if @add_prefix
88
+ tag = tag + "." + @add_suffix if @add_suffix
89
+ @consumer.fetch.each { |msg|
90
+ begin
91
+ msg_record = parse_line(msg.value)
92
+ es.add(Time.now.to_i, msg_record)
93
+ rescue
94
+ $log.warn msg_record.to_s, :error=>$!.to_s
95
+ $log.debug_backtrace
96
+ end
97
+ }
98
+
99
+ unless es.empty?
100
+ Engine.emit_stream(tag, es)
101
+ end
102
+ end
103
+
104
+ def parse_line(record)
105
+ parsed_record = {}
106
+ case @format
107
+ when 'json'
108
+ parsed_record = JSON.parse(record)
109
+ when 'text'
110
+ parsed_record = record
111
+ end
112
+ parsed_record
113
+ end
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,83 @@
1
+ class Fluent::KafkaOutput < Fluent::Output
2
+ Fluent::Plugin.register_output('kafka', self)
3
+
4
+ def initialize
5
+ super
6
+ require 'poseidon'
7
+ end
8
+
9
+ config_param :brokers, :string, :default => 'localhost:9092'
10
+ config_param :default_topic, :string, :default => nil
11
+ config_param :default_partition, :integer, :default => 0
12
+ config_param :client_id, :string, :default => 'kafka'
13
+ config_param :output_data_type, :string, :default => 'json'
14
+ attr_accessor :output_data_type
15
+ attr_accessor :field_separator
16
+
17
+ def configure(conf)
18
+ super
19
+ @seed_brokers = @brokers.match(",").nil? ? [@brokers] : @brokers.split(",")
20
+ @producers = {} # keyed by topic:partition
21
+ case @output_data_type
22
+ when 'json'
23
+ require 'json'
24
+ when 'ltsv'
25
+ require 'ltsv'
26
+ end
27
+
28
+ @f_separator = case @field_separator
29
+ when /SPACE/i then ' '
30
+ when /COMMA/i then ','
31
+ when /SOH/i then "\x01"
32
+ else "\t"
33
+ end
34
+
35
+ @custom_attributes = if @output_data_type == 'json'
36
+ nil
37
+ elsif @output_data_type == 'ltsv'
38
+ nil
39
+ elsif @output_data_type =~ /^attr:(.*)$/
40
+ $1.split(',').map(&:strip).reject(&:empty?)
41
+ else
42
+ nil
43
+ end
44
+
45
+ end
46
+
47
+ def start
48
+ super
49
+ end
50
+
51
+ def shutdown
52
+ super
53
+ end
54
+
55
+ def parse_record(record)
56
+ if @custom_attributes.nil?
57
+ case @output_data_type
58
+ when 'json'
59
+ JSON.dump(record)
60
+ when 'ltsv'
61
+ LTSV.dump(record)
62
+ else
63
+ record.to_s
64
+ end
65
+ else
66
+ @custom_attributes.map { |attr|
67
+ record[attr].nil? ? '' : record[attr].to_s
68
+ }.join(@f_separator)
69
+ end
70
+ end
71
+
72
+ def emit(tag, es, chain)
73
+ chain.next
74
+ es.each do |time,record|
75
+ topic = record['topic'] || self.default_topic || tag
76
+ partition = record['partition'] || self.default_partition
77
+ message = Poseidon::MessageToSend.new(topic, parse_record(record))
78
+ @producers[topic] ||= Poseidon::Producer.new(@seed_brokers, self.client_id)
79
+ @producers[topic].send_messages([message])
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,97 @@
1
+ class Fluent::KafkaOutputBuffered < Fluent::BufferedOutput
2
+ Fluent::Plugin.register_output('kafka_buffered', self)
3
+
4
+ def initialize
5
+ super
6
+ require 'poseidon'
7
+ end
8
+
9
+ config_param :brokers, :string, :default => 'localhost:9092'
10
+ config_param :default_topic, :string, :default => nil
11
+ config_param :default_partition, :integer, :default => 0
12
+ config_param :client_id, :string, :default => 'kafka'
13
+ config_param :output_data_type, :string, :default => 'json'
14
+ attr_accessor :output_data_type
15
+ attr_accessor :field_separator
16
+
17
+ def configure(conf)
18
+ super
19
+ @seed_brokers = @brokers.match(",").nil? ? [@brokers] : @brokers.split(",")
20
+ @producers = {} # keyed by topic:partition
21
+ case @output_data_type
22
+ when 'json'
23
+ require 'json'
24
+ when 'ltsv'
25
+ require 'ltsv'
26
+ end
27
+
28
+ @f_separator = case @field_separator
29
+ when /SPACE/i then ' '
30
+ when /COMMA/i then ','
31
+ when /SOH/i then "\x01"
32
+ else "\t"
33
+ end
34
+
35
+ @custom_attributes = if @output_data_type == 'json'
36
+ nil
37
+ elsif @output_data_type == 'ltsv'
38
+ nil
39
+ elsif @output_data_type =~ /^attr:(.*)$/
40
+ $1.split(',').map(&:strip).reject(&:empty?)
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ def start
47
+ super
48
+ end
49
+
50
+ def shutdown
51
+ super
52
+ end
53
+
54
+ def format(tag, time, record)
55
+ [tag, time, record].to_msgpack
56
+ end
57
+
58
+ def parse_record(record)
59
+ if @custom_attributes.nil?
60
+ case @output_data_type
61
+ when 'json'
62
+ JSON.dump(record)
63
+ when 'ltsv'
64
+ LTSV.dump(record)
65
+ else
66
+ record.to_s
67
+ end
68
+ else
69
+ @custom_attributes.map { |attr|
70
+ record[attr].nil? ? '' : record[attr].to_s
71
+ }.join(@f_separator)
72
+ end
73
+ end
74
+
75
+ def write(chunk)
76
+ records_by_topic = {}
77
+ chunk.msgpack_each { |tag, time, record|
78
+ topic = record['topic'] || self.default_topic || tag
79
+ partition = record['partition'] || self.default_partition
80
+ message = Poseidon::MessageToSend.new(topic, parse_record(record))
81
+ records_by_topic[topic] ||= []
82
+ records_by_topic[topic][partition] ||= []
83
+ records_by_topic[topic][partition] << message
84
+ }
85
+ publish(records_by_topic)
86
+ end
87
+
88
+ def publish(records_by_topic)
89
+ records_by_topic.each { |topic, partitions|
90
+ partitions.each_with_index { |messages, partition|
91
+ next if not messages
92
+ @producers[topic] ||= Poseidon::Producer.new(@seed_brokers, self.client_id)
93
+ @producers[topic].send_messages(messages)
94
+ }
95
+ }
96
+ end
97
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ end
20
+ }
21
+ $log = nulllogger
22
+ end
23
+
24
+ require 'fluent/plugin/out_kafka'
25
+
26
+ class Test::Unit::TestCase
27
+ end
@@ -0,0 +1,33 @@
1
+ require 'helper'
2
+
3
+ class KafkaOutputTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ default_topic kitagawakeiko
10
+ brokers localhost:9092
11
+ ]
12
+
13
+ def create_driver(conf = CONFIG, tag='test')
14
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::KafkaOutput, tag).configure(conf)
15
+ end
16
+
17
+ def test_configure
18
+ d = create_driver
19
+ assert_equal 'kitagawakeiko', d.instance.default_topic
20
+ assert_equal 'localhost:9092', d.instance.brokers
21
+ end
22
+
23
+ def test_format
24
+ d = create_driver
25
+ end
26
+
27
+ def test_write
28
+ d = create_driver
29
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
30
+ d.emit({"a"=>1}, time)
31
+ d.emit({"a"=>2}, time)
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-kafka
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Hidemasa Togashi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: poseidon
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ltsv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Fluentd plugin for Apache Kafka > 0.8
70
+ email:
71
+ - togachiro@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - Gemfile
77
+ - LICENSE
78
+ - README.md
79
+ - Rakefile
80
+ - fluent-plugin-kafka.gemspec
81
+ - lib/fluent/plugin/in_kafka.rb
82
+ - lib/fluent/plugin/out_kafka.rb
83
+ - lib/fluent/plugin/out_kafka_buffered.rb
84
+ - test/helper.rb
85
+ - test/plugin/test_out_kafka.rb
86
+ homepage: https://github.com/htgc/fluent-plugin-kafka
87
+ licenses: []
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.0.3
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Fluentd plugin for Apache Kafka > 0.8
109
+ test_files:
110
+ - test/helper.rb
111
+ - test/plugin/test_out_kafka.rb