fluent-plugin-sumologic-cloud-syslog 0.1.2

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.
@@ -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