fluent-plugin-mqtt-io 0.2.3 → 0.3.0

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: c1403811416a6ec5ae4b65f1e2f910425c78464f
4
- data.tar.gz: 36025a84dafc6aacc2295c76cd0a9f8110563514
3
+ metadata.gz: c1ec56bcb964c914765afcd471b69413e623a54d
4
+ data.tar.gz: d7784e5b1e39d416cec4b40a1868ca72fe0f3afc
5
5
  SHA512:
6
- metadata.gz: 64ae726ba5f10ba20d16692626cd9ce1d6424fe1a0aacd735fbacd73f3bf3c927c07838980907e9bb5ef89b3227eb7c03b64b82979de5df3825e168a5a58d5c8
7
- data.tar.gz: 9501641e21422a5fa755550a1e9a32427515e03ef9cfd3721095bbcdb2d6d806e03e36d71b35a1a8d334696a6d7f5e42127145e4bbad970bb89562f4c83ce2a6
6
+ metadata.gz: 62e831aece6900ea1b8f3b8b98fae4cf0391c2bf223803dba5ba8aecd2182c103172523793721ab25bf5279f266e193a3dd4bf86b79167c51b197d44e9ea9475
7
+ data.tar.gz: 3efb8116780288682a990f56c98b4b338a7823e037f9a5088e62071d537b9d2e330841bb06fb68b4adb02ac90ffd93382b1694ef13557cb288326795a3f2c167
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.3.0 (2017-03-20)
2
+
3
+ Features:
4
+ - compatible with fluentd v0.14
5
+ - change license from MIT License to Apache License Version 2.0
6
+
7
+ Deprecations:
8
+
9
+ - deprecated support of fluentd v0.12
data/LICENSE CHANGED
@@ -1,22 +1,191 @@
1
- The MIT License (MIT)
2
1
 
3
- Copyright (c) 2015 Toyokazu Akiyama
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ https://www.apache.org/licenses/
4
5
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
11
7
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
8
+ 1. Definitions.
14
9
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
22
12
 
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ Copyright 2015-2017 Toyokazu Akiyama
180
+
181
+ Licensed under the Apache License, Version 2.0 (the "License");
182
+ you may not use this file except in compliance with the License.
183
+ You may obtain a copy of the License at
184
+
185
+ https://www.apache.org/licenses/LICENSE-2.0
186
+
187
+ Unless required by applicable law or agreed to in writing, software
188
+ distributed under the License is distributed on an "AS IS" BASIS,
189
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
+ See the License for the specific language governing permissions and
191
+ limitations under the License.
data/README.md CHANGED
@@ -240,4 +240,4 @@ MQTT output plugin can be used as the following. If you have tiny computers like
240
240
 
241
241
  ## License
242
242
 
243
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
243
+ The gem is available as open source under the terms of the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
7
+ t.test_files = FileList['test/**/test_*.rb']
8
8
  end
9
9
 
10
10
  task :default => :test
@@ -4,14 +4,14 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "fluent-plugin-mqtt-io"
7
- spec.version = "0.2.3"
7
+ spec.version = "0.3.0"
8
8
  spec.authors = ["Toyokazu Akiyama"]
9
9
  spec.email = ["toyokazu@gmail.com"]
10
10
 
11
11
  spec.summary = %q{fluentd input/output plugin for mqtt broker}
12
12
  spec.description = %q{fluentd input/output plugin for mqtt broker}
13
13
  spec.homepage = "https://github.com/toyokazu/fluent-plugin-mqtt-io"
14
- spec.license = "MIT"
14
+ spec.license = "Apache License Version 2.0"
15
15
 
16
16
  spec.files = `git ls-files`.gsub(/images\/[\w\.]+\n/, "").split($/)
17
17
  spec.bindir = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -19,9 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency 'fluentd', '>= 0.10.0'
23
- spec.add_dependency "mqtt", "~> 0.3"
22
+ spec.required_ruby_version = '>= 2.2.0'
24
23
 
25
- spec.add_development_dependency "bundler", "~> 1.10"
26
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_dependency 'fluentd', '~> 0.14'
25
+ spec.add_dependency "mqtt", "~> 0.4"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.14"
28
+ spec.add_development_dependency "rake", "~> 12.0"
29
+ spec.add_development_dependency "test-unit"
27
30
  end
@@ -1,30 +1,59 @@
1
- module Fluent
1
+ require 'fluent/plugin/input'
2
+ require 'fluent/event'
3
+ require 'fluent/time'
4
+ require 'mqtt'
5
+
6
+ module Fluent::Plugin
2
7
  class MqttInput < Input
3
- Plugin.register_input('mqtt', self)
8
+ Fluent::Plugin.register_input('mqtt', self)
9
+
10
+ helpers :thread, :compat_parameters, :parser
11
+
12
+ MQTT_PORT = 1883
4
13
 
5
- config_param :port, :integer, :default => 1883
14
+ desc 'The address to connect to.'
6
15
  config_param :host, :string, :default => '127.0.0.1'
16
+ desc 'The port to connect to.'
17
+ config_param :port, :integer, :default => MQTT_PORT
18
+ desc 'The topic to subscribe.'
7
19
  config_param :topic, :string, :default => '#'
20
+ desc 'The format to receive.'
8
21
  config_param :format, :string, :default => 'json'
9
- config_param :bulk_trans, :bool, :default => true
10
- config_param :bulk_trans_sep, :string, :default => "\t"
11
- config_param :username, :string, :default => nil
12
- config_param :password, :string, :default => nil
22
+ desc 'Specify keep alive interval.'
13
23
  config_param :keep_alive, :integer, :default => 15
14
- config_param :ssl, :bool, :default => nil
15
- config_param :ca_file, :string, :default => nil
16
- config_param :key_file, :string, :default => nil
17
- config_param :cert_file, :string, :default => nil
18
- config_param :recv_time, :bool, :default => false
19
- config_param :recv_time_key, :string, :default => "recv_time"
24
+ desc 'Specify initial interval for reconnection.'
20
25
  config_param :initial_interval, :integer, :default => 1
26
+ desc 'Specify increasing ratio of reconnection interval.'
21
27
  config_param :retry_inc_ratio, :integer, :default => 2
22
28
 
23
- require 'mqtt'
29
+ # bulk_trans is deprecated
30
+ # multiple entries must be inputted as an Array
31
+ #config_param :bulk_trans, :bool, :default => true
32
+ #config_param :bulk_trans_sep, :string, :default => "\t"
33
+
34
+ config_section :security, required: false, multi: false do
35
+ ### User based authentication
36
+ desc 'The username for authentication'
37
+ config_param :username, :string, :default => nil
38
+ desc 'The password for authentication'
39
+ config_param :password, :string, :default => nil
40
+ desc 'Use TLS or not.'
41
+ config_param :use_tls, :bool, :default => nil
42
+ config_section :tls, required: false, multi: true do
43
+ desc 'Specify TLS ca file.'
44
+ config_param :ca_file, :string, :default => nil
45
+ desc 'Specify TLS key file.'
46
+ config_param :key_file, :string, :default => nil
47
+ desc 'Specify TLS cert file.'
48
+ config_param :cert_file, :string, :default => nil
49
+ end
50
+ end
24
51
 
25
- # Define `router` method of v0.12 to support v0.10 or earlier
26
- unless method_defined?(:router)
27
- define_method("router") { Fluent::Engine }
52
+ config_section :monitor, required: false, multi: false do
53
+ desc 'Record received time into message or not.'
54
+ config_param :recv_time, :bool, :default => false
55
+ desc 'Specify the attribute name of received time.'
56
+ config_param :recv_time_key, :string, :default => "recv_time"
28
57
  end
29
58
 
30
59
  def configure(conf)
@@ -34,10 +63,11 @@ module Fluent
34
63
  end
35
64
 
36
65
  def configure_parser(conf)
37
- @parser = Plugin.new_parser(conf['format'])
38
- @parser.configure(conf)
66
+ compat_parameters_convert(conf, :parser)
67
+ parser_config = conf.elements('parse').first
68
+ @parser = parser_create(conf: parser_config)
39
69
  end
40
-
70
+
41
71
  def init_retry_interval
42
72
  @retry_interval = @initial_interval
43
73
  end
@@ -58,46 +88,51 @@ module Fluent
58
88
  opts = {
59
89
  host: @host,
60
90
  port: @port,
61
- username: @username,
62
- password: @password,
63
91
  keep_alive: @keep_alive
64
92
  }
65
- opts[:ssl] = @ssl if @ssl
66
- opts[:ca_file] = @ca_file if @ca_file
67
- opts[:cert_file] = @cert_file if @cert_file
68
- opts[:key_file] = @key_file if @key_file
93
+ opts[:username] = @security.username if @security.respond_to?(:username)
94
+ opts[:password] = @security.password if @security.respond_to?(:password)
95
+ if @security.respond_to?(:use_tls) && @security.use_tls
96
+ opts[:ssl] = @security.use_tls
97
+ opts[:ca_file] = @security.tls.ca_file
98
+ opts[:cert_file] = @security.tls.cert_file
99
+ opts[:key_file] = @security.tls.key_file
100
+ end
69
101
 
70
102
  # In order to handle Exception raised from reading Thread
71
103
  # in MQTT::Client caused by network disconnection (during read_byte),
72
104
  # @connect_thread generates connection.
73
105
  @client = MQTT::Client.new(opts)
74
- @connect_thread = Thread.new do
75
- while (true)
76
- begin
77
- @client.disconnect if @client.connected?
78
- @client.connect
79
- @client.subscribe(@topic)
80
- @get_thread.kill if !@get_thread.nil? && @get_thread.alive?
81
- @get_thread = Thread.new do
82
- @client.get do |topic, message|
83
- emit(topic, message)
84
- end
106
+ @get_thread = nil
107
+ @connect_thread = Thread.new(&method(:connect_loop))
108
+ end
109
+
110
+ def connect_loop
111
+ while (true)
112
+ begin
113
+ @get_thread.kill if !@get_thread.nil? && @get_thread.alive?
114
+ @client.disconnect if @client.connected?
115
+ @client.connect
116
+ @client.subscribe(@topic)
117
+ @get_thread = Thread.new do
118
+ @client.get do |topic, message|
119
+ emit(topic, message)
85
120
  end
86
- init_retry_interval
87
- sleep
88
- rescue MQTT::ProtocolException => e
89
- sleep_retry_interval(e, "Protocol error occurs.")
90
- next
91
- rescue Timeout::Error => e
92
- sleep_retry_interval(e, "Timeout error occurs.")
93
- next
94
- rescue SystemCallError => e
95
- sleep_retry_interval(e, "System call error occurs.")
96
- next
97
- rescue StandardError=> e
98
- sleep_retry_interval(e, "The other error occurs.")
99
- next
100
121
  end
122
+ init_retry_interval
123
+ sleep
124
+ rescue MQTT::ProtocolException => e
125
+ sleep_retry_interval(e, "Protocol error occurs.")
126
+ next
127
+ rescue Timeout::Error => e
128
+ sleep_retry_interval(e, "Timeout error occurs.")
129
+ next
130
+ rescue SystemCallError => e
131
+ sleep_retry_interval(e, "System call error occurs.")
132
+ next
133
+ rescue StandardError=> e
134
+ sleep_retry_interval(e, "The other error occurs.")
135
+ next
101
136
  end
102
137
  end
103
138
  end
@@ -105,7 +140,7 @@ module Fluent
105
140
  def add_recv_time(record)
106
141
  if @recv_time
107
142
  # recv_time is recorded in ms
108
- record.merge({@recv_time_key => Time.now.instance_eval { self.to_i * 1000 + (usec/1000) }})
143
+ record.merge({@recv_time_key => Fluent::EventTime.now})
109
144
  else
110
145
  record
111
146
  end
@@ -114,8 +149,8 @@ module Fluent
114
149
  def parse(message)
115
150
  @parser.parse(message) do |time, record|
116
151
  if time.nil?
117
- $log.debug "Since time_key field is nil, Fluent::Engine.now is used."
118
- time = Fluent::Engine.now
152
+ $log.debug "Since time_key field is nil, Fluent::EventTime.now is used."
153
+ time = Fluent::EventTime.now
119
154
  end
120
155
  $log.debug "#{topic}, #{time}, #{add_recv_time(record)}"
121
156
  return [time, add_recv_time(record)]
@@ -124,13 +159,16 @@ module Fluent
124
159
 
125
160
  def emit(topic, message)
126
161
  begin
127
- topic.gsub!("/","\.")
128
- if @bulk_trans
129
- message.split(@bulk_trans_sep).each do |m|
130
- router.emit(topic, *parse(m))
162
+ tag = topic.gsub("/","\.")
163
+ time, record = parse(message)
164
+ if record.is_a?(Array)
165
+ mes = Fluent::MultiEventStream.new
166
+ record.each do |single_record|
167
+ mes.add(@parser.parse_time(single_record), single_record)
131
168
  end
169
+ router.emit_stream(tag, mes)
132
170
  else
133
- router.emit(topic, *parse(message))
171
+ router.emit(tag, time, record)
134
172
  end
135
173
  rescue Exception => e
136
174
  $log.error :error => e.to_s
@@ -1,20 +1,225 @@
1
- module Fluent
2
- class MqttOutput < Output
3
- require 'fluent/plugin/mqtt_output_mixin'
4
- include Fluent::MqttOutputMixin
1
+ require 'fluent/plugin/output'
2
+ require 'fluent/event'
3
+ require 'fluent/time'
4
+ require 'mqtt'
5
5
 
6
- # First, register the plugin. NAME is the name of this plugin
7
- # and identifies the plugin in the configuration file.
6
+ module Fluent::Plugin
7
+ class MqttOutput < Output
8
8
  Fluent::Plugin.register_output('mqtt', self)
9
9
 
10
- def emit(tag, es, chain)
11
- es.each {|time,record|
12
- $log.debug "#{tag}, #{format_time(time)}, #{add_send_time(record)}"
13
- @client.publish(rewrite_tag(tag), add_send_time(record).merge(timestamp_hash(time)).to_json)
10
+ helpers :compat_parameters, :formatter, :inject
11
+
12
+ MQTT_PORT = 1883
13
+
14
+ desc 'The address to connect to.'
15
+ config_param :host, :string, :default => '127.0.0.1'
16
+ desc 'The port to connect to.'
17
+ config_param :port, :integer, :default => MQTT_PORT
18
+ desc 'MQTT keep alive interval.'
19
+ config_param :keep_alive, :integer, :default => 15
20
+ desc 'Topic rewrite matching pattern.'
21
+ config_param :topic_rewrite_pattern, :string, :default => nil
22
+ desc 'Topic rewrite replacement string.'
23
+ config_param :topic_rewrite_replacement, :string, :default => nil
24
+ desc 'Initial retry interval.'
25
+ config_param :initial_interval, :integer, :default => 1
26
+ desc 'Increasing ratio of retry interval.'
27
+ config_param :retry_inc_ratio, :integer, :default => 2
28
+
29
+ config_section :security, required: false, multi: false do
30
+ ### User based authentication
31
+ desc 'The username for authentication'
32
+ config_param :username, :string, :default => nil
33
+ desc 'The password for authentication'
34
+ config_param :password, :string, :default => nil
35
+ desc 'Use TLS or not.'
36
+ config_param :use_tls, :bool, :default => nil
37
+ config_section :tls, required: false, multi: true do
38
+ desc 'TLS ca file.'
39
+ config_param :ca_file, :string, :default => nil
40
+ desc 'TLS key file.'
41
+ config_param :key_file, :string, :default => nil
42
+ desc 'TLS cert file.'
43
+ config_param :cert_file, :string, :default => nil
44
+ end
45
+ end
46
+
47
+ config_section :monitor, required: false, multi: false do
48
+ desc 'Recording send time for monitoring.'
49
+ config_param :send_time, :bool, :default => false
50
+ desc 'Recording key name of send time for monitoring.'
51
+ config_param :send_time_key, :string, :default => "send_time"
52
+ end
53
+
54
+ # This method is called before starting.
55
+ # 'conf' is a Hash that includes configuration parameters.
56
+ # If the configuration is invalid, raise Fluent::ConfigError.
57
+ def configure(conf)
58
+ super
59
+ compat_parameters_convert(conf, :formatter, :inject, :buffer, default_chunk_key: "time")
60
+ formatter_config = conf.elements(name: 'format').first
61
+ @formatter = formatter_create(conf: formatter_config)
62
+ @has_buffer_section = conf.elements(name: 'buffer').size > 0
63
+ init_retry_interval
64
+ end
65
+
66
+ def init_retry_interval
67
+ @retry_interval = @initial_interval
68
+ end
69
+
70
+ def increment_retry_interval
71
+ @retry_interval = @retry_interval * @retry_inc_ratio
72
+ end
73
+
74
+ def sleep_retry_interval(e, message)
75
+ $log.error "#{message},#{e.class},#{e.message}"
76
+ $log.error "Retry in #{@retry_interval} sec"
77
+ sleep @retry_interval
78
+ increment_retry_interval
79
+ end
80
+
81
+ def rewrite_tag(tag)
82
+ if @topic_rewrite_pattern.nil?
83
+ tag.gsub("\.", "/")
84
+ else
85
+ tag.gsub("\.", "/").gsub(Regexp.new(@topic_rewrite_pattern), @topic_rewrite_replacement)
86
+ end
87
+ end
88
+
89
+ def prefer_buffered_processing
90
+ @has_buffer_section
91
+ end
92
+
93
+ # This method is called when starting.
94
+ # Open sockets or files here.
95
+ def start
96
+ super
97
+
98
+ $log.debug "start to connect mqtt broker #{@host}:#{@port}"
99
+ opts = {
100
+ host: @host,
101
+ port: @port,
102
+ keep_alive: @keep_alive
14
103
  }
104
+ opts[:username] = @security.username if @security.respond_to?(:username)
105
+ opts[:password] = @security.password if @security.respond_to?(:password)
106
+ if @security.respond_to?(:use_tls) && @security.use_tls
107
+ opts[:ssl] = @security.use_tls
108
+ opts[:ca_file] = @security.tls.ca_file
109
+ opts[:cert_file] = @security.tls.cert_file
110
+ opts[:key_file] = @security.tls.key_file
111
+ end
112
+ # In order to handle Exception raised from reading Thread
113
+ # in MQTT::Client caused by network disconnection (during read_byte),
114
+ # @connect_thread generates connection.
115
+ @client_mutex = Mutex.new
116
+ @client = MQTT::Client.new(opts)
117
+ connect_loop
118
+ end
119
+
120
+ # This method is called when shutting down.
121
+ # Shutdown the thread and close sockets or files here.
122
+ def shutdown
123
+ super
124
+
125
+ @client.disconnect
126
+ end
127
+
128
+ def connect_loop
129
+ while (true)
130
+ @client_mutex.lock
131
+ begin
132
+ @client.disconnect if @client.connected?
133
+ @client.connect
134
+ init_retry_interval
135
+ rescue MQTT::ProtocolException => e
136
+ sleep_retry_interval(e, "Protocol error occurs.")
137
+ @client_mutex.unlock
138
+ next
139
+ rescue Timeout::Error => e
140
+ sleep_retry_interval(e, "Timeout error occurs.")
141
+ @client_mutex.unlock
142
+ next
143
+ rescue SystemCallError => e
144
+ sleep_retry_interval(e, "System call error occurs.")
145
+ @client_mutex.unlock
146
+ next
147
+ rescue StandardError=> e
148
+ sleep_retry_interval(e, "The other error occurs.")
149
+ @client_mutex.unlock
150
+ next
151
+ end
152
+ @client_mutex.unlock
153
+ break
154
+ end
155
+ end
156
+
157
+ def publish_error_handler
158
+ while(true)
159
+ begin
160
+ yield
161
+ rescue MQTT::ProtocolException => e
162
+ sleep_retry_interval(e, "Protocol error occurs.")
163
+ connect_loop
164
+ next
165
+ rescue Timeout::Error => e
166
+ sleep_retry_interval(e, "Timeout error occurs.")
167
+ connect_loop
168
+ next
169
+ rescue SystemCallError => e
170
+ sleep_retry_interval(e, "System call error occurs.")
171
+ connect_loop
172
+ next
173
+ rescue StandardError=> e
174
+ sleep_retry_interval(e, "The other error occurs.")
175
+ connect_loop
176
+ next
177
+ end
178
+ break
179
+ end
180
+ end
181
+
182
+ def add_send_time(record)
183
+ if @send_time
184
+ # send_time is recorded in ms
185
+ record.merge({@send_time_key => Fluent::EventTime.now})
186
+ else
187
+ record
188
+ end
189
+ end
190
+
191
+ def publish_event_stream(tag, es)
192
+ if es.class == Fluent::OneEventStream
193
+ es = inject_values_to_event_stream(tag, es)
194
+ es.each do |time, record|
195
+ $log.debug "#{rewrite_tag(tag)}, #{add_send_time(record)}"
196
+ publish_error_handler do
197
+ @client.publish(rewrite_tag(tag), @formatter.format(tag, time, add_send_time(record)))
198
+ end
199
+ end
200
+ else
201
+ es = inject_values_to_event_stream(tag, es)
202
+ array = []
203
+ es.each do |time, record|
204
+ $log.debug "#{rewrite_tag(tag)}, #{add_send_time(record)}"
205
+ array << add_send_time(record)
206
+ end
207
+ publish_error_handler do
208
+ @client.publish(rewrite_tag(tag), @formatter.format(tag, Fluent::EventTime.now, array))
209
+ end
210
+ end
15
211
  $log.flush
212
+ end
213
+
214
+ def process(tag, es)
215
+ publish_event_stream(tag, es)
216
+ end
217
+
218
+ def write(chunk)
219
+ return if chunk.empty?
220
+ tag = chunk.metadata.tag
16
221
 
17
- chain.next
222
+ publish_event_stream(tag, es)
18
223
  end
19
224
  end
20
225
  end
@@ -0,0 +1,32 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/input'
3
+ require 'fluent/plugin/in_mqtt'
4
+
5
+ class MqttInputTest < Test::Unit::TestCase
6
+ def setup
7
+ Fluent::Test.setup
8
+ end
9
+
10
+
11
+ CONFIG = %[
12
+ ]
13
+
14
+ def create_driver(conf = CONFIG, opts = {})
15
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::MqttInput, opts: opts).configure(conf)
16
+ end
17
+
18
+ sub_test_case "configure" do
19
+ test "host and port" do
20
+ d = create_driver %[
21
+ host 127.0.0.1
22
+ port 1300
23
+ <parse>
24
+ @type json
25
+ time_format %FT%T%:z
26
+ </parse>
27
+ ]
28
+ assert_equal '127.0.0.1', d.instance.host
29
+ assert_equal 1300, d.instance.port
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/output'
3
+ require 'fluent/plugin/out_mqtt'
4
+
5
+ class MqttOutputTest < Test::Unit::TestCase
6
+ def setup
7
+ Fluent::Test.setup
8
+ end
9
+
10
+
11
+ CONFIG = %[
12
+ ]
13
+
14
+ def create_driver(conf = CONFIG, opts = {})
15
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::MqttOutput, opts: opts).configure(conf)
16
+ end
17
+
18
+ sub_test_case 'non-buffered' do
19
+ test 'configure' do
20
+ d = create_driver %[
21
+ host 127.0.0.1
22
+ port 1300
23
+ <format>
24
+ @type json
25
+ </format>
26
+ ]
27
+ assert_equal '127.0.0.1', d.instance.host
28
+ assert_equal 1300, d.instance.port
29
+ end
30
+ end
31
+ end
metadata CHANGED
@@ -1,71 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-mqtt-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Toyokazu Akiyama
8
8
  autorequire:
9
9
  bindir: []
10
10
  cert_chain: []
11
- date: 2016-03-05 00:00:00.000000000 Z
11
+ date: 2017-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.0
19
+ version: '0.14'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.0
26
+ version: '0.14'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: mqtt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
33
+ version: '0.4'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.3'
40
+ version: '0.4'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.10'
47
+ version: '1.14'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.10'
54
+ version: '1.14'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: '12.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: '12.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: fluentd input/output plugin for mqtt broker
70
84
  email:
71
85
  - toyokazu@gmail.com
@@ -75,19 +89,20 @@ extra_rdoc_files: []
75
89
  files:
76
90
  - ".gitignore"
77
91
  - ".travis.yml"
92
+ - CHANGELOG.md
78
93
  - Gemfile
79
94
  - LICENSE
80
95
  - README.md
81
96
  - Rakefile
82
97
  - fluent-plugin-mqtt-io.gemspec
83
98
  - lib/fluent/plugin/in_mqtt.rb
84
- - lib/fluent/plugin/mqtt_output_mixin.rb
85
99
  - lib/fluent/plugin/out_mqtt.rb
86
- - lib/fluent/plugin/out_mqtt_buf.rb
87
100
  - test/helper.rb
101
+ - test/plugin/test_in_mqtt.rb
102
+ - test/plugin/test_out_mqtt.rb
88
103
  homepage: https://github.com/toyokazu/fluent-plugin-mqtt-io
89
104
  licenses:
90
- - MIT
105
+ - Apache License Version 2.0
91
106
  metadata: {}
92
107
  post_install_message:
93
108
  rdoc_options: []
@@ -97,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
112
  requirements:
98
113
  - - ">="
99
114
  - !ruby/object:Gem::Version
100
- version: '0'
115
+ version: 2.2.0
101
116
  required_rubygems_version: !ruby/object:Gem::Requirement
102
117
  requirements:
103
118
  - - ">="
@@ -105,9 +120,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
120
  version: '0'
106
121
  requirements: []
107
122
  rubyforge_project:
108
- rubygems_version: 2.4.5.1
123
+ rubygems_version: 2.6.8
109
124
  signing_key:
110
125
  specification_version: 4
111
126
  summary: fluentd input/output plugin for mqtt broker
112
127
  test_files:
113
128
  - test/helper.rb
129
+ - test/plugin/test_in_mqtt.rb
130
+ - test/plugin/test_out_mqtt.rb
@@ -1,155 +0,0 @@
1
- module Fluent
2
- module MqttOutputMixin
3
- # config_param defines a parameter. You can refer a parameter via @path instance variable
4
- # Without :default, a parameter is required.
5
-
6
- def self.included(base)
7
- base.config_param :port, :integer, :default => 1883
8
- base.config_param :host, :string, :default => '127.0.0.1'
9
- base.config_param :username, :string, :default => nil
10
- base.config_param :password, :string, :default => nil
11
- base.config_param :keep_alive, :integer, :default => 15
12
- base.config_param :ssl, :bool, :default => nil
13
- base.config_param :ca_file, :string, :default => nil
14
- base.config_param :key_file, :string, :default => nil
15
- base.config_param :cert_file, :string, :default => nil
16
- base.config_param :time_key, :string, :default => 'time'
17
- base.config_param :time_format, :string, :default => nil
18
- base.config_param :topic_rewrite_pattern, :string, :default => nil
19
- base.config_param :topic_rewrite_replacement, :string, :default => nil
20
- base.config_param :bulk_trans_sep, :string, :default => "\t"
21
- base.config_param :send_time, :bool, :default => false
22
- base.config_param :send_time_key, :string, :default => "send_time"
23
- base.config_param :initial_interval, :integer, :default => 1
24
- base.config_param :retry_inc_ratio, :integer, :default => 2
25
- end
26
-
27
- require 'mqtt'
28
-
29
- # This method is called before starting.
30
- # 'conf' is a Hash that includes configuration parameters.
31
- # If the configuration is invalid, raise Fluent::ConfigError.
32
- def configure(conf)
33
- super
34
- init_retry_interval
35
- end
36
-
37
- def init_retry_interval
38
- @retry_interval = @initial_interval
39
- end
40
-
41
- def increment_retry_interval
42
- @retry_interval = @retry_interval * @retry_inc_ratio
43
- end
44
-
45
- def sleep_retry_interval(e, message)
46
- $log.error "#{message},#{e.class},#{e.message}"
47
- $log.error "Retry in #{@retry_interval} sec"
48
- sleep @retry_interval
49
- increment_retry_interval
50
- end
51
-
52
- # This method is called when starting.
53
- # Open sockets or files here.
54
- def start
55
- super
56
-
57
- $log.debug "start mqtt #{@host}"
58
- opts = {
59
- host: @host,
60
- port: @port,
61
- username: @username,
62
- password: @password,
63
- keep_alive: @keep_alive
64
- }
65
- opts[:ssl] = @ssl if @ssl
66
- opts[:ca_file] = @ca_file if @ca_file
67
- opts[:cert_file] = @cert_file if @cert_file
68
- opts[:key_file] = @key_file if @key_file
69
- # In order to handle Exception raised from reading Thread
70
- # in MQTT::Client caused by network disconnection (during read_byte),
71
- # @connect_thread generates connection.
72
- @client = MQTT::Client.new(opts)
73
- @connect_thread = Thread.new do
74
- while (true)
75
- begin
76
- @client.disconnect if @client.connected?
77
- @client.connect
78
- init_retry_interval
79
- sleep
80
- rescue MQTT::ProtocolException => e
81
- sleep_retry_interval(e, "Protocol error occurs.")
82
- next
83
- rescue Timeout::Error => e
84
- sleep_retry_interval(e, "Timeout error occurs.")
85
- next
86
- rescue SystemCallError => e
87
- sleep_retry_interval(e, "System call error occurs.")
88
- next
89
- rescue StandardError=> e
90
- sleep_retry_interval(e, "The other error occurs.")
91
- next
92
- end
93
- end
94
- end
95
- end
96
-
97
- # This method is called when shutting down.
98
- # Shutdown the thread and close sockets or files here.
99
- def shutdown
100
- super
101
-
102
- @client.disconnect
103
- end
104
-
105
- def format_time(time)
106
- case @time_format
107
- when nil then
108
- # default format is integer value
109
- time
110
- when "iso8601" then
111
- # iso8601 format
112
- Time.at(time).iso8601
113
- else
114
- # specified strftime format
115
- Time.at(time).strftime(@time_format)
116
- end
117
- end
118
-
119
- def timestamp_hash(time)
120
- if @time_key.nil?
121
- {}
122
- else
123
- {@time_key => format_time(time)}
124
- end
125
- end
126
-
127
- def add_send_time(record)
128
- if @send_time
129
- # send_time is recorded in ms
130
- record.merge({@send_time_key => Time.now.instance_eval { self.to_i * 1000 + (usec/1000) }})
131
- else
132
- record
133
- end
134
- end
135
-
136
-
137
- def rewrite_tag(tag)
138
- if @topic_rewrite_pattern.nil?
139
- tag.gsub("\.", "/")
140
- else
141
- tag.gsub("\.", "/").gsub(Regexp.new(@topic_rewrite_pattern), @topic_rewrite_replacement)
142
- end
143
- end
144
-
145
- def json_parse(message)
146
- begin
147
- y = Yajl::Parser.new
148
- y.parse(message)
149
- rescue
150
- $log.error "JSON parse error", :error => $!.to_s, :error_class => $!.class.to_s
151
- $log.warn_backtrace $!.backtrace
152
- end
153
- end
154
- end
155
- end
@@ -1,36 +0,0 @@
1
- module Fluent
2
- class MqttBufferedOutput < BufferedOutput
3
- require 'fluent/plugin/mqtt_output_mixin'
4
- include Fluent::MqttOutputMixin
5
-
6
- # First, register the plugin. NAME is the name of this plugin
7
- # and identifies the plugin in the configuration file.
8
- Fluent::Plugin.register_output('mqtt_buf', self)
9
-
10
- # This method is called when an event reaches to Fluentd.
11
- # Convert the event to a raw string.
12
- def format(tag, time, record)
13
- [tag, time, record].to_msgpack
14
- end
15
-
16
- # This method is called every flush interval. Write the buffer chunk
17
- # to files or databases here.
18
- # 'chunk' is a buffer chunk that includes multiple formatted
19
- # events. You can use 'data = chunk.read' to get all events and
20
- # 'chunk.open {|io| ... }' to get IO objects.
21
- #
22
- # NOTE! This method is called by internal thread, not Fluentd's main thread. So IO wait doesn't affect other plugins.
23
- def write(chunk)
24
- messages = {}
25
- chunk.msgpack_each do |tag, time, record|
26
- messages[tag] = [] if messages[tag].nil?
27
- messages[tag] << add_send_time(record).merge(timestamp_hash(time))
28
- end
29
- messages.keys.each do |tag|
30
- $log.debug "Thread ID: #{Thread.current.object_id}, topic: #{rewrite_tag(tag)}, message: #{messages[tag]}"
31
- @client.publish(rewrite_tag(tag), messages[tag].map {|m| m.to_json}.join(@bulk_trans_sep))
32
- end
33
- $log.flush
34
- end
35
- end
36
- end