fluent-plugin-sumologic-cloud-syslog 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +15 -0
- data/.gitignore +40 -0
- data/.travis.yml +29 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +19 -0
- data/LICENSE +235 -0
- data/README.md +75 -0
- data/Rakefile +24 -0
- data/docs/configuration.md +119 -0
- data/fluent-plugin-sumologic-cloud-syslog.gemspec +43 -0
- data/lib/fluent/plugin/out_sumologic_cloud_syslog.rb +118 -0
- data/lib/sumologic_cloud_syslog/facility.rb +46 -0
- data/lib/sumologic_cloud_syslog/logger.rb +81 -0
- data/lib/sumologic_cloud_syslog/lookup_from_const.rb +36 -0
- data/lib/sumologic_cloud_syslog/protocol.rb +138 -0
- data/lib/sumologic_cloud_syslog/severity.rb +30 -0
- data/lib/sumologic_cloud_syslog/ssl_transport.rb +53 -0
- data/lib/sumologic_cloud_syslog/version.rb +17 -0
- data/test/fluent/test_out_sumologic_cloud_syslog.rb +138 -0
- data/test/helper.rb +33 -0
- data/test/sumologic_cloud_syslog/test_logger.rb +47 -0
- data/test/sumologic_cloud_syslog/test_protocol.rb +149 -0
- metadata +182 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'date'
|
16
|
+
|
17
|
+
require_relative 'facility'
|
18
|
+
require_relative 'severity'
|
19
|
+
|
20
|
+
# Syslog protocol https://tools.ietf.org/html/rfc5424
|
21
|
+
module SumologicCloudSyslog
|
22
|
+
# RFC defined nil value
|
23
|
+
NIL_VALUE = '-'
|
24
|
+
|
25
|
+
# All headers by specification wrapped in single object
|
26
|
+
class Header
|
27
|
+
attr_accessor :facility, :severity, :version, :timestamp, :hostname, :app_name, :procid, :msgid
|
28
|
+
|
29
|
+
FACILITIES = {}
|
30
|
+
SEVERITIES = {}
|
31
|
+
|
32
|
+
Facility.setup_constants FACILITIES
|
33
|
+
Severity.setup_constants SEVERITIES
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@timestamp = Time.now
|
37
|
+
@severity = 'INFO'
|
38
|
+
@facility = 'LOCAL0'
|
39
|
+
@version = 1
|
40
|
+
@hostname = SumologicCloudSyslog::NIL_VALUE
|
41
|
+
@app_name = SumologicCloudSyslog::NIL_VALUE
|
42
|
+
@procid = SumologicCloudSyslog::NIL_VALUE
|
43
|
+
@msgid = SumologicCloudSyslog::NIL_VALUE
|
44
|
+
end
|
45
|
+
|
46
|
+
def timestamp=(val)
|
47
|
+
raise ArgumentError.new("Must provide Time object value instead: #{val.inspect}") unless val.is_a?(Time)
|
48
|
+
@timestamp = val
|
49
|
+
end
|
50
|
+
|
51
|
+
def facility=(val)
|
52
|
+
raise ArgumentError.new("Invalid facility value: #{val.inspect}") unless FACILITIES.key?(val)
|
53
|
+
@facility = val
|
54
|
+
end
|
55
|
+
|
56
|
+
def severity=(val)
|
57
|
+
raise ArgumentError.new("Invalid severity value: #{val.inspect}") unless SEVERITIES.key?(val)
|
58
|
+
@severity = val
|
59
|
+
end
|
60
|
+
|
61
|
+
# Priority value is calculated by first multiplying the Facility
|
62
|
+
# number by 8 and then adding the numerical value of the Severity.
|
63
|
+
def pri
|
64
|
+
FACILITIES[facility] * 8 + SEVERITIES[severity]
|
65
|
+
end
|
66
|
+
|
67
|
+
def assemble
|
68
|
+
[
|
69
|
+
"<#{pri}>#{version}",
|
70
|
+
timestamp.to_datetime.rfc3339,
|
71
|
+
hostname,
|
72
|
+
app_name,
|
73
|
+
procid,
|
74
|
+
msgid
|
75
|
+
].join(' ')
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
assemble
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Structured data field
|
84
|
+
class StructuredData
|
85
|
+
attr_accessor :id, :data
|
86
|
+
|
87
|
+
def initialize(id)
|
88
|
+
@id = id
|
89
|
+
@data = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Format data structured data to
|
93
|
+
# [id k="v" ...]
|
94
|
+
def assemble
|
95
|
+
parts = [id]
|
96
|
+
data.each do |k, v|
|
97
|
+
# Characters ", ] and \ must be escaped to prevent any parsing errors
|
98
|
+
v = v.gsub(/(\"|\]|\\)/) { |match| '\\' + match }
|
99
|
+
parts << "#{k}=\"#{v}\""
|
100
|
+
end
|
101
|
+
"[#{parts.join(' ')}]"
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
assemble
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Message represents full message that can be sent to syslog
|
110
|
+
class Message
|
111
|
+
attr_accessor :header, :structured_data, :msg
|
112
|
+
|
113
|
+
def initialize
|
114
|
+
@msg = ''
|
115
|
+
@structured_data = []
|
116
|
+
@header = Header.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def assemble
|
120
|
+
# Start with header
|
121
|
+
out = [header.to_s]
|
122
|
+
# Add all structured data
|
123
|
+
if structured_data.length > 0
|
124
|
+
out << structured_data.map(&:to_s).join('')
|
125
|
+
else
|
126
|
+
out << SumologicCloudSyslog::NIL_VALUE
|
127
|
+
end
|
128
|
+
# Add message
|
129
|
+
out << msg if msg.length > 0
|
130
|
+
# Message must end with new line delimiter
|
131
|
+
out.join(' ') + "\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_s
|
135
|
+
assemble
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative 'lookup_from_const'
|
16
|
+
|
17
|
+
module SumologicCloudSyslog
|
18
|
+
module Severity
|
19
|
+
extend LookupFromConst
|
20
|
+
EMERG = PANIC = 0
|
21
|
+
ALERT = 1
|
22
|
+
CRIT = 2
|
23
|
+
ERR = ERROR = 3
|
24
|
+
WARN = WARNING = 4
|
25
|
+
NOTICE = 5
|
26
|
+
INFO = 6
|
27
|
+
DEBUG = 7
|
28
|
+
NONE = 10
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'socket'
|
16
|
+
require 'openssl'
|
17
|
+
|
18
|
+
module SumologicCloudSyslog
|
19
|
+
# Supports SSL connection to remote host
|
20
|
+
class SSLTransport
|
21
|
+
attr_accessor :socket
|
22
|
+
|
23
|
+
attr_reader :host, :port, :cert, :key, :ssl_version
|
24
|
+
|
25
|
+
def initialize(host, port, cert: nil, key: nil, ssl_version: :TLSv1_2)
|
26
|
+
@host = host
|
27
|
+
@port = port
|
28
|
+
@cert = cert
|
29
|
+
@key = key
|
30
|
+
@ssl_version = ssl_version
|
31
|
+
connect
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect
|
35
|
+
tcp = TCPSocket.new(host, port)
|
36
|
+
|
37
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
38
|
+
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
39
|
+
ctx.ssl_version = ssl_version
|
40
|
+
|
41
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.open(cert)) if cert
|
42
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.open(key)) if key
|
43
|
+
|
44
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
45
|
+
@socket.connect
|
46
|
+
end
|
47
|
+
|
48
|
+
# Forward any methods directly to SSLSocket
|
49
|
+
def method_missing(method_sym, *arguments, &block)
|
50
|
+
@socket.send(method_sym, *arguments, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module SumologicCloudSyslog
|
16
|
+
VERSION = '0.1.2'
|
17
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'helper'
|
16
|
+
require 'date'
|
17
|
+
require 'minitest/mock'
|
18
|
+
require 'fluent/plugin/out_sumologic_cloud_syslog'
|
19
|
+
|
20
|
+
class SumologicCloudSyslogOutput < Test::Unit::TestCase
|
21
|
+
def setup
|
22
|
+
Fluent::Test.setup
|
23
|
+
@driver = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def driver(tag='test', conf='')
|
27
|
+
@driver ||= Fluent::Test::OutputTestDriver.new(Fluent::SumologicCloudSyslogOutput, tag).configure(conf)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sample_record
|
31
|
+
{
|
32
|
+
"app_name" => "app",
|
33
|
+
"hostname" => "host",
|
34
|
+
"procid" => $$,
|
35
|
+
"msgid" => 1000,
|
36
|
+
"message" => "MESSAGE",
|
37
|
+
"severity" => "PANIC",
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def mock_logger
|
42
|
+
io = StringIO.new
|
43
|
+
io.set_encoding('utf-8')
|
44
|
+
logger = ::SumologicCloudSyslog::Logger.new(io, "TOKEN")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_configure
|
48
|
+
config = %{
|
49
|
+
host syslog.collection.us1.sumologic.com
|
50
|
+
port 6514
|
51
|
+
cert
|
52
|
+
key
|
53
|
+
token 1234567890
|
54
|
+
}
|
55
|
+
instance = driver('test', config).instance
|
56
|
+
|
57
|
+
assert_equal 'syslog.collection.us1.sumologic.com', instance.host
|
58
|
+
assert_equal '6514', instance.port
|
59
|
+
assert_equal '', instance.cert
|
60
|
+
assert_equal '', instance.key
|
61
|
+
assert_equal '1234567890', instance.token
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_default_emit
|
65
|
+
config = %{
|
66
|
+
host syslog.collection.us1.sumologic.com
|
67
|
+
port 6514
|
68
|
+
cert
|
69
|
+
key
|
70
|
+
token 1234567890
|
71
|
+
}
|
72
|
+
instance = driver('test', config).instance
|
73
|
+
|
74
|
+
time = Time.now
|
75
|
+
record = sample_record
|
76
|
+
logger = mock_logger
|
77
|
+
|
78
|
+
instance.stub(:new_logger, logger) do
|
79
|
+
chain = Minitest::Mock.new
|
80
|
+
chain.expect(:next, nil)
|
81
|
+
instance.emit('test', {time.to_i => record}, chain)
|
82
|
+
end
|
83
|
+
|
84
|
+
formatted_time = time.dup.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
85
|
+
assert_equal logger.transport.string, "<134>1 #{time.to_datetime.rfc3339} - - - - [TOKEN] #{formatted_time}\ttest\t#{record.to_json.to_s}\n\n"
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_message_headers_mapping
|
89
|
+
config = %{
|
90
|
+
host syslog.collection.us1.sumologic.com
|
91
|
+
port 6514
|
92
|
+
cert
|
93
|
+
key
|
94
|
+
token 1234567890
|
95
|
+
hostname_key hostname
|
96
|
+
procid_key procid
|
97
|
+
app_name_key app_name
|
98
|
+
msgid_key msgid
|
99
|
+
}
|
100
|
+
instance = driver('test', config).instance
|
101
|
+
|
102
|
+
time = Time.now
|
103
|
+
record = sample_record
|
104
|
+
logger = mock_logger
|
105
|
+
|
106
|
+
instance.stub(:new_logger, logger) do
|
107
|
+
chain = Minitest::Mock.new
|
108
|
+
chain.expect(:next, nil)
|
109
|
+
instance.emit('test', {time.to_i => record}, chain)
|
110
|
+
end
|
111
|
+
|
112
|
+
assert_true logger.transport.string.start_with?("<134>1 #{time.to_datetime.rfc3339} host app #{$$} 1000 [TOKEN]")
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_message_severity_mapping
|
116
|
+
config = %{
|
117
|
+
host syslog.collection.us1.sumologic.com
|
118
|
+
port 6514
|
119
|
+
cert
|
120
|
+
key
|
121
|
+
token 1234567890
|
122
|
+
severity_key severity
|
123
|
+
}
|
124
|
+
instance = driver('test', config).instance
|
125
|
+
|
126
|
+
time = Time.now
|
127
|
+
record = sample_record
|
128
|
+
logger = mock_logger
|
129
|
+
|
130
|
+
instance.stub(:new_logger, logger) do
|
131
|
+
chain = Minitest::Mock.new
|
132
|
+
chain.expect(:next, nil)
|
133
|
+
instance.emit('test', {time.to_i => record}, chain)
|
134
|
+
end
|
135
|
+
|
136
|
+
assert_true logger.transport.string.start_with?("<128>1")
|
137
|
+
end
|
138
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'coveralls'
|
16
|
+
require 'simplecov'
|
17
|
+
|
18
|
+
SimpleCov.start
|
19
|
+
|
20
|
+
Coveralls.wear! if ENV['TRAVIS']
|
21
|
+
|
22
|
+
# Fluentd sets default encoding to ASCII-8BIT, but coverall can load git data which can contain UTF-8 characters
|
23
|
+
at_exit do
|
24
|
+
Encoding.default_internal = 'UTF-8' if defined?(Encoding) && Encoding.respond_to?(:default_internal)
|
25
|
+
Encoding.default_external = 'UTF-8' if defined?(Encoding) && Encoding.respond_to?(:default_external)
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'test/unit'
|
29
|
+
require 'fluent/test'
|
30
|
+
require 'minitest/pride'
|
31
|
+
|
32
|
+
require 'webmock/test_unit'
|
33
|
+
WebMock.disable_net_connect!
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Copyright 2016 Acquia, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'helper'
|
16
|
+
require 'date'
|
17
|
+
require 'sumologic_cloud_syslog/logger'
|
18
|
+
|
19
|
+
class Logger < Test::Unit::TestCase
|
20
|
+
def test_logger_defaults
|
21
|
+
io = StringIO.new
|
22
|
+
l = SumologicCloudSyslog::Logger.new(io, "TOKEN")
|
23
|
+
time = Time.now
|
24
|
+
l.log(:WARN, "MESSAGE", time: time)
|
25
|
+
assert_equal io.string, "<132>1 #{time.to_datetime.rfc3339} - - - - [TOKEN] MESSAGE\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_logger_default_headers
|
29
|
+
io = StringIO.new
|
30
|
+
l = SumologicCloudSyslog::Logger.new(io, "TOKEN")
|
31
|
+
l.hostname("hostname")
|
32
|
+
l.app_name("appname")
|
33
|
+
l.procid($$)
|
34
|
+
l.facility("SYSLOG")
|
35
|
+
time = Time.now
|
36
|
+
l.log(:WARN, "MESSAGE", time: time)
|
37
|
+
assert_equal io.string, "<44>1 #{time.to_datetime.rfc3339} hostname appname #{$$} - [TOKEN] MESSAGE\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_logger_closed
|
41
|
+
io = StringIO.new
|
42
|
+
l = SumologicCloudSyslog::Logger.new(io, "TOKEN")
|
43
|
+
assert_false l.closed?
|
44
|
+
l.close
|
45
|
+
assert_true l.closed?
|
46
|
+
end
|
47
|
+
end
|