fluent-plugin-devo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
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 'bundler/gem_tasks'
16
+ require 'rake/testtask'
17
+
18
+ Rake::TestTask.new(:test) do |test|
19
+ test.libs << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+
24
+ task :default => :test
@@ -0,0 +1,151 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ # Copyright 2021 JMC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'socket'
18
+ require 'syslog_tls/logger'
19
+ require 'fluent/plugin/output'
20
+
21
+ module Fluent::Plugin
22
+ class SyslogTlsOutput < Output
23
+ Fluent::Plugin.register_output('devo', self)
24
+
25
+ helpers :inject, :formatter, :compat_parameters
26
+
27
+ DEFAULT_FORMAT_TYPE = 'json'
28
+
29
+ config_param :host, :string
30
+ config_param :port, :integer
31
+ config_param :idle_timeout, :integer, default: nil
32
+ config_param :ca_cert, :string, default: 'system'
33
+ config_param :verify_cert_name, :bool, default: true
34
+ config_param :token, :string, default: nil
35
+ config_param :client_cert, :string, default: nil
36
+ config_param :client_key, :string, default: nil
37
+ config_param :hostname, :string, default: nil
38
+ config_param :facility, :string, default: 'LOCAL0'
39
+
40
+ # Allow to map keys from record to syslog message headers
41
+ SYSLOG_HEADERS = [
42
+ :severity, :facility, :hostname, :app_name, :procid, :msgid
43
+ ]
44
+
45
+ SYSLOG_HEADERS.each do |key_name|
46
+ config_param "#{key_name}_key".to_sym, :string, default: nil
47
+ end
48
+
49
+ config_section :format do
50
+ config_set_default :@type, DEFAULT_FORMAT_TYPE
51
+ end
52
+
53
+ attr_accessor :formatter
54
+
55
+
56
+ def initialize
57
+ super
58
+ @loggers = {}
59
+ end
60
+
61
+ def shutdown
62
+ @loggers.values.each(&:close)
63
+ super
64
+ end
65
+
66
+ # This method is called before starting.
67
+ def configure(conf)
68
+ if conf['output_type'] && !conf['format']
69
+ conf['format'] = conf['output_type']
70
+ end
71
+ compat_parameters_convert(conf, :inject, :formatter)
72
+
73
+ super
74
+ @host = conf['host']
75
+ @port = conf['port']
76
+ @token = conf['token']
77
+ @hostname = conf['hostname'] || Socket.gethostname.split('.').first
78
+
79
+ # Determine mapping of record keys to syslog keys
80
+ @mappings = {}
81
+ SYSLOG_HEADERS.each do |key_name|
82
+ conf_key = "#{key_name}_key"
83
+ @mappings[key_name] = conf[conf_key] if conf.key?(conf_key)
84
+ end
85
+
86
+ @formatter = formatter_create(conf: conf.elements('format').first, default_type: DEFAULT_FORMAT_TYPE)
87
+ end
88
+
89
+ # Get logger for given tag
90
+ def logger(tag)
91
+ # Try to reuse existing logger
92
+ @loggers[tag] ||= new_logger(tag)
93
+
94
+ # Create new logger if old one is closed
95
+ if @loggers[tag].closed?
96
+ @loggers[tag] = new_logger(tag)
97
+ end
98
+
99
+ @loggers[tag]
100
+ end
101
+
102
+ def new_logger(tag)
103
+ transport = ::SyslogTls::SSLTransport.new(host, port,
104
+ idle_timeout: idle_timeout,
105
+ ca_cert: ca_cert,
106
+ client_cert: client_cert,
107
+ client_key: client_key,
108
+ verify_cert_name: verify_cert_name,
109
+ max_retries: 3,
110
+ )
111
+ logger = ::SyslogTls::Logger.new(transport, token)
112
+ logger.facility(facility)
113
+ logger.hostname(hostname)
114
+ logger.app_name(tag)
115
+ logger
116
+ end
117
+
118
+ def format(tag, time, record)
119
+ record = inject_values_to_record(tag, time, record)
120
+ @formatter.format(tag, time, record)
121
+ end
122
+
123
+ def process(tag, es)
124
+ es.each do |time, record|
125
+ record.each_pair do |_, v|
126
+ v.force_encoding('utf-8') if v.is_a?(String)
127
+ end
128
+
129
+ # Check if severity has been provided in record otherwise use INFO
130
+ # by default.
131
+ severity = if @mappings.key?(:severity)
132
+ record[@mappings[:severity]] || 'INFO'
133
+ else
134
+ 'INFO'
135
+ end
136
+
137
+ # Send message to Syslog
138
+ begin
139
+ logger(tag).log(severity, format(tag, time, record), time: Time.at(time)) do |header|
140
+ # Map syslog headers from record
141
+ @mappings.each do |name, record_key|
142
+ header.send("#{name}=", record[record_key]) unless record[record_key].nil?
143
+ end
144
+ end
145
+ rescue => e
146
+ log.error e.to_s
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2021 Devo .Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require_relative 'lookup_from_const'
17
+
18
+ module SyslogTls
19
+ module Facility
20
+ extend LookupFromConst
21
+ KERN = 0
22
+ USER = 1
23
+ MAIL = 2
24
+ DAEMON = 3
25
+ AUTH = 4
26
+ SYSLOG = 5
27
+ LPR = 6
28
+ NEWS = 7
29
+ UUCP = 8
30
+ CRON = 9
31
+ AUTHPRIV = 10
32
+ FTP = 11
33
+ NTP = 12
34
+ SECURITY = 13
35
+ CONSOLE = 14
36
+ RAS = 15
37
+ LOCAL0 = 16
38
+ LOCAL1 = 17
39
+ LOCAL2 = 18
40
+ LOCAL3 = 19
41
+ LOCAL4 = 20
42
+ LOCAL5 = 21
43
+ LOCAL6 = 22
44
+ LOCAL7 = 23
45
+ NONE = SYSLOG
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016 t.e.morgan.
3
+ # Copyright 2021 Devo .Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require_relative 'protocol'
18
+ require_relative 'ssl_transport'
19
+
20
+ module SyslogTls
21
+ class Logger
22
+ attr_reader :token
23
+ attr_accessor :transport
24
+
25
+ # Logger accepts transport which should implement IO methods
26
+ # close, closed? and write
27
+ def initialize(transport, token=nil)
28
+ @transport = transport
29
+ @default_header = SyslogTls::Header.new
30
+ @default_structured_data = SyslogTls::StructuredData.new(token)
31
+ end
32
+
33
+ # Sets default facility for each message
34
+ def facility(val)
35
+ @default_header.facility = val
36
+ end
37
+
38
+ # Sets default hostname for each message
39
+ def hostname(val)
40
+ @default_header.hostname = val
41
+ end
42
+
43
+ # Sets default app_name for each message
44
+ def app_name(val)
45
+ @default_header.app_name = val
46
+ end
47
+
48
+ # Sets default procid for message
49
+ def procid(val)
50
+ @default_header.procid = val
51
+ end
52
+
53
+ # Check if IO is closed
54
+ def closed?
55
+ transport && transport.closed?
56
+ end
57
+
58
+ def close
59
+ transport.close
60
+ end
61
+
62
+ # Send log message with severity to syslog
63
+ def log(severity, message, time: nil)
64
+ time ||= Time.now
65
+
66
+ m = SyslogTls::Message.new
67
+
68
+ # Include authentication header
69
+ m.structured_data << @default_structured_data
70
+
71
+ # Adjust header with current timestamp and severity
72
+ m.header = @default_header.dup
73
+ m.header.severity = severity
74
+ m.header.timestamp = time
75
+
76
+ yield m.header if block_given?
77
+
78
+ m.msg = message
79
+
80
+ transport.write(m.to_s)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2021 Devo .Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module SyslogTls
17
+ module LookupFromConst
18
+ def setup_constants(dst)
19
+ constants.each do |pri|
20
+ cval = const_get pri
21
+
22
+ dst[pri] = cval
23
+ dst[pri.downcase] = cval
24
+
25
+ dst[:"LOG_#{pri.to_s}"] = cval
26
+ dst[:"LOG_#{pri.downcase.to_s}"] = cval
27
+ const_set :"LOG_#{pri.to_s}", cval
28
+
29
+ dst[pri.to_s] = cval
30
+ dst[pri.downcase.to_s] = cval
31
+
32
+ dst[cval] = cval
33
+ dst[cval.to_s] = cval
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,137 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016 t.e.morgan.
3
+ # Copyright 2021 Devo .Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'date'
18
+
19
+ require_relative 'facility'
20
+ require_relative 'severity'
21
+
22
+ # Syslog protocol https://tools.ietf.org/html/rfc5424
23
+ module SyslogTls
24
+ # RFC defined nil value
25
+ NIL_VALUE = ''
26
+
27
+ # All headers by specification wrapped in single object
28
+ class Header
29
+ attr_accessor :version, :hostname, :app_name, :procid, :msgid
30
+ attr_reader :facility, :severity, :timestamp
31
+
32
+ FACILITIES = {}
33
+ SEVERITIES = {}
34
+
35
+ Facility.setup_constants FACILITIES
36
+ Severity.setup_constants SEVERITIES
37
+
38
+ def initialize
39
+ @timestamp = Time.now
40
+ @severity = 'INFO'
41
+ @facility = 'LOCAL0'
42
+ @version = 1
43
+ @hostname = NIL_VALUE
44
+ @app_name = NIL_VALUE
45
+ @procid = NIL_VALUE
46
+ @msgid = NIL_VALUE
47
+ end
48
+
49
+ def timestamp=(val)
50
+ raise ArgumentError.new("Must provide Time object value instead: #{val.inspect}") unless val.is_a?(Time)
51
+ @timestamp = val
52
+ end
53
+
54
+ def facility=(val)
55
+ raise ArgumentError.new("Invalid facility value: #{val.inspect}") unless FACILITIES.key?(val)
56
+ @facility = val
57
+ end
58
+
59
+ def severity=(val)
60
+ raise ArgumentError.new("Invalid severity value: #{val.inspect}") unless SEVERITIES.key?(val)
61
+ @severity = val
62
+ end
63
+
64
+ # Priority value is calculated by first multiplying the Facility
65
+ # number by 8 and then adding the numerical value of the Severity.
66
+ def pri
67
+ FACILITIES[facility] * 8 + SEVERITIES[severity]
68
+ end
69
+
70
+ def assemble
71
+ [
72
+ "<#{pri}>#{timestamp.to_datetime.strftime("%b %d %H:%M:%S")}",
73
+ hostname,
74
+ app_name
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
+ return NIL_VALUE unless id
96
+ parts = [id]
97
+ data.each do |k, v|
98
+ # Characters ", ] and \ must be escaped to prevent any parsing errors
99
+ v = v.gsub(/(\"|\]|\\)/) { |match| '\\' + match }
100
+ parts << "#{k}=\"#{v}\""
101
+ end
102
+ "[#{parts.join(' ')}]"
103
+ end
104
+
105
+ def to_s
106
+ assemble
107
+ end
108
+ end
109
+
110
+ # Message represents full message that can be sent to syslog
111
+ class Message
112
+ attr_accessor :structured_data, :msg
113
+ attr_writer :header
114
+
115
+ def initialize
116
+ @msg = ''
117
+ @structured_data = []
118
+ end
119
+
120
+ def header
121
+ @header ||= Header.new
122
+ end
123
+
124
+ def assemble
125
+ # Start with header
126
+ out = [header.to_s]
127
+ # Add message
128
+ out << msg if msg.length > 0
129
+ # Message must end with new line delimiter
130
+ out.join(' ') + "\n"
131
+ end
132
+
133
+ def to_s
134
+ assemble
135
+ end
136
+ end
137
+ end