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 +4 -4
- data/.travis.yml +1 -0
- data/README.md +42 -5
- data/fluent-plugin-heroku-syslog.gemspec +3 -2
- data/lib/fluent/plugin/in_heroku_syslog.rb +22 -208
- data/lib/fluent/plugin/in_heroku_syslog_http.rb +37 -0
- data/lib/fluent/plugin/logplex.rb +55 -0
- data/test/helper.rb +1 -0
- data/test/plugin/test_in_heroku_syslog.rb +60 -10
- data/test/plugin/test_in_heroku_syslog_http.rb +181 -0
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45464deb9237a40a07e441e968af9c5b70fa3bce
|
4
|
+
data.tar.gz: e4308639e7ccb688d490d81f6e2d47ff11b125fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f5051f8e44f95e6f6597e9a6c2c3be526396c0bf97e146d0d310d857f2d4676b524636209cb2b1df00376f44779fc1d6569547de5ec91320438b3cbb7432bc1
|
7
|
+
data.tar.gz: a2037e66e3937285987c138091a7959e16e807ad328120d81d61a9f794d47c5a0dcd58b5d359a33ab98ce27eca09647294060e7d1b6d763b7ab60433f16edb48
|
data/.travis.yml
CHANGED
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
|
[](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/
|
23
|
+
Plugin to accept syslog input from [heroku syslog drains](https://devcenter.heroku.com/articles/log-drains#syslog-drains).
|
12
24
|
|
13
|
-
|
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
|
-
|
36
|
+
### HerokuSyslogHttpInput
|
25
37
|
|
26
|
-
|
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
|
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 <
|
5
|
+
class HerokuSyslogInput < TcpInput
|
3
6
|
Plugin.register_input('heroku_syslog', self)
|
7
|
+
include Logplex
|
4
8
|
|
5
|
-
|
6
|
-
|
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
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
data/test/helper.rb
CHANGED
@@ -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' =>
|
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' =>
|
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
|
-
|
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(
|
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
|
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:
|
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:
|
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:
|
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.
|
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
|