fluent-plugin-heroku-syslog 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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/
|
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
|