fluent-plugin-heroku-syslog 0.0.1 → 0.1.0

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: c1ba40b8e6dea0a28ffed82b25787897c0ead301
4
- data.tar.gz: e2e430813032e6445a4bf0344dca1f746580ca09
3
+ metadata.gz: 45464deb9237a40a07e441e968af9c5b70fa3bce
4
+ data.tar.gz: e4308639e7ccb688d490d81f6e2d47ff11b125fa
5
5
  SHA512:
6
- metadata.gz: cc75128973dfcb608908f1f930e9cf4e6eb436457a12d1cbfddc1308c902e8e56d77df21f34b153c9f32c3c9a2c3b3c47e6490eb31df67cb1c3c699d35dfbf53
7
- data.tar.gz: 4743b87ccc0fec77eb82613a49308700995aa305a133ed68c55c39631339b4b356e14c6b1dbabe0475491ee69dddbfea839cd21cd2404aa8f4a44cae51f5f98e
6
+ metadata.gz: 7f5051f8e44f95e6f6597e9a6c2c3be526396c0bf97e146d0d310d857f2d4676b524636209cb2b1df00376f44779fc1d6569547de5ec91320438b3cbb7432bc1
7
+ data.tar.gz: a2037e66e3937285987c138091a7959e16e807ad328120d81d61a9f794d47c5a0dcd58b5d359a33ab98ce27eca09647294060e7d1b6d763b7ab60433f16edb48
@@ -3,3 +3,4 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
5
  - 2.1.0
6
+ - 2.2.0
data/README.md CHANGED
@@ -1,16 +1,28 @@
1
1
  # fluent-plugin-heroku-syslog
2
2
 
3
- fluent plugin to drain heroku syslog.
3
+ [fluent](http://fluentd.org) plugin to drain heroku syslog.
4
4
 
5
5
  [![Build Status](https://travis-ci.org/hakobera/fluent-plugin-heroku-syslog.png?branch=master)](https://travis-ci.org/hakobera/fluent-plugin-heroku-syslog)
6
6
 
7
+ ## Installation
8
+
9
+ Install with gem or fluent-gem command as:
10
+
11
+ ```
12
+ # for fluentd
13
+ $ gem install fluent-plugin-heroku-syslog
14
+
15
+ # for td-agent
16
+ $ sudo /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-heroku-syslog
17
+ ```
18
+
7
19
  ## Component
8
20
 
9
21
  ### HerokuSyslogInput
10
22
 
11
- Plugin to accept syslog input from [heroku syslog drains](https://devcenter.heroku.com/articles/logging#syslog-drains).
23
+ Plugin to accept syslog input from [heroku syslog drains](https://devcenter.heroku.com/articles/log-drains#syslog-drains).
12
24
 
13
- ## Configuration
25
+ #### Configuration
14
26
 
15
27
  ```
16
28
  <source>
@@ -21,9 +33,34 @@ Plugin to accept syslog input from [heroku syslog drains](https://devcenter.hero
21
33
  </source>
22
34
  ```
23
35
 
24
- ## TODO
36
+ ### HerokuSyslogHttpInput
25
37
 
26
- - Implement authentication logic or filter like HTTP basic auth.
38
+ Plugin to accept syslog input from [heroku http(s) drains](https://devcenter.heroku.com/articles/log-drains#http-s-drains).
39
+
40
+ #### Configuration
41
+
42
+ ##### Basic
43
+
44
+ ```
45
+ <source>
46
+ type heroku_syslog_http
47
+ port 9880
48
+ bind 0.0.0.0
49
+ tag heroku
50
+ </source>
51
+ ```
52
+
53
+ ##### Filter by drain_ids
54
+
55
+ ```
56
+ <source>
57
+ type heroku_syslog_http
58
+ port 9880
59
+ bind 0.0.0.0
60
+ tag heroku
61
+ drain_ids ["YOUR-HEROKU-DRAIN-ID"]
62
+ </source>
63
+ ```
27
64
 
28
65
  ## Copyright
29
66
 
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "fluent-plugin-heroku-syslog"
4
- gem.version = "0.0.1"
4
+ gem.version = "0.1.0"
5
5
  gem.authors = ["Kazuyuki Honda"]
6
6
  gem.email = ["hakobera@gmail.com"]
7
7
  gem.description = %q{fluent plugin to drain heroku syslog}
@@ -14,6 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
15
  gem.require_paths = ["lib"]
16
16
 
17
- gem.add_runtime_dependency "fluentd"
17
+ gem.add_runtime_dependency "fluentd", ">= 0.10.55"
18
18
  gem.add_development_dependency "rake"
19
+ gem.add_development_dependency("test-unit", ["~> 3.0.2"])
19
20
  end
@@ -1,221 +1,35 @@
1
+ require 'fluent/plugin/in_tcp'
2
+ require_relative 'logplex'
3
+
1
4
  module Fluent
2
- class HerokuSyslogInput < Input
5
+ class HerokuSyslogInput < TcpInput
3
6
  Plugin.register_input('heroku_syslog', self)
7
+ include Logplex
4
8
 
5
- OCTET_COUNTING_REGEXP = /^([0-9]+)\s+(.*)/
6
- SYSLOG_REGEXP = /^\<([0-9]+)\>[0-9]*(.*)/
7
- SYSLOG_ALL_REGEXP = /^\<(?<pri>[0-9]+)\>[0-9]* (?<time>[^ ]*) (?<drain_id>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*) (?<pid>[a-zA-Z0-9\.]+)? *(?<message>.*)$/
8
- TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
9
-
10
- FACILITY_MAP = {
11
- 0 => 'kern',
12
- 1 => 'user',
13
- 2 => 'mail',
14
- 3 => 'daemon',
15
- 4 => 'auth',
16
- 5 => 'syslog',
17
- 6 => 'lpr',
18
- 7 => 'news',
19
- 8 => 'uucp',
20
- 9 => 'cron',
21
- 10 => 'authpriv',
22
- 11 => 'ftp',
23
- 12 => 'ntp',
24
- 13 => 'audit',
25
- 14 => 'alert',
26
- 15 => 'at',
27
- 16 => 'local0',
28
- 17 => 'local1',
29
- 18 => 'local2',
30
- 19 => 'local3',
31
- 20 => 'local4',
32
- 21 => 'local5',
33
- 22 => 'local6',
34
- 23 => 'local7'
35
- }
36
-
37
- PRIORITY_MAP = {
38
- 0 => 'emerg',
39
- 1 => 'alert',
40
- 2 => 'crit',
41
- 3 => 'err',
42
- 4 => 'warn',
43
- 5 => 'notice',
44
- 6 => 'info',
45
- 7 => 'debug'
46
- }
47
-
48
- def initialize
49
- super
50
- require 'cool.io'
51
- require 'fluent/plugin/socket_util'
52
- end
53
-
54
- config_param :port, :integer, :default => 5140
55
- config_param :bind, :string, :default => '0.0.0.0'
56
- config_param :tag, :string
57
-
58
- def configure(conf)
59
- super
60
-
61
- parser = TextParser.new
62
- if parser.configure(conf, false)
63
- @parser = parser
64
- else
65
- @parser = nil
66
- @time_parser = TextParser::TimeParser.new(TIME_FORMAT)
67
- end
68
- end
69
-
70
- def start
71
- if @parser
72
- callback = method(:receive_data_parser)
73
- else
74
- callback = method(:receive_data)
75
- end
76
-
77
- @loop = Coolio::Loop.new
78
- @handler = listen(callback)
79
- @loop.attach(@handler)
80
-
81
- @thread = Thread.new(&method(:run))
82
- end
83
-
84
- def shutdown
85
- @loop.watchers.each {|w| w.detach }
86
- @loop.stop
87
- @handler.close
88
- @thread.join
89
- end
90
-
91
- def run
92
- @loop.run
93
- rescue
94
- $log.error "unexpected error", :error=>$!.to_s
95
- $log.error_backtrace
96
- end
97
-
98
- protected
99
- def receive_data_parser(data)
100
- m = SYSLOG_REGEXP.match(data)
101
- unless m
102
- $log.debug "invalid syslog message: #{data.dump}"
103
- return
104
- end
105
- pri = m[1].to_i
106
- text = m[2]
107
-
108
- time, record = @parser.parse(text)
109
- unless time && record
110
- return
111
- end
112
-
113
- emit(pri, time, record)
114
-
115
- rescue
116
- $log.warn data.dump, :error=>$!.to_s
117
- $log.debug_backtrace
118
- end
119
-
120
- def receive_data(data)
121
- m = SYSLOG_ALL_REGEXP.match(data)
122
- unless m
123
- $log.debug "invalid syslog message", :data=>data
124
- return
125
- end
126
-
127
- pri = nil
128
- time = nil
129
- record = {}
130
-
131
- m.names.each {|name|
132
- if value = m[name]
133
- case name
134
- when "pri"
135
- pri = value.to_i
136
- when "time"
137
- time = @time_parser.parse(value.gsub(/ +/, ' ').gsub(/\.[0-9]+/, ''))
138
- else
139
- record[name] = value
140
- end
141
- end
142
- }
143
-
144
- time ||= Engine.now
145
-
146
- emit(pri, time, record)
147
-
148
- rescue
149
- $log.warn data.dump, :error=>$!.to_s
150
- $log.debug_backtrace
151
- end
9
+ config_param :format, :string, :default => SYSLOG_REGEXP
10
+ config_param :drain_ids, :array, :default => nil
152
11
 
153
12
  private
154
13
 
155
- def listen(callback)
156
- $log.debug "listening heroku syslog socket on #{@bind}:#{@port}"
157
- Coolio::TCPServer.new(@bind, @port, TcpHandler, callback)
158
- end
159
-
160
- def emit(pri, time, record)
161
- facility = FACILITY_MAP[pri >> 3]
162
- priority = PRIORITY_MAP[pri & 0b111]
163
-
164
- tag = "#{@tag}.#{facility}.#{priority}"
165
-
166
- Engine.emit(tag, time, record)
167
- rescue => e
168
- $log.error "syslog failed to emit", :error => e.to_s, :error_class => e.class.to_s, :tag => tag, :record => Yajl.dump(record)
169
- end
170
-
171
- class TcpHandler < Coolio::Socket
172
- def initialize(io, on_message)
173
- super(io)
174
- if io.is_a?(TCPSocket)
175
- opt = [1, @timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
176
- io.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
14
+ def on_message(msg, addr)
15
+ @parser.parse(msg) { |time, record|
16
+ unless time && record
17
+ log.warn "pattern not match: #{msg.inspect}"
18
+ return
177
19
  end
178
- $log.trace { "accepted fluent socket object_id=#{self.object_id}" }
179
- @on_message = on_message
180
- @buffer = "".force_encoding('ASCII-8BIT')
181
- end
182
-
183
- def on_connect
184
- end
185
20
 
186
- def on_read(data)
187
- @buffer << data
188
- pos = 0
189
-
190
- # syslog family add "\n" to each message and this seems only way to split messages in tcp stream
191
- while i = @buffer.index("\n", pos)
192
- msg = @buffer[pos..i]
193
-
194
- # Support Octet Counting
195
- # https://tools.ietf.org/html/rfc6587#section-3.4.1
196
- m = OCTET_COUNTING_REGEXP.match(msg)
197
- valid = true
198
- if m
199
- msg_len = m[1].to_i - 1
200
- msg = m[2]
201
-
202
- if msg_len != msg.length
203
- $log.debug "invalid syslog message length", :expected => msg_len, :actual => msg.length, :data => msg
204
- valid = false
205
- end
206
- end
207
- @on_message.call(msg) if valid
208
- pos = i + 1
21
+ unless @drain_ids.nil? || @drain_ids.include?(record['drain_id'])
22
+ log.warn "drain_id not match: #{msg.inspect}"
23
+ return
209
24
  end
210
- @buffer.slice!(0, pos) if pos > 0
211
- rescue => e
212
- $log.error "syslog error", :error => e, :error_class => e.class
213
- close
214
- end
215
25
 
216
- def on_close
217
- $log.trace { "closed fluent socket object_id=#{self.object_id}" }
218
- end
26
+ record[@source_host_key] = addr[3] if @source_host_key
27
+ parse_logplex(record)
28
+ router.emit(@tag, time, record)
29
+ }
30
+ rescue => e
31
+ log.error msg.dump, :error => e, :error_class => e.class, :host => addr[3]
32
+ log.error_backtrace
219
33
  end
220
34
  end
221
35
  end
@@ -0,0 +1,37 @@
1
+ require 'fluent/plugin/in_http'
2
+ require_relative 'logplex'
3
+
4
+ module Fluent
5
+ class HerokuSyslogHttpInput < HttpInput
6
+ Plugin.register_input('heroku_syslog_http', self)
7
+ include Logplex
8
+
9
+ config_param :format, :string, :default => SYSLOG_REGEXP
10
+ config_param :drain_ids, :array, :default => nil
11
+
12
+ private
13
+
14
+ def parse_params_with_parser(params)
15
+ if content = params[EVENT_RECORD_PARAMETER]
16
+ records = []
17
+ messages = content.split("\n")
18
+ messages.each do |msg|
19
+ @parser.parse(msg) { |time, record|
20
+ raise "Received event is not #{@format}: #{content}" if record.nil?
21
+
22
+ record["time"] ||= time
23
+ parse_logplex(record, params)
24
+ unless @drain_ids.nil? || @drain_ids.include?(record['drain_id'])
25
+ log.warn "drain_id not match: #{msg.inspect}"
26
+ next
27
+ end
28
+ records << record
29
+ }
30
+ end
31
+ return nil, records
32
+ else
33
+ raise "'#{EVENT_RECORD_PARAMETER}' parameter is required"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+ module Fluent
2
+ module Logplex
3
+ SYSLOG_REGEXP = '/^([0-9]+)\\s+\\<(?<pri>[0-9]+)\\>[0-9]* (?<time>[^ ]*) (?<drain_id>[^ ]*) (?<ident>[a-zA-Z0-9_\\/\\.\\-]*) (?<pid>[a-zA-Z0-9\\.]+)? *(?<message>.*)$/'
4
+
5
+ FACILITY_MAP = {
6
+ 0 => 'kern',
7
+ 1 => 'user',
8
+ 2 => 'mail',
9
+ 3 => 'daemon',
10
+ 4 => 'auth',
11
+ 5 => 'syslog',
12
+ 6 => 'lpr',
13
+ 7 => 'news',
14
+ 8 => 'uucp',
15
+ 9 => 'cron',
16
+ 10 => 'authpriv',
17
+ 11 => 'ftp',
18
+ 12 => 'ntp',
19
+ 13 => 'audit',
20
+ 14 => 'alert',
21
+ 15 => 'at',
22
+ 16 => 'local0',
23
+ 17 => 'local1',
24
+ 18 => 'local2',
25
+ 19 => 'local3',
26
+ 20 => 'local4',
27
+ 21 => 'local5',
28
+ 22 => 'local6',
29
+ 23 => 'local7'
30
+ }
31
+
32
+ PRIORITY_MAP = {
33
+ 0 => 'emerg',
34
+ 1 => 'alert',
35
+ 2 => 'crit',
36
+ 3 => 'err',
37
+ 4 => 'warn',
38
+ 5 => 'notice',
39
+ 6 => 'info',
40
+ 7 => 'debug'
41
+ }
42
+
43
+ def parse_logplex(record, params=nil)
44
+ pri = record['pri'].to_i
45
+ record['facility'] = FACILITY_MAP[pri >> 3]
46
+ record['priority'] = PRIORITY_MAP[pri & 0b111]
47
+
48
+ if params
49
+ record['drain_id'] = params['HTTP_LOGPLEX_DRAIN_TOKEN']
50
+ end
51
+
52
+ record
53
+ end
54
+ end
55
+ end
@@ -42,6 +42,7 @@ def ipv6_enabled?
42
42
  end
43
43
 
44
44
  require 'fluent/plugin/in_heroku_syslog'
45
+ require 'fluent/plugin/in_heroku_syslog_http'
45
46
 
46
47
  class Test::Unit::TestCase
47
48
  end
@@ -15,7 +15,7 @@ class HerokuSyslogInputTest < Test::Unit::TestCase
15
15
  IPv6_CONFIG = %[
16
16
  port #{PORT}
17
17
  bind ::1
18
- tag syslog
18
+ tag heroku.syslog
19
19
  ]
20
20
 
21
21
  def create_driver(conf=CONFIG)
@@ -43,11 +43,13 @@ class HerokuSyslogInputTest < Test::Unit::TestCase
43
43
  tests = [
44
44
  {
45
45
  'msg' => "92 <13>1 2014-01-29T06:25:52.589365+00:00 d.916a3e50-efa1-4754-aded-ffffffffffff app web.1 foo\n",
46
- 'expected' => Time.strptime('2014-01-29T06:25:52+00:00', '%Y-%m-%dT%H:%M:%S%z').to_i
46
+ 'expected' => 'foo',
47
+ 'expected_time' => Time.strptime('2014-01-29T06:25:52+00:00', '%Y-%m-%dT%H:%M:%S%z').to_i
47
48
  },
48
49
  {
49
50
  'msg' => "92 <13>1 2014-01-30T07:35:00.123456+09:00 d.916a3e50-efa1-4754-aded-ffffffffffff app web.1 bar\n",
50
- 'expected' => Time.strptime('2014-01-30T07:35:00+09:00', '%Y-%m-%dT%H:%M:%S%z').to_i
51
+ 'expected' => 'bar',
52
+ 'expected_time' => Time.strptime('2014-01-30T07:35:00+09:00', '%Y-%m-%dT%H:%M:%S%z').to_i
51
53
  }
52
54
  ]
53
55
 
@@ -60,12 +62,7 @@ class HerokuSyslogInputTest < Test::Unit::TestCase
60
62
  sleep 1
61
63
  end
62
64
 
63
- emits = d.emits
64
- $log.debug emits
65
- emits.each_index {|i|
66
- $log.debug emits[i][1]
67
- assert_equal(tests[i]['expected'], emits[i][1])
68
- }
65
+ compare_test_result(d.emits, tests)
69
66
  }
70
67
  end
71
68
 
@@ -101,6 +98,54 @@ class HerokuSyslogInputTest < Test::Unit::TestCase
101
98
  compare_test_result(d.emits, tests)
102
99
  end
103
100
 
101
+ def test_accept_matched_drain_id
102
+ d = create_driver(CONFIG + "\ndrain_ids [\"d.916a3e50-efa1-4754-aded-ffffffffffff\"]")
103
+ tests = create_test_case
104
+
105
+ d.run do
106
+ TCPSocket.open('127.0.0.1', PORT) do |s|
107
+ tests.each {|test|
108
+ s.send(test['msg'], 0)
109
+ }
110
+ end
111
+ sleep 1
112
+ end
113
+
114
+ compare_test_result(d.emits, tests)
115
+ end
116
+
117
+ def test_accept_matched_drain_id_multiple
118
+ d = create_driver(CONFIG + "\ndrain_ids [\"abc\",\"d.916a3e50-efa1-4754-aded-ffffffffffff\"]")
119
+ tests = create_test_case
120
+
121
+ d.run do
122
+ TCPSocket.open('127.0.0.1', PORT) do |s|
123
+ tests.each {|test|
124
+ s.send(test['msg'], 0)
125
+ }
126
+ end
127
+ sleep 1
128
+ end
129
+
130
+ compare_test_result(d.emits, tests)
131
+ end
132
+
133
+ def test_ignore_unmatched_drain_id
134
+ d = create_driver(CONFIG + "\ndrain_ids [\"abc\"]")
135
+ tests = create_test_case
136
+
137
+ d.run do
138
+ TCPSocket.open('127.0.0.1', PORT) do |s|
139
+ tests.each {|test|
140
+ s.send(test['msg'], 0)
141
+ }
142
+ end
143
+ sleep 1
144
+ end
145
+
146
+ assert_equal(0, d.emits.length)
147
+ end
148
+
104
149
  def create_test_case
105
150
  # actual syslog message has "\n"
106
151
  msgs = [
@@ -116,11 +161,16 @@ class HerokuSyslogInputTest < Test::Unit::TestCase
116
161
  end
117
162
 
118
163
  def compare_test_result(emits, tests)
164
+ assert_equal(tests.length, emits.length)
119
165
  emits.each_index {|i|
120
- assert_equal(tests[i]['expected'], emits[i][2]['message'])
166
+ assert_equal('heroku.syslog', emits[i][0])
167
+ assert_equal(tests[i]['expected_time'], emits[i][1]) if tests[i]['expected_time']
168
+ assert_equal(tests[i]['expected'], emits[i][2]['message']) if tests[i]['expected']
121
169
  assert_equal('d.916a3e50-efa1-4754-aded-ffffffffffff', emits[i][2]['drain_id'])
122
170
  assert_equal('app', emits[i][2]['ident'])
123
171
  assert_equal('web.1', emits[i][2]['pid'])
172
+ assert_equal('user', emits[i][2]['facility'])
173
+ assert_equal('notice', emits[i][2]['priority'])
124
174
  }
125
175
  end
126
176
 
@@ -0,0 +1,181 @@
1
+ require 'helper'
2
+ require 'net/http'
3
+
4
+ class HerokuSyslogHttpInputTest < Test::Unit::TestCase
5
+ def setup
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ PORT = unused_port
10
+ CONFIG = %[
11
+ port #{PORT}
12
+ bind 127.0.0.1
13
+ body_size_limit 10m
14
+ keepalive_timeout 5
15
+ ]
16
+
17
+ def create_driver(conf=CONFIG)
18
+ Fluent::Test::InputTestDriver.new(Fluent::HerokuSyslogHttpInput).configure(conf)
19
+ end
20
+
21
+ def test_configure
22
+ d = create_driver
23
+ assert_equal PORT, d.instance.port
24
+ assert_equal '127.0.0.1', d.instance.bind
25
+ assert_equal 10*1024*1024, d.instance.body_size_limit
26
+ assert_equal 5, d.instance.keepalive_timeout
27
+ end
28
+
29
+ def test_time_format
30
+ d = create_driver
31
+
32
+ tests = [
33
+ {
34
+ 'msg' => "<13>1 2014-01-29T06:25:52.589365+00:00 host app web.1 foo",
35
+ 'expected' => 'foo',
36
+ 'expected_time' => Time.strptime('2014-01-29T06:25:52+00:00', '%Y-%m-%dT%H:%M:%S%z').to_i
37
+ },
38
+ {
39
+ 'msg' => "<13>1 2014-01-30T07:35:00.123456+09:00 host app web.1 bar",
40
+ 'expected' => 'bar',
41
+ 'expected_time' => Time.strptime('2014-01-30T07:35:00+09:00', '%Y-%m-%dT%H:%M:%S%z').to_i
42
+ }
43
+ ]
44
+
45
+ tests.each do |msg|
46
+ msg['msg'] = "#{msg['msg'].length} #{msg['msg']}"
47
+ end
48
+
49
+ d.expect_emit 'heroku', tests[0]['expected_time'], {
50
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
51
+ "ident"=>"app",
52
+ "pid"=>"web.1",
53
+ "message"=> "foo",
54
+ "pri" => "13",
55
+ "facility" => "user",
56
+ "priority" => "notice"
57
+ }
58
+
59
+ d.expect_emit 'heroku', tests[1]['expected_time'], {
60
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
61
+ "ident"=>"app",
62
+ "pid"=>"web.1",
63
+ "message"=> "bar",
64
+ "pri" => "13",
65
+ "facility" => "user",
66
+ "priority" => "notice"
67
+ }
68
+
69
+ d.run do
70
+ res = post(tests)
71
+ assert_equal "200", res.code
72
+ end
73
+ end
74
+
75
+ def test_msg_size
76
+ d = create_driver
77
+ tests = create_test_case
78
+
79
+ d.expect_emit 'heroku', tests[0]['expected_time'], {
80
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
81
+ "ident" => "app",
82
+ "pid" => "web.1",
83
+ "message" => "x" * 100,
84
+ "pri" => "13",
85
+ "facility" => "user",
86
+ "priority" => "notice"
87
+ }
88
+ d.expect_emit 'heroku', tests[1]['expected_time'], {
89
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
90
+ "ident" => "app",
91
+ "pid" => "web.1",
92
+ "message" => "x" * 1024,
93
+ "pri" => "13",
94
+ "facility" => "user",
95
+ "priority" => "notice"
96
+ }
97
+
98
+ d.run do
99
+ res = post(tests)
100
+ assert_equal "200", res.code
101
+ end
102
+ end
103
+
104
+ def test_accept_matched_drain_id_multiple
105
+ d = create_driver(CONFIG + "\ndrain_ids [\"abc\", \"d.fc6b856b-3332-4546-93de-7d0ee272c3bd\"]")
106
+ tests = create_test_case
107
+
108
+ d.expect_emit 'heroku', tests[0]['expected_time'], {
109
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
110
+ "ident" => "app",
111
+ "pid" => "web.1",
112
+ "message" => "x" * 100,
113
+ "pri" => "13",
114
+ "facility" => "user",
115
+ "priority" => "notice"
116
+ }
117
+ d.expect_emit 'heroku', tests[1]['expected_time'], {
118
+ "drain_id" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
119
+ "ident" => "app",
120
+ "pid" => "web.1",
121
+ "message" => "x" * 1024,
122
+ "pri" => "13",
123
+ "facility" => "user",
124
+ "priority" => "notice"
125
+ }
126
+
127
+ d.run do
128
+ res = post(tests)
129
+ assert_equal "200", res.code
130
+ end
131
+ end
132
+
133
+ def test_ignore_unmatched_drain_id
134
+ d = create_driver(CONFIG + "\ndrain_ids [\"abc\"]")
135
+ tests = create_test_case
136
+
137
+ d.run do
138
+ res = post(tests)
139
+ assert_equal "200", res.code
140
+ end
141
+
142
+ assert_equal(0, d.emits.length)
143
+ end
144
+
145
+ def create_test_case
146
+ # actual syslog message has "\n"
147
+ msgs = [
148
+ {
149
+ 'msg' => '<13>1 2014-01-01T01:23:45.123456+00:00 host app web.1 ' + 'x' * 100,
150
+ 'expected' => 'x' * 100,
151
+ 'expected_time' => Time.parse("2014-01-01T01:23:45 UTC").to_i
152
+ },
153
+ {
154
+ 'msg' => '<13>1 2014-01-01T01:23:45.123456+00:00 host app web.1 ' + 'x' * 1024,
155
+ 'expected' => 'x' * 1024,
156
+ 'expected_time' => Time.parse("2014-01-01T01:23:45 UTC").to_i
157
+ }
158
+ ]
159
+
160
+ msgs.each do |msg|
161
+ msg['msg'] = "#{msg['msg'].length} #{msg['msg']}"
162
+ end
163
+
164
+ msgs
165
+ end
166
+
167
+ def post(messages)
168
+ # https://github.com/heroku/logplex/blob/master/doc/README.http_drains.md
169
+ http = Net::HTTP.new("127.0.0.1", PORT)
170
+ req = Net::HTTP::Post.new('/heroku', {
171
+ "Content-Type" => "application/logplex-1",
172
+ "Logplex-Msg-Count" => messages.length.to_s,
173
+ "Logplex-Frame-Id" => "09C557EAFCFB6CF2740EE62F62971098",
174
+ "Logplex-Drain-Token" => "d.fc6b856b-3332-4546-93de-7d0ee272c3bd",
175
+ "User-Agent" => "Logplex/v49"
176
+ })
177
+ req.body = messages.map {|msg| msg['msg']}.join("\n")
178
+ http.request(req)
179
+ end
180
+
181
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-heroku-syslog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kazuyuki Honda
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-30 00:00:00.000000000 Z
11
+ date: 2015-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 0.10.55
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'
26
+ version: 0.10.55
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.2
41
55
  description: fluent plugin to drain heroku syslog
42
56
  email:
43
57
  - hakobera@gmail.com
@@ -53,8 +67,11 @@ files:
53
67
  - Rakefile
54
68
  - fluent-plugin-heroku-syslog.gemspec
55
69
  - lib/fluent/plugin/in_heroku_syslog.rb
70
+ - lib/fluent/plugin/in_heroku_syslog_http.rb
71
+ - lib/fluent/plugin/logplex.rb
56
72
  - test/helper.rb
57
73
  - test/plugin/test_in_heroku_syslog.rb
74
+ - test/plugin/test_in_heroku_syslog_http.rb
58
75
  homepage: https://github.com/hakobera/fluent-plugin-heroku-syslog
59
76
  licenses:
60
77
  - APLv2
@@ -75,10 +92,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
92
  version: '0'
76
93
  requirements: []
77
94
  rubyforge_project:
78
- rubygems_version: 2.2.0
95
+ rubygems_version: 2.2.2
79
96
  signing_key:
80
97
  specification_version: 4
81
98
  summary: fluent plugin to drain heroku syslog
82
99
  test_files:
83
100
  - test/helper.rb
84
101
  - test/plugin/test_in_heroku_syslog.rb
102
+ - test/plugin/test_in_heroku_syslog_http.rb