fluent-plugin-flume 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Muga Nishizawa <muga.nishizawa _at_ gmail.com>
@@ -0,0 +1 @@
1
+ Release 0.1.0 - 2012/05/02
@@ -0,0 +1,105 @@
1
+ = Flume input/output plugin for Fluent
2
+
3
+ == Overview
4
+
5
+ This is a plugin for fluentd[https://github.com/fluentd] event collector. This plugin adds the Flume[https://github.com/apache/flume] compatible interface to fluentd.
6
+
7
+ == What's Flume?
8
+
9
+ Flume[https://github.com/apache/flume] is a distributed, reliable, and available service for efficiently collecting, aggregating, and moving large amounts of log data.
10
+
11
+ It uses Thrift[http://thrift.apache.org/], a cross-language RPC framework, to communicate between clients and servers.
12
+
13
+ == What's Flume plugin for fluent?
14
+
15
+ The Flume plugin for fluentd, which enables fluentd to talk the Flume protocol. Flume protocol is defined as follows, in Thrift-IDL format:
16
+
17
+ typedef i64 Timestamp
18
+
19
+ enum Priority {
20
+ FATAL = 0,
21
+ ERROR = 1,
22
+ WARN = 2,
23
+ INFO = 3,
24
+ DEBUG = 4,
25
+ TRACE = 5
26
+ }
27
+
28
+ enum EventStatus {
29
+ ACK = 0,
30
+ COMMITED = 1,
31
+ ERR = 2
32
+ }
33
+
34
+ struct ThriftFlumeEvent {
35
+ 1: Timestamp timestamp,
36
+ 2: Priority priority,
37
+ 3: binary body,
38
+ 4: i64 nanos,
39
+ 5: string host,
40
+ 6: map<string,binary> fields
41
+ }
42
+
43
+ # Instead of using thrift's serialization, we just assume the contents are serialized already.
44
+ struct RawEvent {
45
+ 1: binary raw
46
+ }
47
+
48
+ service ThriftFlumeEventServer {
49
+ oneway void append( 1:ThriftFlumeEvent evt ),
50
+ oneway void rawAppend( 1:RawEvent evt),
51
+ EventStatus ackedAppend( 1: ThriftFlumeEvent evt ),
52
+
53
+ void close(),
54
+ }
55
+
56
+ A value that is stored in the ThriftFlumeEvent.fields map is used as fluentd 'tag'. A key of the value enables be specified by users as configuration parameter.
57
+
58
+ == How to use?
59
+
60
+ fluent-plugin-flume contains both input and output.
61
+
62
+ == Flume Input
63
+
64
+ Please add the following configurations to fluent.conf.
65
+
66
+ # Flume input
67
+ <source>
68
+ type flume
69
+ port 56789
70
+ </source>
71
+
72
+ These options are supported.
73
+
74
+ * port: port number (default: 56789)
75
+ * bind: bind address (default: 0.0.0.0)
76
+ * server_type: server architecture either in 'simple', 'threaded', 'thread_pool', (default: simple)
77
+ * is_framed: use framed protocol or not (default: false)
78
+ * tag_field: key name of fluentd 'tag' that is stored in ThriftFlumeEvent.fields (default: nil)
79
+ * default_tag: default fluentd 'tag' (default: 'category')
80
+ * add_prefix: prefix string, added to the tag (default: nil)
81
+
82
+ == Flume Output
83
+
84
+ Please add the following configurations to fluent.conf. This allows fluentd to output its logs into another Flume server. Note that fluentd conveys semi-structured data while Flume conveys unstructured data. Thus the plugin translates semi-structured data into JSON data and conveys it to Flume.
85
+
86
+ # Flume output
87
+ <match *>
88
+ type flume
89
+ host flume-host.local
90
+ port 56789
91
+ </match>
92
+
93
+ These options are supported.
94
+
95
+ * host: host name or address (default: localhost)
96
+ * port: port number (default: 35863)
97
+ * timeout: thrift protocol timeout (default: 30)
98
+ * remove_prefix: prefix string, removed from the tag (default: nil)
99
+
100
+ == Contributors
101
+
102
+ == Copyright
103
+
104
+ Copyright:: Copyright (c) 2012 Treasure Data, Inc.
105
+ License:: Apache License, Version 2.0
@@ -0,0 +1,57 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "fluent-plugin-flume"
9
+ gemspec.summary = "Flume Input/Output plugin for Fluentd event collector"
10
+ gemspec.author = "Muga Nishizawa"
11
+ gemspec.email = "muga.nishizawa@gmail.com"
12
+ gemspec.homepage = "https://github.com/muga/fluent-plugin-flume"
13
+ gemspec.has_rdoc = false
14
+ gemspec.require_paths = ["lib"]
15
+ gemspec.add_dependency "fluentd", "~> 0.10.16"
16
+ gemspec.add_dependency "thrift", "~> 0.6.0"
17
+ gemspec.test_files = Dir["test/**/*.rb"]
18
+ gemspec.files = Dir["bin/**/*", "lib/**/*", "test/**/*.rb"] +
19
+ %w[example.conf VERSION AUTHORS Rakefile fluent-plugin-flume.gemspec]
20
+ gemspec.executables = ['fluent-flume-remote']
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+ task "thrift_gen" do
28
+ system "mkdir -p tmp"
29
+ system "thrift --gen rb -o tmp lib/fluent/plugin/thrift/flume.thrift"
30
+ system "mv tmp/gen-rb/* lib/fluent/plugin/thrift/"
31
+ system "rm -fR tmp"
32
+ end
33
+
34
+ Rake::TestTask.new(:test) do |t|
35
+ t.test_files = Dir['test/plugin/*.rb']
36
+ t.ruby_opts = ['-rubygems'] if defined? Gem
37
+ t.ruby_opts << '-I.'
38
+ end
39
+
40
+ #VERSION_FILE = "lib/fluent/version.rb"
41
+ #
42
+ #file VERSION_FILE => ["VERSION"] do |t|
43
+ # version = File.read("VERSION").strip
44
+ # File.open(VERSION_FILE, "w") {|f|
45
+ # f.write <<EOF
46
+ #module Fluent
47
+ #
48
+ #VERSION = '#{version}'
49
+ #
50
+ #end
51
+ #EOF
52
+ # }
53
+ #end
54
+ #
55
+ #task :default => [VERSION_FILE, :build]
56
+
57
+ task :default => [:build]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ require 'thrift'
3
+ require 'json'
4
+ $:.unshift File.join(File.dirname(__FILE__), '../lib/fluent/plugin/thrift')
5
+ require 'flume_types'
6
+ require 'flume_constants'
7
+ require 'thrift_flume_event_server'
8
+
9
+ host = 'localhost'
10
+ port = 3586
11
+
12
+ socket = Thrift::Socket.new host, port.to_i
13
+ transport = Thrift::BufferedTransport.new socket
14
+ protocol = Thrift::BinaryProtocol.new transport
15
+ client = ThriftFlumeEventServer::Client.new protocol
16
+ transport.open
17
+
18
+ # 2011/09/02 Kazuki Ohta <kazuki.ohta@gmail.com>
19
+ # explicitly specify TCP_NODELAY for low-latency communication.
20
+ raw_sock = socket.to_io
21
+ raw_sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
22
+
23
+ entry = ThriftFlumeEvent.new(:body=>{'a'=>'b'}.to_json,
24
+ :priority=>Priority::INFO,
25
+ :timestamp=>(Time.now.to_i * 1000))
26
+ client.append entry
27
+
28
+ transport.close
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "fluent-plugin-flume"
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Muga Nishizawa"]
12
+ s.date = "2012-04-07"
13
+ s.email = "muga.nishizawa@gmail.com"
14
+ s.executables = ["fluent-flume-remote"]
15
+ s.extra_rdoc_files = [
16
+ "ChangeLog",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "AUTHORS",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bin/fluent-flume-remote",
24
+ "example.conf",
25
+ "fluent-plugin-flume.gemspec",
26
+ "lib/fluent/plugin/in_flume.rb",
27
+ "lib/fluent/plugin/out_flume.rb",
28
+ "lib/fluent/plugin/thrift/flume.thrift",
29
+ "lib/fluent/plugin/thrift/flume.thrift.orig",
30
+ "lib/fluent/plugin/thrift/flume_constants.rb",
31
+ "lib/fluent/plugin/thrift/flume_types.rb",
32
+ "lib/fluent/plugin/thrift/thrift_flume_event_server.rb",
33
+ "test/plugin/out_flume.rb"
34
+ ]
35
+ s.homepage = "https://github.com/muga/fluent-plugin-flume"
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = "1.8.15"
38
+ s.summary = "Flume Input/Output plugin for Fluentd event collector"
39
+ s.test_files = ["test/plugin/out_flume.rb"]
40
+
41
+ if s.respond_to? :specification_version then
42
+ s.specification_version = 3
43
+
44
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
+ s.add_runtime_dependency(%q<fluentd>, ["~> 0.10.16"])
46
+ s.add_runtime_dependency(%q<thrift>, ["~> 0.6.0"])
47
+ else
48
+ s.add_dependency(%q<fluentd>, ["~> 0.10.16"])
49
+ s.add_dependency(%q<thrift>, ["~> 0.6.0"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<fluentd>, ["~> 0.10.16"])
53
+ s.add_dependency(%q<thrift>, ["~> 0.6.0"])
54
+ end
55
+ end
56
+
@@ -0,0 +1,164 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2012 Muga Nishizawa
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+ class FlumeInput < Input
21
+ Plugin.register_input('flume', self)
22
+
23
+ config_param :port, :integer, :default => 56789
24
+ config_param :bind, :string, :default => '0.0.0.0'
25
+ config_param :server_type, :string, :default => 'simple'
26
+ config_param :is_framed, :bool, :default => false
27
+ config_param :body_size_limit, :size, :default => 32*1024*1024 # TODO default
28
+ config_param :tag_field, :string, :default => nil
29
+ config_param :default_tag, :string, :default => 'category'
30
+ config_param :add_prefix, :string, :default => nil
31
+ config_param :message_format, :string, :default => 'text'
32
+
33
+ def initialize
34
+ require 'thrift'
35
+ $:.unshift File.join(File.dirname(__FILE__), 'thrift')
36
+ require 'flume_types'
37
+ require 'flume_constants'
38
+ require 'thrift_flume_event_server'
39
+
40
+ super
41
+ end
42
+
43
+ def configure(conf)
44
+ super
45
+ end
46
+
47
+ def start
48
+ $log.debug "listening flume on #{@bind}:#{@port}"
49
+
50
+ handler = FluentFlumeHandler.new
51
+ handler.tag_field = @tag_field
52
+ handler.default_tag = @default_tag
53
+ handler.add_prefix = @add_prefix
54
+ handler.message_format = @message_format
55
+ processor = ThriftFlumeEventServer::Processor.new handler
56
+
57
+ @transport = Thrift::ServerSocket.new @bind, @port
58
+ unless @is_framed
59
+ transport_factory = Thrift::BufferedTransportFactory.new
60
+ else
61
+ raise ConfigError, "in_flume: unsupported is_framed '#{@is_framed}'"
62
+ end
63
+
64
+ unless ['text', 'json'].include? @message_format
65
+ raise 'Unknown format: message_format=#{@message_format}'
66
+ end
67
+
68
+ # 2011/09/29 Kazuki Ohta <kazuki.ohta@gmail.com>
69
+ # This section is a workaround to set strict_read and strict_write option.
70
+ # Ruby-Thrift 0.7 set them both 'true' in default, but Flume protocol set
71
+ # them both 'false'.
72
+ protocol_factory = Thrift::BinaryProtocolFactory.new
73
+ protocol_factory.instance_eval {|obj|
74
+ def get_protocol(trans) # override
75
+ return Thrift::BinaryProtocol.new(trans,
76
+ strict_read=false,
77
+ strict_write=false)
78
+ end
79
+ }
80
+
81
+ case @server_type
82
+ when 'simple'
83
+ @server = Thrift::SimpleServer.new processor, @transport, transport_factory, protocol_factory
84
+ when 'threaded'
85
+ @server = Thrift::ThreadedServer.new processor, @transport, transport_factory, protocol_factory
86
+ when 'thread_pool'
87
+ @server = Thrift::ThreadPoolServer.new processor, @transport, transport_factory, protocol_factory
88
+ else
89
+ raise ConfigError, "in_flume: unsupported server_type '#{@server_type}'"
90
+ end
91
+ @thread = Thread.new(&method(:run))
92
+ end
93
+
94
+ def shutdown
95
+ @transport.close unless @transport.closed? # TODO
96
+ #@thread.join # TODO
97
+ end
98
+
99
+ def run
100
+ $log.debug "starting server: #{@server}"
101
+ @server.serve
102
+ rescue
103
+ $log.error "unexpected error", :error=>$!.to_s
104
+ $log.error_backtrace
105
+ end
106
+
107
+ class FluentFlumeHandler
108
+ attr_accessor :tag_field
109
+ attr_accessor :default_tag
110
+ attr_accessor :add_prefix
111
+ attr_accessor :message_format
112
+
113
+ def append(evt)
114
+ begin
115
+ record = create_record(evt)
116
+ if @tag_field
117
+ tag = evt.fieldss[@tag_field] || @default_tag
118
+ unless tag
119
+ return # ignore
120
+ end
121
+ else
122
+ tag = @default_tag
123
+ end
124
+ timestamp = evt.timestamp.to_i
125
+ if @add_prefix
126
+ Engine.emit(@add_prefix + '.' + tag, timestamp, record)
127
+ else
128
+ Engine.emit(tag, timestamp, record)
129
+ end
130
+ rescue => e
131
+ $log.error "unexpected error", :error=>$!.to_s
132
+ $log.error_backtrace
133
+ end
134
+ end
135
+
136
+ def rawAppend(evt)
137
+ $log.error "rawAppend is not implemented yet: #{evt}"
138
+ end
139
+
140
+ def ackedAppend(evt)
141
+ $log.error "ackedAppend is not implemented yet: #{evt}"
142
+ EventStatus::OK
143
+ end
144
+
145
+ def close()
146
+ end
147
+
148
+ private
149
+ def create_record(evt)
150
+ case @message_format
151
+ when 'text'
152
+ return {'message'=>evt.body.force_encoding('UTF-8')}
153
+ when 'json'
154
+ js = JSON.parse(evt.body.force_encoding('UTF-8'))
155
+ raise 'body must be a Hash, if json_body=true' unless js.is_a?(Hash)
156
+ return js
157
+ else
158
+ raise 'Invalid format: #{@message_format}'
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ end
@@ -0,0 +1,96 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2012 Muga Nishizawa
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+ class FlumeOutput < BufferedOutput
21
+ Fluent::Plugin.register_output('flume', self)
22
+
23
+ config_param :host, :string, :default => 'localhost'
24
+ config_param :port, :integer, :default => 35863
25
+ config_param :timeout, :integer, :default => 30
26
+ config_param :remove_prefix, :string, :default => nil
27
+ config_param :default_category, :string, :default => 'unknown'
28
+
29
+ def initialize
30
+ require 'thrift'
31
+ $:.unshift File.join(File.dirname(__FILE__), 'thrift')
32
+ require 'flume_types'
33
+ require 'flume_constants'
34
+ require 'thrift_flume_event_server'
35
+
36
+ super
37
+ end
38
+
39
+ def configure(conf)
40
+ super
41
+ end
42
+
43
+ def start
44
+ super
45
+
46
+ if @remove_prefix
47
+ @removed_prefix_string = @remove_prefix + '.'
48
+ @removed_length = @removed_prefix_string.length
49
+ end
50
+ end
51
+
52
+ def shutdown
53
+ super
54
+ end
55
+
56
+ def format(tag, time, record)
57
+ if @remove_prefix and
58
+ ( (tag[0, @removed_length] == @removed_prefix_string and tag.length > @removed_length) or
59
+ tag == @remove_prefix)
60
+ [(tag[@removed_length..-1] || @default_category), time, record].to_msgpack
61
+ else
62
+ [tag, time, record].to_msgpack
63
+ end
64
+ end
65
+
66
+ def write(chunk)
67
+ records = []
68
+ chunk.msgpack_each { |arr|
69
+ records << arr
70
+ }
71
+
72
+ transport = Thrift::Socket.new @host, @port, @timeout
73
+ #transport = Thrift::FramedTransport.new socket
74
+ #protocol = Thrift::BinaryProtocol.new transport, false, false
75
+ protocol = Thrift::BinaryProtocol.new transport
76
+ client = ThriftFlumeEventServer::Client.new protocol
77
+
78
+ transport.open
79
+ $log.debug "thrift client opend: #{client}"
80
+ begin
81
+ records.each { |r|
82
+ tag, time, record = r
83
+ entry = ThriftFlumeEvent.new(:body=>record.to_json.to_s.force_encoding('UTF-8'),
84
+ :priority=>Priority::INFO,
85
+ :timestamp=>time,
86
+ :fieldss=>{'category'=>tag})
87
+ client.append entry
88
+ }
89
+ ensure
90
+ $log.debug "thrift client closing: #{client}"
91
+ transport.close
92
+ end
93
+ end
94
+ end
95
+
96
+ end