fluent-plugin-out-falcon 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93262b579cc60c929f865d81171a46b4d610738d
4
- data.tar.gz: 6d0e4885faaebce8db8a1ca12488f9f0fd426a3c
3
+ metadata.gz: 7e69f38952bf8c9cf7da9555ffaedd7c83f7b47d
4
+ data.tar.gz: 5e1313a65375d800cfc35d73442748dee87911c0
5
5
  SHA512:
6
- metadata.gz: 00d5bc6bb7e9ed15148858bc792a3aed8290cea197e7f66c8d22cfe2579667583df53e79edcbd8a3b2c7ff3b7e2e026b184ddc48f063703c8d8e51c2b1764aef
7
- data.tar.gz: 6d8d6d3b27bc2cdbca11cd8238d127a90ecc5b14337bad1360d479e0e5bb5abb8d7bd62549b3b3a0610ce1fed8a8ec217193de2cd39bbfdc2bb811c52f664d27
6
+ metadata.gz: 78e8a8af151ac7e71d86c9f83de59aca0e986dadf16758e38e857fd84d6131524afa5e078bcde962c77ebf114fa47f69dfe7f31efde435ca85b0d27a62c68a87
7
+ data.tar.gz: f0f3564531cd9e50af2b50a21a2c38d32e0ff930ef4c77d24bfbc7ce0c1dfdc20137e7338f05a31d372fa0ad4a3b9de7b7c2a978ab686f35e34a1b6497221d6b
data/README.md CHANGED
@@ -1,12 +1,23 @@
1
1
  # fluent-plugin-out-falcon, a plugin for [Fluentd](http://fluentd.org)
2
2
 
3
- A [fluentd][1] output plugin for sending logs to [falcon][2]'s push api.
3
+ A [fluentd][1] output plugin for sending logs to [falcon][2]'s push API.
4
4
 
5
5
  ## Configuration options
6
6
 
7
- <match *>
7
+ `record` option will be posted to falcon.
8
+ It can only be in one line(limited by fluentd), in the form as `([{ruby_hash_items},{ruby_hash_items},...])`.
9
+ The outsider `()` is used to avoid fluentd interpret this line as json.
10
+ You can use `tag`, `time` and `record` variables while writing ruby\_hash.
11
+
12
+ <source>
13
+ @type dummy
14
+ tag xx.yyy
15
+ dummy {"ip":"8.8.8.8", "data1":888, "data2": "message"}
16
+ </source>
17
+ <match xx.yyy>
8
18
  type falcon
9
- endpoint_url http://localhost.local/api/
19
+ endpoint_url http://localhost:1988/v1/push
20
+ records ([{'metric'=>'number_data', 'endpoint'=>record['ip'], 'timestamp'=>time, 'value'=>record['data1'], 'step'=>1, 'counterType'=>'GAUGE', 'tags'=>nil}, {'metric'=>'string_data', 'endpoint'=>record['ip'], 'timestamp'=>time, 'value'=>record['data2'], 'step'=>1, 'counterType'=>'GAUGE', 'tags'=>nil}])
10
21
  rate_limit_msec 100 # default: 0 = no rate limiting
11
22
  raise_on_error false # default: true
12
23
  authentication basic # default: none
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "fluent-plugin-out-falcon"
5
+ gem.version = "0.1.6"
6
+ gem.authors = ["tsingakbar"]
7
+ gem.email = ["yuqing@cmcm.com"]
8
+ gem.summary = %q{A Fluentd output plugin to send logs to falcon's push API}
9
+ gem.description = gem.summary
10
+ gem.homepage = "https://github.com/tsingakbar/fluent-plugin-out-falcon"
11
+ gem.licenses = ["Apache-2.0"]
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ["lib"]
17
+
18
+ gem.add_runtime_dependency "yajl-ruby", "~> 1.0"
19
+ gem.add_runtime_dependency "fluentd", [">= 0.10.0", "< 2"]
20
+ gem.add_development_dependency "bundler"
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "test-unit", ">= 3.1.0"
23
+ end
@@ -8,18 +8,11 @@ class Fluent::FalconOutput < Fluent::Output
8
8
  require 'yajl'
9
9
  end
10
10
 
11
- # Map which record field to falcon's value filed
12
- #config_param :value_mapping, :string
11
+ config_param :records, :string
13
12
 
14
13
  # Endpoint URL ex. localhost.local/api/
15
14
  config_param :endpoint_url, :string
16
15
 
17
- # HTTP method
18
- config_param :http_method, :string, :default => :post
19
-
20
- # form | json
21
- config_param :serializer, :string, :default => :json
22
-
23
16
  # Simple rate limiting: ignore any records within `rate_limit_msec`
24
17
  # since the last one.
25
18
  config_param :rate_limit_msec, :integer, :default => 0
@@ -35,20 +28,6 @@ class Fluent::FalconOutput < Fluent::Output
35
28
  def configure(conf)
36
29
  super
37
30
 
38
- serializers = [:json, :form]
39
- @serializer = if serializers.include? @serializer.intern
40
- @serializer.intern
41
- else
42
- :form
43
- end
44
-
45
- http_methods = [:get, :put, :post, :delete]
46
- @http_method = if http_methods.include? @http_method.intern
47
- @http_method.intern
48
- else
49
- :post
50
- end
51
-
52
31
  @auth = case @authentication
53
32
  when 'basic' then :basic
54
33
  else
@@ -69,11 +48,8 @@ class Fluent::FalconOutput < Fluent::Output
69
48
  end
70
49
 
71
50
  def set_body(req, tag, time, record)
72
- if @serializer == :json
73
- set_json_body(req, record)
74
- else
75
- req.set_form_data(record)
76
- end
51
+ req.body = Yajl.dump(eval(@records))
52
+ req['Content-Type'] = 'application/json'
77
53
  req
78
54
  end
79
55
 
@@ -81,22 +57,10 @@ class Fluent::FalconOutput < Fluent::Output
81
57
  req
82
58
  end
83
59
 
84
- def set_json_body(req, data)
85
- # TODO remove record_modifier
86
- #req.body = Yajl.dump([data].map do |record|
87
- # temp = record[@value_mapping]
88
- # data.clear
89
- # data['value'] = temp
90
- # data['time'] = Time.now.to_i
91
- #end)
92
- req.body = Yajl.dump([data])
93
- req['Content-Type'] = 'application/json'
94
- end
95
-
96
60
  def create_request(tag, time, record)
97
61
  url = format_url(tag, time, record)
98
62
  uri = URI.parse(url)
99
- req = Net::HTTP.const_get(@http_method.to_s.capitalize).new(uri.path)
63
+ req = Net::HTTP.const_get('Post').new(uri.path)
100
64
  set_body(req, tag, time, record)
101
65
  set_header(req, tag, time, record)
102
66
  return req, uri
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-out-falcon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
- - Marica Odagaki
7
+ - tsingakbar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
@@ -86,9 +86,9 @@ dependencies:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  version: 3.1.0
89
- description: A Fluentd output plugin to send logs to falcon's push api
89
+ description: A Fluentd output plugin to send logs to falcon's push API
90
90
  email:
91
- - ento.entotto@gmail.com
91
+ - yuqing@cmcm.com
92
92
  executables: []
93
93
  extensions: []
94
94
  extra_rdoc_files: []
@@ -98,10 +98,8 @@ files:
98
98
  - Gemfile
99
99
  - LICENSE.txt
100
100
  - README.md
101
- - Rakefile
101
+ - fluent-plugin-out-falcon.gemspec
102
102
  - lib/fluent/plugin/out_falcon.rb
103
- - lib/fluent/test/falcon_output_test.rb
104
- - test/plugin/test_out_falcon.rb
105
103
  homepage: https://github.com/tsingakbar/fluent-plugin-out-falcon
106
104
  licenses:
107
105
  - Apache-2.0
@@ -125,6 +123,5 @@ rubyforge_project:
125
123
  rubygems_version: 2.5.1
126
124
  signing_key:
127
125
  specification_version: 4
128
- summary: A Fluentd output plugin to send logs to falcon's push api
129
- test_files:
130
- - test/plugin/test_out_falcon.rb
126
+ summary: A Fluentd output plugin to send logs to falcon's push API
127
+ test_files: []
data/Rakefile DELETED
@@ -1,11 +0,0 @@
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
@@ -1,54 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
-
4
- begin
5
- Bundler.setup(:default, :development)
6
- rescue Bundler::BundlerError => e
7
- $stderr.puts e.message
8
- $stderr.puts "Run `bundle install` to install missing gems"
9
- exit e.status_code
10
- end
11
-
12
- require 'test/unit'
13
-
14
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
- $LOAD_PATH.unshift(File.dirname(__FILE__))
16
-
17
- require 'fluent/test'
18
-
19
- unless ENV.has_key?('VERBOSE')
20
- nulllogger = Object.new
21
- nulllogger.instance_eval {|obj|
22
- def method_missing(method, *args)
23
- # pass
24
- end
25
- }
26
- $log = nulllogger
27
- end
28
-
29
- class Test::Unit::TestCase
30
- end
31
-
32
- require 'webrick'
33
-
34
- # to handle POST/PUT/DELETE ...
35
- module WEBrick::HTTPServlet
36
- class ProcHandler < AbstractServlet
37
- alias do_POST do_GET
38
- alias do_PUT do_GET
39
- alias do_DELETE do_GET
40
- end
41
- end
42
-
43
- def get_code(server, port, path, headers={})
44
- require 'net/http'
45
- Net::HTTP.start(server, port){|http|
46
- http.get(path, headers).code
47
- }
48
- end
49
- def get_content(server, port, path, headers={})
50
- require 'net/http'
51
- Net::HTTP.start(server, port){|http|
52
- http.get(path, headers).body
53
- }
54
- end
@@ -1,320 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- require 'uri'
3
- require 'yajl'
4
- require 'fluent/test/falcon_output_test'
5
- require 'fluent/plugin/out_falcon'
6
-
7
-
8
- TEST_LISTEN_PORT = 5126
9
-
10
-
11
- class FalconOutputTestBase < Test::Unit::TestCase
12
- # setup / teardown for servers
13
- def setup
14
- Fluent::Test.setup
15
- @posts = []
16
- @puts = []
17
- @prohibited = 0
18
- @requests = 0
19
- @auth = false
20
- @dummy_server_thread = Thread.new do
21
- srv = if ENV['VERBOSE']
22
- WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT})
23
- else
24
- logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
25
- WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => TEST_LISTEN_PORT, :Logger => logger, :AccessLog => []})
26
- end
27
- begin
28
- allowed_methods = %w(POST PUT)
29
- srv.mount_proc('/api/') { |req,res|
30
- @requests += 1
31
- unless allowed_methods.include? req.request_method
32
- res.status = 405
33
- res.body = 'request method mismatch'
34
- next
35
- end
36
- if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
37
- # ok, authorized
38
- elsif @auth
39
- res.status = 403
40
- @prohibited += 1
41
- next
42
- else
43
- # ok, authorization not required
44
- end
45
-
46
- record = {:auth => nil}
47
- if req.content_type == 'application/json'
48
- record[:json] = Yajl.load(req.body)
49
- else
50
- record[:form] = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
51
- end
52
-
53
- instance_variable_get("@#{req.request_method.downcase}s").push(record)
54
-
55
- res.status = 200
56
- }
57
- srv.mount_proc('/') { |req,res|
58
- res.status = 200
59
- res.body = 'running'
60
- }
61
- srv.start
62
- ensure
63
- srv.shutdown
64
- end
65
- end
66
-
67
- # to wait completion of dummy server.start()
68
- require 'thread'
69
- cv = ConditionVariable.new
70
- watcher = Thread.new {
71
- connected = false
72
- while not connected
73
- begin
74
- get_content('localhost', TEST_LISTEN_PORT, '/')
75
- connected = true
76
- rescue Errno::ECONNREFUSED
77
- sleep 0.1
78
- rescue StandardError => e
79
- p e
80
- sleep 0.1
81
- end
82
- end
83
- cv.signal
84
- }
85
- mutex = Mutex.new
86
- mutex.synchronize {
87
- cv.wait(mutex)
88
- }
89
- end
90
-
91
- def test_dummy_server
92
- host = '127.0.0.1'
93
- port = TEST_LISTEN_PORT
94
- client = Net::HTTP.start(host, port)
95
-
96
- assert_equal '200', client.request_get('/').code
97
- assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge').code
98
-
99
- assert_equal 1, @posts.size
100
-
101
- assert_equal '1', @posts[0][:form]['number']
102
- assert_equal 'gauge', @posts[0][:form]['mode']
103
- assert_nil @posts[0][:auth]
104
-
105
- @auth = true
106
-
107
- assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge').code
108
-
109
- req_with_auth = lambda do |number, mode, user, pass|
110
- url = URI.parse("http://#{host}:#{port}/api/service/metrics/pos")
111
- req = Net::HTTP::Post.new(url.path)
112
- req.basic_auth user, pass
113
- req.set_form_data({'number'=>number, 'mode'=>mode})
114
- req
115
- end
116
-
117
- assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
118
-
119
- assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
120
-
121
- assert_equal 1, @posts.size
122
-
123
- assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
124
-
125
- assert_equal 2, @posts.size
126
-
127
- end
128
-
129
- def teardown
130
- @dummy_server_thread.kill
131
- @dummy_server_thread.join
132
- end
133
- end
134
-
135
- class FalconOutputTest < FalconOutputTestBase
136
- CONFIG_FORM = %[
137
- endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
138
- serializer form
139
- ]
140
-
141
- CONFIG_JSON = %[
142
- endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
143
- serializer json
144
- ]
145
-
146
- CONFIG_PUT = %[
147
- endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
148
- serializer form
149
- http_method put
150
- ]
151
-
152
- CONFIG_HTTP_ERROR = %[
153
- endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT + 1}/api/
154
- serializer form
155
- ]
156
-
157
- CONFIG_HTTP_ERROR_SUPPRESSED = %[
158
- endpoint_url https://127.0.0.1:#{TEST_LISTEN_PORT + 1}/api/
159
- serializer form
160
- raise_on_error false
161
- ]
162
-
163
- RATE_LIMIT_MSEC = 1200
164
-
165
- CONFIG_RATE_LIMIT = %[
166
- endpoint_url http://127.0.0.1:#{TEST_LISTEN_PORT}/api/
167
- serializer form
168
- rate_limit_msec #{RATE_LIMIT_MSEC}
169
- ]
170
-
171
- def create_driver(conf=CONFIG_FORM, tag='test.metrics')
172
- Fluent::Test::OutputTestDriver.new(Fluent::FalconOutput, tag).configure(conf)
173
- end
174
-
175
- def test_configure
176
- d = create_driver
177
- assert_equal "http://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
178
- assert_equal :form, d.instance.serializer
179
-
180
- d = create_driver CONFIG_JSON
181
- assert_equal "http://127.0.0.1:#{TEST_LISTEN_PORT}/api/", d.instance.endpoint_url
182
- assert_equal :json, d.instance.serializer
183
- end
184
-
185
- def test_emit_form
186
- d = create_driver
187
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => "\xe3\x81\x82".force_encoding("ascii-8bit") })
188
- d.run
189
-
190
- assert_equal 1, @posts.size
191
- record = @posts[0]
192
-
193
- assert_equal '50', record[:form]['field1']
194
- assert_equal '20', record[:form]['field2']
195
- assert_equal '10', record[:form]['field3']
196
- assert_equal '1', record[:form]['otherfield']
197
- assert_equal URI.encode_www_form_component("あ").upcase, record[:form]['binary'].upcase
198
- assert_nil record[:auth]
199
-
200
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
201
- d.run
202
-
203
- assert_equal 2, @posts.size
204
- end
205
-
206
- def test_emit_form_put
207
- d = create_driver CONFIG_PUT
208
- d.emit({ 'field1' => 50 })
209
- d.run
210
-
211
- assert_equal 0, @posts.size
212
- assert_equal 1, @puts.size
213
- record = @puts[0]
214
-
215
- assert_equal '50', record[:form]['field1']
216
- assert_nil record[:auth]
217
-
218
- d.emit({ 'field1' => 50 })
219
- d.run
220
-
221
- assert_equal 0, @posts.size
222
- assert_equal 2, @puts.size
223
- end
224
-
225
- def test_emit_json
226
- binary_string = "\xe3\x81\x82".force_encoding("ascii-8bit")
227
- d = create_driver CONFIG_JSON
228
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1, 'binary' => binary_string })
229
- d.run
230
-
231
- assert_equal 1, @posts.size
232
- record = @posts[0]
233
-
234
- assert_equal 50, record[:json][0]['field1']
235
- assert_equal 20, record[:json][0]['field2']
236
- assert_equal 10, record[:json][0]['field3']
237
- assert_equal 1, record[:json][0]['otherfield']
238
- assert_equal binary_string, record[:json][0]['binary'].force_encoding("ascii-8bit")
239
- assert_nil record[:auth]
240
- end
241
-
242
- def test_http_error_is_raised
243
- d = create_driver CONFIG_HTTP_ERROR
244
- assert_raise Errno::ECONNREFUSED do
245
- d.emit({ 'field1' => 50 })
246
- end
247
- end
248
-
249
- def test_http_error_is_suppressed_with_raise_on_error_false
250
- d = create_driver CONFIG_HTTP_ERROR_SUPPRESSED
251
- d.emit({ 'field1' => 50 })
252
- d.run
253
- # drive asserts the next output chain is called;
254
- # so no exception means our plugin handled the error
255
-
256
- assert_equal 0, @requests
257
- end
258
-
259
- def test_rate_limiting
260
- d = create_driver CONFIG_RATE_LIMIT
261
- record = { :k => 1 }
262
-
263
- last_emit = _current_msec
264
- d.emit(record)
265
- d.run
266
-
267
- assert_equal 1, @posts.size
268
-
269
- d.emit({})
270
- d.run
271
- assert last_emit + RATE_LIMIT_MSEC > _current_msec, "Still under rate limiting interval"
272
- assert_equal 1, @posts.size
273
-
274
- wait_msec = 500
275
- sleep (last_emit + RATE_LIMIT_MSEC - _current_msec + wait_msec) * 0.001
276
-
277
- assert last_emit + RATE_LIMIT_MSEC < _current_msec, "No longer under rate limiting interval"
278
- d.emit(record)
279
- d.run
280
- assert_equal 2, @posts.size
281
- end
282
-
283
- def _current_msec
284
- Time.now.to_f * 1000
285
- end
286
-
287
- def test_auth
288
- @auth = true # enable authentication of dummy server
289
-
290
- d = create_driver(CONFIG_FORM, 'test.metrics')
291
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
292
- d.run # failed in background, and output warn log
293
-
294
- assert_equal 0, @posts.size
295
- assert_equal 1, @prohibited
296
-
297
- d = create_driver(CONFIG_FORM + %[
298
- authentication basic
299
- username alice
300
- password wrong_password
301
- ], 'test.metrics')
302
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
303
- d.run # failed in background, and output warn log
304
-
305
- assert_equal 0, @posts.size
306
- assert_equal 2, @prohibited
307
-
308
- d = create_driver(CONFIG_FORM + %[
309
- authentication basic
310
- username alice
311
- password secret!
312
- ], 'test.metrics')
313
- d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
314
- d.run # failed in background, and output warn log
315
-
316
- assert_equal 1, @posts.size
317
- assert_equal 2, @prohibited
318
- end
319
-
320
- end