gelf_redux 3.1.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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/CHANGELOG +54 -0
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/benchmarks/notifier.rb +39 -0
- data/gelf.gemspec +70 -0
- data/lib/gelf/logger.rb +64 -0
- data/lib/gelf/notifier.rb +280 -0
- data/lib/gelf/severity.rb +50 -0
- data/lib/gelf/transport/tcp.rb +79 -0
- data/lib/gelf/transport/tcp_tls.rb +129 -0
- data/lib/gelf/transport/udp.rb +41 -0
- data/lib/gelf.rb +16 -0
- data/test/helper.rb +11 -0
- data/test/test_logger.rb +247 -0
- data/test/test_notifier.rb +317 -0
- data/test/test_ruby_sender.rb +28 -0
- data/test/test_severity.rb +9 -0
- metadata +142 -0
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'gelf/transport/udp'
|
2
|
+
require 'gelf/transport/tcp'
|
3
|
+
require 'gelf/transport/tcp_tls'
|
4
|
+
|
5
|
+
# replace JSON and #to_json with Yajl if available
|
6
|
+
begin
|
7
|
+
require 'yajl/json_gem'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
module GELF
|
12
|
+
# Graylog2 notifier.
|
13
|
+
class Notifier
|
14
|
+
# Maximum number of GELF chunks as per GELF spec
|
15
|
+
MAX_CHUNKS = 128
|
16
|
+
MAX_CHUNK_SIZE_WAN = 1420
|
17
|
+
MAX_CHUNK_SIZE_LAN = 8154
|
18
|
+
|
19
|
+
attr_accessor :enabled, :collect_file_and_line, :rescue_network_errors
|
20
|
+
attr_reader :max_chunk_size, :level, :default_options, :level_mapping
|
21
|
+
|
22
|
+
# +host+ and +port+ are host/ip and port of graylog2-server.
|
23
|
+
# +max_size+ is passed to max_chunk_size=.
|
24
|
+
# +default_options+ is used in notify!
|
25
|
+
def initialize(host = 'localhost', port = 12201, max_size = 'WAN', default_options = {})
|
26
|
+
@enabled = true
|
27
|
+
@collect_file_and_line = true
|
28
|
+
@random = Random.new
|
29
|
+
|
30
|
+
self.level = GELF::DEBUG
|
31
|
+
self.max_chunk_size = max_size
|
32
|
+
self.rescue_network_errors = false
|
33
|
+
|
34
|
+
self.default_options = default_options.dup
|
35
|
+
self.default_options['version'] = SPEC_VERSION
|
36
|
+
self.default_options['host'] ||= Socket.gethostname
|
37
|
+
self.default_options['level'] ||= GELF::UNKNOWN
|
38
|
+
self.default_options['facility'] ||= 'gelf-rb'
|
39
|
+
self.default_options['protocol'] ||= GELF::Protocol::UDP
|
40
|
+
|
41
|
+
self.level_mapping = :logger
|
42
|
+
@sender = create_sender(host, port)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get a list of receivers.
|
46
|
+
# notifier.addresses # => [['localhost', 12201], ['localhost', 12202]]
|
47
|
+
def addresses
|
48
|
+
@sender.addresses
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set a list of receivers.
|
52
|
+
# notifier.addresses = [['localhost', 12201], ['localhost', 12202]]
|
53
|
+
def addresses=(addrs)
|
54
|
+
@sender.addresses = addrs
|
55
|
+
end
|
56
|
+
|
57
|
+
# +size+ may be a number of bytes, 'WAN' (1420 bytes) or 'LAN' (8154).
|
58
|
+
# Default (safe) value is 'WAN'.
|
59
|
+
def max_chunk_size=(size)
|
60
|
+
case size.to_s.downcase
|
61
|
+
when 'wan'
|
62
|
+
@max_chunk_size = MAX_CHUNK_SIZE_WAN
|
63
|
+
when 'lan'
|
64
|
+
@max_chunk_size = MAX_CHUNK_SIZE_LAN
|
65
|
+
else
|
66
|
+
@max_chunk_size = size.to_int
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def level=(new_level)
|
71
|
+
@level = if new_level.is_a?(Integer)
|
72
|
+
new_level
|
73
|
+
else
|
74
|
+
GELF.const_get(new_level.to_s.upcase)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_options=(options)
|
79
|
+
@default_options = self.class.stringify_keys(options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# +mapping+ may be a hash, 'logger' (GELF::LOGGER_MAPPING) or 'direct' (GELF::DIRECT_MAPPING).
|
83
|
+
# Default (compatible) value is 'logger'.
|
84
|
+
def level_mapping=(mapping)
|
85
|
+
case mapping.to_s.downcase
|
86
|
+
when 'logger'
|
87
|
+
@level_mapping = GELF::LOGGER_MAPPING
|
88
|
+
when 'direct'
|
89
|
+
@level_mapping = GELF::DIRECT_MAPPING
|
90
|
+
else
|
91
|
+
@level_mapping = mapping
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def disable
|
96
|
+
@enabled = false
|
97
|
+
end
|
98
|
+
|
99
|
+
def enable
|
100
|
+
@enabled = true
|
101
|
+
end
|
102
|
+
|
103
|
+
# Closes sender
|
104
|
+
def close
|
105
|
+
@sender.close
|
106
|
+
end
|
107
|
+
|
108
|
+
# Same as notify!, but rescues all exceptions (including +ArgumentError+)
|
109
|
+
# and sends them instead.
|
110
|
+
def notify(*args)
|
111
|
+
notify_with_level(nil, *args)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Sends message to Graylog2 server.
|
115
|
+
# +args+ can be:
|
116
|
+
# - hash-like object (any object which responds to +to_hash+, including +Hash+ instance):
|
117
|
+
# notify!(:short_message => 'All your rebase are belong to us', :user => 'AlekSi')
|
118
|
+
# - exception with optional hash-like object:
|
119
|
+
# notify!(SecurityError.new('ALARM!'), :trespasser => 'AlekSi')
|
120
|
+
# - string-like object (anything which responds to +to_s+) with optional hash-like object:
|
121
|
+
# notify!('Plain olde text message', :scribe => 'AlekSi')
|
122
|
+
# Resulted fields are merged with +default_options+, the latter will never overwrite the former.
|
123
|
+
# This method will raise +ArgumentError+ if arguments are wrong. Consider using notify instead.
|
124
|
+
def notify!(*args)
|
125
|
+
notify_with_level!(nil, *args)
|
126
|
+
end
|
127
|
+
|
128
|
+
GELF::Levels.constants.each do |const|
|
129
|
+
define_method(const.downcase) do |*args|
|
130
|
+
level = GELF.const_get(const)
|
131
|
+
notify_with_level(level, *args)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def create_sender(host, port)
|
138
|
+
addresses = [[host, port]]
|
139
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
140
|
+
if default_options.key?('tls')
|
141
|
+
tls_options = default_options.delete('tls')
|
142
|
+
GELF::Transport::TCPTLS.new(addresses, tls_options)
|
143
|
+
else
|
144
|
+
GELF::Transport::TCP.new(addresses)
|
145
|
+
end
|
146
|
+
else
|
147
|
+
GELF::Transport::UDP.new(addresses)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def notify_with_level(message_level, *args)
|
152
|
+
notify_with_level!(message_level, *args)
|
153
|
+
rescue SocketError, SystemCallError
|
154
|
+
raise unless rescue_network_errors
|
155
|
+
rescue Exception => exception
|
156
|
+
notify_with_level!(GELF::UNKNOWN, exception)
|
157
|
+
end
|
158
|
+
|
159
|
+
def notify_with_level!(message_level, *args)
|
160
|
+
return unless @enabled
|
161
|
+
hash = extract_hash(*args)
|
162
|
+
hash['level'] = message_level unless message_level.nil?
|
163
|
+
if hash['level'] >= level
|
164
|
+
if default_options['protocol'] == GELF::Protocol::TCP
|
165
|
+
validate_hash(hash)
|
166
|
+
@sender.send(hash.to_json + "\0")
|
167
|
+
else
|
168
|
+
@sender.send_datagrams(datagrams_from_hash(hash))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def extract_hash(object = nil, args = {})
|
174
|
+
primary_data = if object.respond_to?(:to_hash)
|
175
|
+
object.to_hash
|
176
|
+
elsif object.is_a?(Exception)
|
177
|
+
args['level'] ||= GELF::ERROR
|
178
|
+
self.class.extract_hash_from_exception(object)
|
179
|
+
else
|
180
|
+
args['level'] ||= GELF::INFO
|
181
|
+
{ 'short_message' => object.to_s }
|
182
|
+
end
|
183
|
+
|
184
|
+
hash = default_options.merge(self.class.stringify_keys(args.merge(primary_data)))
|
185
|
+
convert_hoptoad_keys_to_graylog2(hash)
|
186
|
+
set_file_and_line(hash) if @collect_file_and_line
|
187
|
+
set_timestamp(hash)
|
188
|
+
check_presence_of_mandatory_attributes(hash)
|
189
|
+
hash
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.extract_hash_from_exception(exception)
|
193
|
+
bt = exception.backtrace || ["Backtrace is not available."]
|
194
|
+
{
|
195
|
+
'short_message' => "#{exception.class}: #{exception.message}",
|
196
|
+
'full_message' => "Backtrace:\n" + bt.join("\n")
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
# Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
|
201
|
+
def convert_hoptoad_keys_to_graylog2(hash)
|
202
|
+
if hash['short_message'].to_s.empty?
|
203
|
+
if hash.has_key?('error_class') && hash.has_key?('error_message')
|
204
|
+
hash['short_message'] = hash.delete('error_class') + ': ' + hash.delete('error_message')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
CALLER_REGEXP = /^(.*):(\d+).*/
|
210
|
+
LIB_GELF_PATTERN = File.join('lib', 'gelf')
|
211
|
+
|
212
|
+
def set_file_and_line(hash)
|
213
|
+
stack = caller
|
214
|
+
frame = stack.find { |f| !f.include?(LIB_GELF_PATTERN) }
|
215
|
+
match = CALLER_REGEXP.match(frame)
|
216
|
+
hash['file'] = match[1]
|
217
|
+
hash['line'] = match[2].to_i
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_timestamp(hash)
|
221
|
+
hash['timestamp'] = Time.now.utc.to_f if hash['timestamp'].nil?
|
222
|
+
end
|
223
|
+
|
224
|
+
def check_presence_of_mandatory_attributes(hash)
|
225
|
+
%w(version short_message host).each do |attribute|
|
226
|
+
if hash[attribute].to_s.empty?
|
227
|
+
raise ArgumentError.new("#{attribute} is missing. Options version, short_message and host must be set.")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def datagrams_from_hash(hash)
|
233
|
+
data = serialize_hash(hash)
|
234
|
+
datagrams = []
|
235
|
+
|
236
|
+
# Maximum total size is 8192 byte for UDP datagram. Split to chunks if bigger. (GELF v1.0 supports chunking)
|
237
|
+
if data.count > @max_chunk_size
|
238
|
+
id = @random.bytes(8)
|
239
|
+
msg_id = Digest::MD5.digest("#{Time.now.to_f}-#{id}")[0, 8]
|
240
|
+
num, count = 0, (data.count.to_f / @max_chunk_size).ceil
|
241
|
+
if count > MAX_CHUNKS
|
242
|
+
raise ArgumentError, "Data too big (#{data.count} bytes), would create more than #{MAX_CHUNKS} chunks!"
|
243
|
+
end
|
244
|
+
data.each_slice(@max_chunk_size) do |slice|
|
245
|
+
datagrams << "\x1e\x0f" + msg_id + [num, count, *slice].pack('C*')
|
246
|
+
num += 1
|
247
|
+
end
|
248
|
+
else
|
249
|
+
datagrams << data.to_a.pack('C*')
|
250
|
+
end
|
251
|
+
|
252
|
+
datagrams
|
253
|
+
end
|
254
|
+
|
255
|
+
def validate_hash(hash)
|
256
|
+
raise ArgumentError.new("Hash is empty.") if hash.nil? || hash.empty?
|
257
|
+
hash['level'] = @level_mapping[hash['level']]
|
258
|
+
end
|
259
|
+
|
260
|
+
def serialize_hash(hash)
|
261
|
+
validate_hash(hash)
|
262
|
+
|
263
|
+
Zlib::Deflate.deflate(hash.to_json).bytes
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.stringify_keys(data)
|
267
|
+
return data unless data.is_a? Hash
|
268
|
+
|
269
|
+
data.each_with_object({}) do |(key, value), obj|
|
270
|
+
key_s = key.to_s
|
271
|
+
|
272
|
+
if (key != key_s) && data.key?(key_s)
|
273
|
+
raise ArgumentError, "Both #{key.inspect} and #{key_s} are present."
|
274
|
+
end
|
275
|
+
|
276
|
+
obj[key_s] = value
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module GELF
|
2
|
+
# There are two things you should know about log levels/severity:
|
3
|
+
# - syslog defines levels from 0 (Emergency) to 7 (Debug).
|
4
|
+
# 0 (Emergency) and 1 (Alert) levels are reserved for OS kernel.
|
5
|
+
# - Ruby default Logger defines levels from 0 (DEBUG) to 4 (FATAL) and 5 (UNKNOWN).
|
6
|
+
# Note that order is inverted.
|
7
|
+
# For compatibility we define our constants as Ruby Logger, and convert values before
|
8
|
+
# generating GELF message, using defined mapping.
|
9
|
+
|
10
|
+
module Levels
|
11
|
+
DEBUG = 0
|
12
|
+
INFO = 1
|
13
|
+
WARN = 2
|
14
|
+
ERROR = 3
|
15
|
+
FATAL = 4
|
16
|
+
UNKNOWN = 5
|
17
|
+
# Additional native syslog severities. These will work in direct mapping mode
|
18
|
+
# only, for compatibility with syslog sources unrelated to Logger.
|
19
|
+
EMERGENCY = 10
|
20
|
+
ALERT = 11
|
21
|
+
CRITICAL = 12
|
22
|
+
WARNING = 14
|
23
|
+
NOTICE = 15
|
24
|
+
INFORMATIONAL = 16
|
25
|
+
end
|
26
|
+
|
27
|
+
include Levels
|
28
|
+
|
29
|
+
# Maps Ruby Logger levels to syslog levels as SyslogLogger and syslogger gems. This one is default.
|
30
|
+
LOGGER_MAPPING = {DEBUG => 7, # Debug
|
31
|
+
INFO => 6, # Informational
|
32
|
+
WARN => 5, # Notice
|
33
|
+
ERROR => 4, # Warning
|
34
|
+
FATAL => 3, # Error
|
35
|
+
UNKNOWN => 1} # Alert – shouldn't be used
|
36
|
+
|
37
|
+
# Maps Syslog or Ruby Logger levels directly to standard syslog numerical severities.
|
38
|
+
DIRECT_MAPPING = {DEBUG => 7, # Debug
|
39
|
+
INFORMATIONAL => 6, # Informational (syslog source)
|
40
|
+
INFO => 6, # Informational (Logger source)
|
41
|
+
NOTICE => 5, # Notice
|
42
|
+
WARNING => 4, # Warning (syslog source)
|
43
|
+
WARN => 4, # Warning (Logger source)
|
44
|
+
ERROR => 3, # Error
|
45
|
+
CRITICAL => 2, # Critical (syslog source)
|
46
|
+
FATAL => 2, # Critical (Logger source)
|
47
|
+
ALERT => 1, # Alert (syslog source)
|
48
|
+
UNKNOWN => 1, # Alert - shouldn't be used (Logger source)
|
49
|
+
EMERGENCY => 0} # Emergency (syslog source)
|
50
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class TCP
|
4
|
+
attr_reader :addresses
|
5
|
+
|
6
|
+
# `addresses` Array of [host, port] pairs
|
7
|
+
def initialize(addresses)
|
8
|
+
@sockets = []
|
9
|
+
self.addresses = addresses
|
10
|
+
end
|
11
|
+
|
12
|
+
def addresses=(addresses)
|
13
|
+
@addresses = addresses.dup.freeze.tap do |addrs|
|
14
|
+
@sockets.each(&:close)
|
15
|
+
@sockets = addrs.map { |peer| connect(*peer) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def send(message)
|
20
|
+
return if @addresses.empty?
|
21
|
+
loop do
|
22
|
+
connected = @sockets.reject(&:closed?)
|
23
|
+
reconnect_all if connected.empty?
|
24
|
+
break if write_any(connected, message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def connect(host, port)
|
31
|
+
socket_class.new(host, port)
|
32
|
+
end
|
33
|
+
|
34
|
+
def reconnect_all
|
35
|
+
@sockets = @sockets.each_with_index.map do |old_socket, index|
|
36
|
+
old_socket.closed? ? connect(*@addresses[index]) : old_socket
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def socket_class
|
41
|
+
if defined?(Celluloid::IO::TCPSocket)
|
42
|
+
Celluloid::IO::TCPSocket
|
43
|
+
else
|
44
|
+
::TCPSocket
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_any(sockets, message)
|
49
|
+
sockets.shuffle.each do |socket|
|
50
|
+
return true if write_socket(socket, message)
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_socket(socket, message)
|
56
|
+
unsafe_write_socket(socket, message)
|
57
|
+
rescue IOError, SystemCallError
|
58
|
+
socket.close unless socket.closed?
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def unsafe_write_socket(socket, message)
|
63
|
+
r,w = IO.select([socket], [socket])
|
64
|
+
# Read everything first
|
65
|
+
while r.any? do
|
66
|
+
# don't expect any reads, but a readable socket might
|
67
|
+
# mean the remote end closed, so read it and throw it away.
|
68
|
+
# we'll get an EOFError if it happens.
|
69
|
+
socket.sysread(16384)
|
70
|
+
r = IO.select([socket])
|
71
|
+
end
|
72
|
+
|
73
|
+
# Now send the payload
|
74
|
+
return false unless w.any?
|
75
|
+
return socket.syswrite(message) > 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module GELF
|
4
|
+
module Transport
|
5
|
+
# Provides encryption capabilities for TCP connections
|
6
|
+
class TCPTLS < TCP
|
7
|
+
# Supported tls_options:
|
8
|
+
# 'no_default_ca' [Boolean] prevents OpenSSL from using the systems CA store.
|
9
|
+
# 'version' [Symbol] any of :TLSv1, :TLSv1_1, :TLSv1_2 (default)
|
10
|
+
# 'ca' [String] the path to a custom CA store
|
11
|
+
# 'cert' [String, IO] the client certificate file
|
12
|
+
# 'key' [String, IO] the key for the client certificate
|
13
|
+
# 'all_ciphers' [Boolean] allows any ciphers to be used, may be insecure
|
14
|
+
# 'rescue_ssl_errors' [Boolean] similar to rescue_network_errors in notifier.rb, allows SSL exceptions to be raised
|
15
|
+
# 'no_verify' [Boolean] disable peer verification
|
16
|
+
|
17
|
+
attr_accessor :rescue_ssl_errors
|
18
|
+
|
19
|
+
def initialize(addresses, tls_options={})
|
20
|
+
@tls_options = tls_options
|
21
|
+
@rescue_ssl_errors = @tls_options['rescue_ssl_errors']
|
22
|
+
@rescue_ssl_errors if @rescue_ssl_errors.nil?
|
23
|
+
super(addresses)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def write_socket(socket, message)
|
29
|
+
super(socket, message)
|
30
|
+
rescue OpenSSL::SSL::SSLError
|
31
|
+
socket.close unless socket.closed?
|
32
|
+
raise unless rescue_ssl_errors
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect(host, port)
|
37
|
+
plain_socket = super(host, port)
|
38
|
+
start_tls(plain_socket)
|
39
|
+
rescue OpenSSL::SSL::SSLError
|
40
|
+
plain_socket.close unless plain_socket.closed?
|
41
|
+
raise unless rescue_ssl_errors
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initiates TLS communication on the socket
|
46
|
+
def start_tls(plain_socket)
|
47
|
+
ssl_socket_class.new(plain_socket, ssl_context).tap do |ssl_socket|
|
48
|
+
ssl_socket.sync_close = true
|
49
|
+
ssl_socket.connect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def ssl_socket_class
|
54
|
+
if defined?(Celluloid::IO::SSLSocket)
|
55
|
+
Celluloid::IO::SSLSocket
|
56
|
+
else
|
57
|
+
OpenSSL::SSL::SSLSocket
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ssl_context
|
62
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
63
|
+
ctx.cert_store = ssl_cert_store
|
64
|
+
ctx.ssl_version = tls_version
|
65
|
+
ctx.verify_mode = verify_mode
|
66
|
+
set_certificate_and_key(ctx)
|
67
|
+
restrict_ciphers(ctx) unless @tls_options['all_ciphers']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_certificate_and_key(context)
|
72
|
+
return unless @tls_options['cert'] && @tls_options['key']
|
73
|
+
context.cert = OpenSSL::X509::Certificate.new(resource(@tls_options['cert']))
|
74
|
+
context.key = OpenSSL::PKey::RSA.new(resource(@tls_options['key']))
|
75
|
+
end
|
76
|
+
|
77
|
+
# checks whether {resource} is a filename and tries to read it
|
78
|
+
# otherwise treats it as if it already contains certificate/key data
|
79
|
+
def resource(data)
|
80
|
+
if data.is_a?(String) && File.exist?(data)
|
81
|
+
File.read(data)
|
82
|
+
else
|
83
|
+
data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ciphers have to come from the CipherString class, specifically the _TXT_ constants here - https://github.com/jruby/jruby-openssl/blob/master/src/main/java/org/jruby/ext/openssl/CipherStrings.java#L47-L178
|
88
|
+
def restrict_ciphers(ctx)
|
89
|
+
# This CipherString is will allow a variety of 'currently' cryptographically secure ciphers,
|
90
|
+
# while also retaining a broad level of compatibility
|
91
|
+
ctx.ciphers = "TLSv1_2:TLSv1_1:TLSv1:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:!ADH:!IDEA:!3DES"
|
92
|
+
end
|
93
|
+
|
94
|
+
def verify_mode
|
95
|
+
@tls_options['no_verify'] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
96
|
+
end
|
97
|
+
|
98
|
+
# SSL v2&3 are insecure, forces at least TLS v1.0 and defaults to v1.2
|
99
|
+
def tls_version
|
100
|
+
if @tls_options.key?('version') &&
|
101
|
+
OpenSSL::SSL::SSLContext::METHODS.include?(@tls_options['version']) &&
|
102
|
+
@tls_options['version'] =~ /\ATLSv/
|
103
|
+
@tls_options['version']
|
104
|
+
else
|
105
|
+
:TLSv1_2
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ssl_cert_store
|
110
|
+
OpenSSL::X509::Store.new.tap do |store|
|
111
|
+
unless @tls_options['no_default_ca']
|
112
|
+
store.set_default_paths
|
113
|
+
end
|
114
|
+
|
115
|
+
if @tls_options.key?('ca')
|
116
|
+
ca = @tls_options['ca']
|
117
|
+
if File.directory?(ca)
|
118
|
+
store.add_path(@tls_options['ca'])
|
119
|
+
elsif File.file?(ca)
|
120
|
+
store.add_file(ca)
|
121
|
+
else
|
122
|
+
$stderr.puts "No directory or file: #{ca}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module GELF
|
2
|
+
module Transport
|
3
|
+
class UDP
|
4
|
+
attr_accessor :addresses
|
5
|
+
|
6
|
+
def initialize(addresses)
|
7
|
+
@addresses = addresses
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_datagrams(datagrams)
|
11
|
+
socket = get_socket
|
12
|
+
idx = get_address_index
|
13
|
+
|
14
|
+
host, port = @addresses[idx]
|
15
|
+
set_address_index((idx + 1) % @addresses.length)
|
16
|
+
datagrams.each do |datagram|
|
17
|
+
socket.send(datagram, 0, host, port)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
socket = get_socket
|
23
|
+
socket.close if socket
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def get_socket
|
29
|
+
Thread.current[:gelf_udp_socket] ||= UDPSocket.open
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_address_index
|
33
|
+
Thread.current[:gelf_udp_address_idx] ||= 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_address_index(value)
|
37
|
+
Thread.current[:gelf_udp_address_idx] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/gelf.rb
ADDED
data/test/helper.rb
ADDED