fluent-plugin-d 0.0.1

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