gelf 1.0.2 → 1.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +19 -4
- data/LICENSE +1 -1
- data/README.rdoc +1 -1
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/benchmarks/notifier.rb +32 -0
- data/gelf.gemspec +16 -5
- data/lib/gelf.rb +3 -0
- data/lib/gelf/deprecations.rb +7 -7
- data/lib/gelf/em_sender.rb +10 -0
- data/lib/gelf/logger.rb +50 -0
- data/lib/gelf/notifier.rb +106 -55
- data/lib/gelf/ruby_sender.rb +15 -0
- data/lib/gelf/severity.rb +28 -0
- data/test/helper.rb +5 -0
- data/test/test_logger.rb +135 -0
- data/test/test_notifier.rb +73 -57
- data/test/test_ruby_sender.rb +21 -0
- data/test/test_severity.rb +9 -0
- metadata +27 -7
data/CHANGELOG
CHANGED
@@ -1,6 +1,21 @@
|
|
1
|
-
1.0
|
2
|
-
|
1
|
+
1.1.0 (not yet released), 2010-11-xx:
|
2
|
+
+ Notifier#default_options;
|
3
|
+
+ severity (level) threshold;
|
4
|
+
+ wrappers for GELF::Notifier#notify with severity:
|
5
|
+
- GELF::Notifier.debug
|
6
|
+
- GELF::Notifier.info
|
7
|
+
- GELF::Notifier.warn
|
8
|
+
- GELF::Notifier.error
|
9
|
+
- GELF::Notifier.fatal
|
10
|
+
- GELF::Notifier.unknown
|
11
|
+
+ full compatibility with Ruby Logger and other loggers:
|
12
|
+
- GELF::Logger#fatal { "Argument 'foo' not given." }
|
13
|
+
- GELF::Logger#error "Argument #{ @foo } mismatch."
|
14
|
+
- GELF::Logger#info('initialize') { "Initializing..." }
|
15
|
+
- GELF::Logger#add(GELF::FATAL) { 'Fatal error!' }
|
16
|
+
- GELF::Logger#close
|
17
|
+
- GELF::Logger#level = GELF::INFO
|
3
18
|
|
4
19
|
1.0.0, 2010-11-10:
|
5
|
-
|
6
|
-
|
20
|
+
+ initial stable version;
|
21
|
+
* deprecated Gelf class is still there.
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'rake'
|
3
3
|
|
4
4
|
begin
|
5
|
-
gem 'jeweler', '1.4.0'
|
5
|
+
gem 'jeweler', '~> 1.4.0' # https://github.com/technicalpickles/jeweler/issues/issue/150
|
6
6
|
require 'jeweler'
|
7
7
|
Jeweler::Tasks.new do |gem|
|
8
8
|
gem.name = "gelf"
|
@@ -42,6 +42,11 @@ rescue LoadError
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
begin
|
46
|
+
require 'metric_fu'
|
47
|
+
rescue LoadError
|
48
|
+
end
|
49
|
+
|
45
50
|
task :test => :check_dependencies
|
46
51
|
|
47
52
|
task :default => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.1.0.beta1
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'gelf'
|
9
|
+
|
10
|
+
srand(1)
|
11
|
+
RANDOM_DATA = ('A'..'z').to_a
|
12
|
+
k1_message = (0..1024).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join
|
13
|
+
|
14
|
+
TARGET_HOST = 'localhost'
|
15
|
+
TARGET_PORT = 12201
|
16
|
+
DEFAULT_OPTIONS = { 'host' => 'localhost' }
|
17
|
+
TIMES = 5000
|
18
|
+
|
19
|
+
SHORT_HASH = { 'short_message' => 'message' }
|
20
|
+
LONG_HASH = { 'short_message' => 'short message', 'long_message' => k1_message, 'user_id' => rand(10000)}
|
21
|
+
|
22
|
+
|
23
|
+
notifier_lan = GELF::Notifier.new(TARGET_HOST, TARGET_PORT, 'LAN', DEFAULT_OPTIONS)
|
24
|
+
notifier_wan = GELF::Notifier.new(TARGET_HOST, TARGET_PORT, 'WAN', DEFAULT_OPTIONS)
|
25
|
+
|
26
|
+
puts "Sending #{TIMES} notifications..."
|
27
|
+
tms = Benchmark.bmbm do |b|
|
28
|
+
b.report('lan, short data') { TIMES.times { notifier_lan.notify!(SHORT_HASH) } }
|
29
|
+
b.report('wan, short data') { TIMES.times { notifier_wan.notify!(SHORT_HASH) } }
|
30
|
+
b.report('lan, long data') { TIMES.times { notifier_lan.notify!(LONG_HASH) } }
|
31
|
+
b.report('wan, long data') { TIMES.times { notifier_wan.notify!(LONG_HASH) } }
|
32
|
+
end
|
data/gelf.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{gelf}
|
8
|
-
s.version = "1.0.
|
8
|
+
s.version = "1.1.0.beta1"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Alexey Palazhchenko", "Lennart Koopmann"]
|
12
|
-
s.date = %q{2010-11-
|
12
|
+
s.date = %q{2010-11-25}
|
13
13
|
s.description = %q{Suports plain-text, GELF messages and exceptions.}
|
14
14
|
s.email = %q{lennart@socketfeed.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -23,13 +23,21 @@ Gem::Specification.new do |s|
|
|
23
23
|
"README.rdoc",
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
|
+
"benchmarks/notifier.rb",
|
26
27
|
"gelf.gemspec",
|
27
28
|
"lib/gelf.rb",
|
28
29
|
"lib/gelf/deprecations.rb",
|
30
|
+
"lib/gelf/em_sender.rb",
|
31
|
+
"lib/gelf/logger.rb",
|
29
32
|
"lib/gelf/notifier.rb",
|
33
|
+
"lib/gelf/ruby_sender.rb",
|
34
|
+
"lib/gelf/severity.rb",
|
30
35
|
"test/helper.rb",
|
31
36
|
"test/test_deprecations.rb",
|
32
|
-
"test/
|
37
|
+
"test/test_logger.rb",
|
38
|
+
"test/test_notifier.rb",
|
39
|
+
"test/test_ruby_sender.rb",
|
40
|
+
"test/test_severity.rb"
|
33
41
|
]
|
34
42
|
s.homepage = %q{http://github.com/Graylog2/gelf-rb}
|
35
43
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -39,7 +47,10 @@ Gem::Specification.new do |s|
|
|
39
47
|
s.test_files = [
|
40
48
|
"test/helper.rb",
|
41
49
|
"test/test_deprecations.rb",
|
42
|
-
"test/
|
50
|
+
"test/test_logger.rb",
|
51
|
+
"test/test_notifier.rb",
|
52
|
+
"test/test_ruby_sender.rb",
|
53
|
+
"test/test_severity.rb"
|
43
54
|
]
|
44
55
|
|
45
56
|
if s.respond_to? :specification_version then
|
data/lib/gelf.rb
CHANGED
data/lib/gelf/deprecations.rb
CHANGED
@@ -19,15 +19,15 @@ class Gelf
|
|
19
19
|
@notifier.notify(@message)
|
20
20
|
end
|
21
21
|
|
22
|
-
[:short_message, :full_message, :level, :host, :line, :file].each do |
|
23
|
-
define_method
|
24
|
-
deprecate("GELF::Message##{
|
25
|
-
@message[
|
22
|
+
[:short_message, :full_message, :level, :host, :line, :file].each do |attribute|
|
23
|
+
define_method attribute do
|
24
|
+
deprecate("GELF::Message##{attribute}")
|
25
|
+
@message[attribute]
|
26
26
|
end
|
27
27
|
|
28
|
-
define_method "#{
|
29
|
-
deprecate("GELF::Message##{
|
30
|
-
@message[
|
28
|
+
define_method "#{attribute}=" do |value|
|
29
|
+
deprecate("GELF::Message##{attribute} = value")
|
30
|
+
@message[attribute] = value
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/lib/gelf/logger.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module GELF
|
2
|
+
# Methods for compatibility with Ruby Logger.
|
3
|
+
module LoggerCompatibility
|
4
|
+
# Does nothing.
|
5
|
+
def close
|
6
|
+
end
|
7
|
+
|
8
|
+
# Use it like Logger#add… or better not to use at all.
|
9
|
+
def add(level, *args)
|
10
|
+
raise ArgumentError.new('Wrong arguments.') unless (0..2).include?(args.count)
|
11
|
+
|
12
|
+
# Ruby Logger's author is a maniac.
|
13
|
+
message, facility = if args.count == 2
|
14
|
+
[args[0], args[1]]
|
15
|
+
elsif args.count == 0
|
16
|
+
[yield, nil]
|
17
|
+
elsif block_given?
|
18
|
+
[yield, args[0]]
|
19
|
+
else
|
20
|
+
[args[0], nil]
|
21
|
+
end
|
22
|
+
|
23
|
+
hash = {'short_message' => message, 'facility' => facility}
|
24
|
+
hash.merge!(self.class.extract_hash_from_exception(message)) if message.is_a?(Exception)
|
25
|
+
notify_with_level(level, hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
GELF::Levels.constants.each do |const|
|
29
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
30
|
+
def #{const.downcase}(*args) # def debug(*args)
|
31
|
+
args.unshift(yield) if block_given? # args.unshift(yield) if block_given?
|
32
|
+
add(GELF::#{const}, *args) # add(GELF::DEBUG, *args)
|
33
|
+
end # end
|
34
|
+
|
35
|
+
def #{const.downcase}? # def debug?
|
36
|
+
GELF::#{const} >= level # GELF::DEBUG >= level
|
37
|
+
end # end
|
38
|
+
EOT
|
39
|
+
end
|
40
|
+
|
41
|
+
def <<(message)
|
42
|
+
notify('short_message' => message, 'level' => GELF::UNKNOWN)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Graylog2 notifier, compatible with Ruby Logger.
|
47
|
+
class Logger < Notifier
|
48
|
+
include LoggerCompatibility
|
49
|
+
end
|
50
|
+
end
|
data/lib/gelf/notifier.rb
CHANGED
@@ -1,117 +1,168 @@
|
|
1
1
|
module GELF
|
2
|
+
# Graylog2 notifier.
|
2
3
|
class Notifier
|
3
|
-
|
4
|
+
@last_chunk_id = 0
|
5
|
+
class << self
|
6
|
+
attr_accessor :last_chunk_id
|
7
|
+
end
|
4
8
|
|
5
|
-
attr_accessor :host, :port
|
6
|
-
attr_reader :max_chunk_size
|
9
|
+
attr_accessor :host, :port, :default_options
|
10
|
+
attr_reader :max_chunk_size, :level
|
7
11
|
|
8
12
|
# +host+ and +port+ are host/ip and port of graylog2-server.
|
9
|
-
|
10
|
-
|
13
|
+
# +max_size+ is passed to max_chunk_size=.
|
14
|
+
# +default_options+ is used in notify!
|
15
|
+
def initialize(host = 'localhost', port = 12201, max_size = 'WAN', default_options = {})
|
16
|
+
self.level = GELF::DEBUG
|
17
|
+
|
18
|
+
self.host, self.port, self.max_chunk_size = host, port, max_size
|
19
|
+
|
20
|
+
@default_options = {}
|
21
|
+
self.default_options = default_options
|
22
|
+
self.default_options['host'] ||= Socket.gethostname
|
23
|
+
self.default_options['level'] ||= GELF::DEBUG
|
24
|
+
self.default_options['facility'] ||= 'gelf-rb'
|
25
|
+
|
26
|
+
@sender = RubySender.new(host, port)
|
11
27
|
end
|
12
28
|
|
13
29
|
# +size+ may be a number of bytes, 'WAN' (1420 bytes) or 'LAN' (8154).
|
14
30
|
# Default (safe) value is 'WAN'.
|
15
31
|
def max_chunk_size=(size)
|
16
|
-
|
17
|
-
if
|
32
|
+
size_s = size.to_s.downcase
|
33
|
+
if size_s == 'wan'
|
18
34
|
@max_chunk_size = 1420
|
19
|
-
elsif
|
35
|
+
elsif size_s == 'lan'
|
20
36
|
@max_chunk_size = 8154
|
21
37
|
else
|
22
38
|
@max_chunk_size = size.to_int
|
23
39
|
end
|
24
40
|
end
|
25
41
|
|
42
|
+
def level=(new_level)
|
43
|
+
@level = new_level
|
44
|
+
end
|
45
|
+
|
26
46
|
# Same as notify!, but rescues all exceptions (including +ArgumentError+)
|
27
47
|
# and sends them instead.
|
28
48
|
def notify(*args)
|
29
|
-
|
30
|
-
rescue Exception => e
|
31
|
-
notify!(e)
|
49
|
+
notify_with_level(nil, *args)
|
32
50
|
end
|
33
51
|
|
34
52
|
# Sends message to Graylog2 server.
|
35
53
|
# +args+ can be:
|
36
|
-
# - hash-like object (any object which responds to +to_hash+, including +Hash+ instance)
|
54
|
+
# - hash-like object (any object which responds to +to_hash+, including +Hash+ instance):
|
37
55
|
# notify!(:short_message => 'All your rebase are belong to us', :user => 'AlekSi')
|
38
|
-
# - exception with optional hash-like object
|
56
|
+
# - exception with optional hash-like object:
|
39
57
|
# notify!(SecurityError.new('ALARM!'), :trespasser => 'AlekSi')
|
40
|
-
# - string-like object (anything which responds to +to_s+) with optional hash-like object
|
58
|
+
# - string-like object (anything which responds to +to_s+) with optional hash-like object:
|
41
59
|
# notify!('Plain olde text message', :scribe => 'AlekSi')
|
60
|
+
# Resulted fields are merged with +default_options+, the latter will never overwrite the former.
|
42
61
|
# This method will raise +ArgumentError+ if arguments are wrong. Consider using notify instead.
|
43
62
|
def notify!(*args)
|
44
|
-
|
63
|
+
notify_with_level!(nil, *args)
|
64
|
+
end
|
65
|
+
|
66
|
+
GELF::Levels.constants.each do |const|
|
67
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
68
|
+
def #{const.downcase}(*args) # def debug(*args)
|
69
|
+
notify_with_level(GELF::#{const}, *args) # notify_with_level(GELF::DEBUG, *args)
|
70
|
+
end # end
|
71
|
+
EOT
|
45
72
|
end
|
46
73
|
|
47
74
|
private
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
75
|
+
def notify_with_level(message_level, *args)
|
76
|
+
notify_with_level!(message_level, *args)
|
77
|
+
rescue Exception => exception
|
78
|
+
notify_with_level!(GELF::UNKNOWN, exception)
|
79
|
+
end
|
80
|
+
|
81
|
+
def notify_with_level!(message_level, *args)
|
82
|
+
extract_hash(*args)
|
83
|
+
@hash['level'] = message_level unless message_level.nil?
|
84
|
+
if @hash['level'] >= level
|
85
|
+
@sender.send_datagrams(datagrams_from_hash)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def extract_hash(object = nil, args = {})
|
90
|
+
primary_data = if object.respond_to?(:to_hash)
|
91
|
+
object.to_hash
|
92
|
+
elsif object.is_a?(Exception)
|
93
|
+
args['level'] ||= GELF::ERROR
|
94
|
+
self.class.extract_hash_from_exception(object)
|
55
95
|
else
|
56
|
-
|
96
|
+
args['level'] ||= GELF::INFO
|
97
|
+
{ 'short_message' => object.to_s }
|
57
98
|
end
|
58
99
|
|
59
|
-
hash = args.merge(primary_data)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
100
|
+
@hash = default_options.merge(args.merge(primary_data))
|
101
|
+
stringify_hash_keys
|
102
|
+
convert_hoptoad_keys_to_graylog2
|
103
|
+
check_presence_of_mandatory_attributes
|
104
|
+
@hash
|
105
|
+
end
|
66
106
|
|
67
|
-
|
107
|
+
def self.extract_hash_from_exception(exception)
|
108
|
+
bt = exception.backtrace || ["Backtrace is not available."]
|
109
|
+
{ 'short_message' => "#{exception.class}: #{exception.message}", 'full_message' => "Backtrace:\n" + bt.join("\n") }
|
110
|
+
end
|
68
111
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
hash
|
74
|
-
hash.delete('
|
112
|
+
# Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
|
113
|
+
def convert_hoptoad_keys_to_graylog2
|
114
|
+
if @hash['short_message'].to_s.empty?
|
115
|
+
if @hash.has_key?('error_class') && @hash.has_key?('error_message')
|
116
|
+
@hash['short_message'] = "#{@hash['error_class']}: #{@hash['error_message']}"
|
117
|
+
@hash.delete('error_class')
|
118
|
+
@hash.delete('error_message')
|
75
119
|
end
|
76
120
|
end
|
121
|
+
end
|
77
122
|
|
78
|
-
|
79
|
-
|
80
|
-
|
123
|
+
def check_presence_of_mandatory_attributes
|
124
|
+
%w(short_message host).each do |attribute|
|
125
|
+
if @hash[attribute].to_s.empty?
|
126
|
+
raise ArgumentError.new("Options short_message and host must be set.")
|
81
127
|
end
|
82
128
|
end
|
83
|
-
|
84
|
-
hash
|
85
129
|
end
|
86
130
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
131
|
+
def datagrams_from_hash
|
132
|
+
raise ArgumentError.new("Hash is empty.") if @hash.nil? || @hash.empty?
|
133
|
+
|
134
|
+
@hash['level'] = GELF::LEVELS_MAPPING[@hash['level']]
|
135
|
+
|
136
|
+
data = Zlib::Deflate.deflate(@hash.to_json).bytes
|
90
137
|
datagrams = []
|
91
138
|
|
92
139
|
# Maximum total size is 8192 byte for UDP datagram. Split to chunks if bigger. (GELFv2 supports chunking)
|
93
140
|
if data.count > @max_chunk_size
|
94
|
-
|
95
|
-
msg_id = Digest::SHA256.digest("#{Time.now.to_f}-#{
|
96
|
-
|
141
|
+
id = self.class.last_chunk_id += 1
|
142
|
+
msg_id = Digest::SHA256.digest("#{Time.now.to_f}-#{id}")
|
143
|
+
num, count = 0, (data.count.to_f / @max_chunk_size).ceil
|
97
144
|
data.each_slice(@max_chunk_size) do |slice|
|
98
|
-
datagrams << chunk_data(slice, msg_id,
|
99
|
-
|
145
|
+
datagrams << self.class.chunk_data(slice, msg_id, num, count)
|
146
|
+
num += 1
|
100
147
|
end
|
101
148
|
else
|
102
149
|
datagrams = [data.map(&:chr).join]
|
103
150
|
end
|
104
151
|
|
105
|
-
datagrams.each { |d| sock.send(d, 0, @host, @port) }
|
106
152
|
datagrams
|
107
153
|
end
|
108
154
|
|
109
|
-
def chunk_data(data, msg_id,
|
110
|
-
|
155
|
+
def self.chunk_data(data, msg_id, num, count)
|
156
|
+
# [30, 15].pack('CC') => "\036\017"
|
157
|
+
return "\036\017" + msg_id + [num, count].pack('nn') + data.map(&:chr).join
|
111
158
|
end
|
112
159
|
|
113
|
-
def
|
114
|
-
@
|
160
|
+
def stringify_hash_keys
|
161
|
+
@hash.keys.each do |key|
|
162
|
+
value, key_s = @hash.delete(key), key.to_s
|
163
|
+
raise ArgumentError.new("Both #{key.inspect} and #{key_s} are present.") if @hash.has_key?(key_s)
|
164
|
+
@hash[key_s] = value
|
165
|
+
end
|
115
166
|
end
|
116
167
|
end
|
117
168
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module GELF
|
2
|
+
# Plain Ruby sender.
|
3
|
+
class RubySender
|
4
|
+
def initialize(host, port)
|
5
|
+
@host, @port = host, port
|
6
|
+
@socket = UDPSocket.open
|
7
|
+
end
|
8
|
+
|
9
|
+
def send_datagrams(datagrams)
|
10
|
+
datagrams.each do |datagram|
|
11
|
+
@socket.send(datagram, 0, @host, @port)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module GELF
|
2
|
+
# There are two things you should know about log leves/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.
|
9
|
+
|
10
|
+
module Levels
|
11
|
+
DEBUG = 0
|
12
|
+
INFO = 1
|
13
|
+
WARN = 2
|
14
|
+
ERROR = 3
|
15
|
+
FATAL = 4
|
16
|
+
UNKNOWN = 5
|
17
|
+
end
|
18
|
+
|
19
|
+
include Levels
|
20
|
+
|
21
|
+
# Maps Ruby Logger levels to syslog levels as SyslogLogger and syslogger gems.
|
22
|
+
LEVELS_MAPPING = {DEBUG => 7, # Debug
|
23
|
+
INFO => 6, # Info
|
24
|
+
WARN => 5, # Notice
|
25
|
+
ERROR => 4, # Warning
|
26
|
+
FATAL => 3, # Error
|
27
|
+
UNKNOWN => 1} # Alert – shouldn't be used
|
28
|
+
end
|
data/test/helper.rb
CHANGED
data/test/test_logger.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestLogger < Test::Unit::TestCase
|
4
|
+
context "with notifier with mocked sender" do
|
5
|
+
setup do
|
6
|
+
Socket.stubs(:gethostname).returns('stubbed_hostname')
|
7
|
+
@notifier = GELF::Logger.new('host', 12345)
|
8
|
+
@sender = mock
|
9
|
+
@notifier.instance_variable_set('@sender', @sender)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "respond to #close" do
|
13
|
+
assert @notifier.respond_to?(:close)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#add" do
|
17
|
+
# logger.add(Logger::INFO, 'Message')
|
18
|
+
should "implement add method with level and message from parameters" do
|
19
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
20
|
+
level == GELF::INFO &&
|
21
|
+
hash['short_message'] == 'Message'
|
22
|
+
end
|
23
|
+
@notifier.add(GELF::INFO, 'Message')
|
24
|
+
end
|
25
|
+
|
26
|
+
# logger.add(Logger::INFO, RuntimeError.new('Boom!'))
|
27
|
+
should "implement add method with level and exception from parameters" do
|
28
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
29
|
+
level == GELF::INFO &&
|
30
|
+
hash['short_message'] == 'RuntimeError: Boom!' &&
|
31
|
+
hash['full_message'] =~ /^Backtrace/
|
32
|
+
end
|
33
|
+
@notifier.add(GELF::INFO, RuntimeError.new('Boom!'))
|
34
|
+
end
|
35
|
+
|
36
|
+
# logger.add(Logger::INFO) { 'Message' }
|
37
|
+
should "implement add method with level from parameter and message from block" do
|
38
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
39
|
+
level == GELF::INFO &&
|
40
|
+
hash['short_message'] == 'Message'
|
41
|
+
end
|
42
|
+
@notifier.add(GELF::INFO) { 'Message' }
|
43
|
+
end
|
44
|
+
|
45
|
+
# logger.add(Logger::INFO) { RuntimeError.new('Boom!') }
|
46
|
+
should "implement add method with level from parameter and exception from block" do
|
47
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
48
|
+
level == GELF::INFO &&
|
49
|
+
hash['short_message'] == 'RuntimeError: Boom!' &&
|
50
|
+
hash['full_message'] =~ /^Backtrace/
|
51
|
+
end
|
52
|
+
@notifier.add(GELF::INFO) { RuntimeError.new('Boom!') }
|
53
|
+
end
|
54
|
+
|
55
|
+
# logger.add(Logger::INFO, 'Message', 'Facility')
|
56
|
+
should "implement add method with level, message and facility from parameters" do
|
57
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
58
|
+
level == GELF::INFO &&
|
59
|
+
hash['short_message'] == 'Message' &&
|
60
|
+
hash['facility'] == 'Facility'
|
61
|
+
end
|
62
|
+
@notifier.add(GELF::INFO, 'Message', 'Facility')
|
63
|
+
end
|
64
|
+
|
65
|
+
# logger.add(Logger::INFO, RuntimeError.new('Boom!'), 'Facility')
|
66
|
+
should "implement add method with level, exception and facility from parameters" do
|
67
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
68
|
+
level == GELF::INFO &&
|
69
|
+
hash['short_message'] == 'RuntimeError: Boom!' &&
|
70
|
+
hash['full_message'] =~ /^Backtrace/ &&
|
71
|
+
hash['facility'] == 'Facility'
|
72
|
+
end
|
73
|
+
@notifier.add(GELF::INFO, RuntimeError.new('Boom!'), 'Facility')
|
74
|
+
end
|
75
|
+
|
76
|
+
# logger.add(Logger::INFO, 'Facility') { 'Message' }
|
77
|
+
should "implement add method with level and facility from parameters and message from block" do
|
78
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
79
|
+
level == GELF::INFO &&
|
80
|
+
hash['short_message'] == 'Message' &&
|
81
|
+
hash['facility'] == 'Facility'
|
82
|
+
end
|
83
|
+
@notifier.add(GELF::INFO, 'Facility') { 'Message' }
|
84
|
+
end
|
85
|
+
|
86
|
+
# logger.add(Logger::INFO, 'Facility') { RuntimeError.new('Boom!') }
|
87
|
+
should "implement add method with level and facility from parameters and exception from block" do
|
88
|
+
@notifier.expects(:notify_with_level!).with do |level, hash|
|
89
|
+
level == GELF::INFO &&
|
90
|
+
hash['short_message'] == 'RuntimeError: Boom!' &&
|
91
|
+
hash['full_message'] =~ /^Backtrace/ &&
|
92
|
+
hash['facility'] == 'Facility'
|
93
|
+
end
|
94
|
+
@notifier.add(GELF::INFO, 'Facility') { RuntimeError.new('Boom!') }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
GELF::Levels.constants.each do |const|
|
99
|
+
# logger.error "Argument #{ @foo } mismatch."
|
100
|
+
should "call notify with level #{const} from method name and message from parameter" do
|
101
|
+
@notifier.expects(:add).with(GELF.const_get(const), 'message')
|
102
|
+
@notifier.__send__(const.downcase, 'message')
|
103
|
+
end
|
104
|
+
|
105
|
+
# logger.fatal { "Argument 'foo' not given." }
|
106
|
+
should "call notify with level #{const} from method name and message from block" do
|
107
|
+
@notifier.expects(:add).with(GELF.const_get(const), 'message')
|
108
|
+
@notifier.__send__(const.downcase) { 'message' }
|
109
|
+
end
|
110
|
+
|
111
|
+
# logger.info('initialize') { "Initializing..." }
|
112
|
+
should "call notify with level #{const} from method name, facility from parameter and message from block" do
|
113
|
+
@notifier.expects(:add).with(GELF.const_get(const), 'message', 'facility')
|
114
|
+
@notifier.__send__(const.downcase, 'facility') { 'message' }
|
115
|
+
end
|
116
|
+
|
117
|
+
should "respond to #{const.downcase}?" do
|
118
|
+
@notifier.level = GELF.const_get(const) - 1
|
119
|
+
assert @notifier.__send__(const.to_s.downcase + '?')
|
120
|
+
@notifier.level = GELF.const_get(const)
|
121
|
+
assert @notifier.__send__(const.to_s.downcase + '?')
|
122
|
+
@notifier.level = GELF.const_get(const) + 1
|
123
|
+
assert !@notifier.__send__(const.to_s.downcase + '?')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
should "support Notifier#<<" do
|
128
|
+
@notifier.expects(:notify_with_level!).with do |nil_, hash|
|
129
|
+
hash['short_message'] == "Message" &&
|
130
|
+
hash['level'] == GELF::UNKNOWN
|
131
|
+
end
|
132
|
+
@notifier << "Message"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/test/test_notifier.rb
CHANGED
@@ -1,24 +1,33 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
HASH = {'short_message' => 'message', 'host' => '
|
3
|
+
HASH = {'short_message' => 'message', 'host' => 'somehost', 'level' => GELF::WARN, 'facility' => 'test'}
|
4
4
|
|
5
5
|
RANDOM_DATA = ('A'..'Z').to_a
|
6
6
|
|
7
7
|
class TestNotifier < Test::Unit::TestCase
|
8
|
-
should "allow access to host, port and
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
assert_equal
|
8
|
+
should "allow access to host, port, max_chunk_size and default_options" do
|
9
|
+
Socket.expects(:gethostname).returns('default_hostname')
|
10
|
+
n = GELF::Notifier.new
|
11
|
+
assert_equal ['localhost', 12201, 1420], [n.host, n.port, n.max_chunk_size]
|
12
|
+
assert_equal({'level' => 0, 'host' => 'default_hostname', 'facility' => 'gelf-rb'}, n.default_options)
|
13
|
+
n.host, n.port, n.max_chunk_size, n.default_options = 'graylog2.org', 7777, :lan, {'host' => 'grayhost'}
|
14
|
+
assert_equal ['graylog2.org', 7777, 8154], [n.host, n.port, n.max_chunk_size]
|
15
|
+
assert_equal({'host' => 'grayhost'}, n.default_options)
|
16
|
+
|
17
|
+
n.max_chunk_size = 1337.1
|
18
|
+
assert_equal 1337, n.max_chunk_size
|
13
19
|
end
|
14
20
|
|
15
|
-
context "with notifier" do
|
21
|
+
context "with notifier with mocked sender" do
|
16
22
|
setup do
|
23
|
+
Socket.stubs(:gethostname).returns('stubbed_hostname')
|
17
24
|
@notifier = GELF::Notifier.new('host', 12345)
|
25
|
+
@sender = mock
|
26
|
+
@notifier.instance_variable_set('@sender', @sender)
|
18
27
|
end
|
19
28
|
|
20
29
|
context "extract_hash" do
|
21
|
-
should "check
|
30
|
+
should "check arguments" do
|
22
31
|
assert_raise(ArgumentError) { @notifier.__send__(:extract_hash) }
|
23
32
|
assert_raise(ArgumentError) { @notifier.__send__(:extract_hash, 1, 2, 3) }
|
24
33
|
end
|
@@ -39,6 +48,7 @@ class TestNotifier < Test::Unit::TestCase
|
|
39
48
|
hash = @notifier.__send__(:extract_hash, e)
|
40
49
|
assert_equal 'RuntimeError: message', hash['short_message']
|
41
50
|
assert_match /Backtrace/, hash['full_message']
|
51
|
+
assert_equal GELF::ERROR, hash['level']
|
42
52
|
end
|
43
53
|
|
44
54
|
should "work with exception without backtrace" do
|
@@ -48,18 +58,23 @@ class TestNotifier < Test::Unit::TestCase
|
|
48
58
|
end
|
49
59
|
|
50
60
|
should "work with exception and hash" do
|
51
|
-
e, h = RuntimeError.new('message'), {'param' => 1, 'short_message' => 'will be hidden by exception'}
|
61
|
+
e, h = RuntimeError.new('message'), {'param' => 1, 'level' => GELF::FATAL, 'short_message' => 'will be hidden by exception'}
|
52
62
|
hash = @notifier.__send__(:extract_hash, e, h)
|
53
63
|
assert_equal 'RuntimeError: message', hash['short_message']
|
64
|
+
assert_equal GELF::FATAL, hash['level']
|
54
65
|
assert_equal 1, hash['param']
|
55
66
|
end
|
56
67
|
|
57
68
|
should "work with plain text" do
|
58
|
-
|
69
|
+
hash = @notifier.__send__(:extract_hash, 'message')
|
70
|
+
assert_equal 'message', hash['short_message']
|
71
|
+
assert_equal GELF::INFO, hash['level']
|
59
72
|
end
|
60
73
|
|
61
74
|
should "work with plain text and hash" do
|
62
|
-
|
75
|
+
hash = @notifier.__send__(:extract_hash, 'message', 'level' => GELF::WARN)
|
76
|
+
assert_equal 'message', hash['short_message']
|
77
|
+
assert_equal GELF::WARN, hash['level']
|
63
78
|
end
|
64
79
|
|
65
80
|
should "covert hash keys to strings" do
|
@@ -72,6 +87,13 @@ class TestNotifier < Test::Unit::TestCase
|
|
72
87
|
assert_raise(ArgumentError) { @notifier.__send__(:extract_hash, :short_message => :message1, 'short_message' => 'message2') }
|
73
88
|
end
|
74
89
|
|
90
|
+
should "use default_options" do
|
91
|
+
@notifier.default_options = {:file => 'somefile.rb', 'short_message' => 'will be hidden by explicit argument'}
|
92
|
+
hash = @notifier.__send__(:extract_hash, HASH)
|
93
|
+
assert_equal 'somefile.rb', hash['file']
|
94
|
+
assert_not_equal 'will be hidden by explicit argument', hash['short_message']
|
95
|
+
end
|
96
|
+
|
75
97
|
should "be compatible with HoptoadNotifier" do
|
76
98
|
# https://github.com/thoughtbot/hoptoad_notifier/blob/master/README.rdoc, section Going beyond exceptions
|
77
99
|
hash = @notifier.__send__(:extract_hash, :error_class => 'Class', :error_message => 'Message')
|
@@ -79,41 +101,52 @@ class TestNotifier < Test::Unit::TestCase
|
|
79
101
|
end
|
80
102
|
end
|
81
103
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
104
|
+
context "datagrams_from_hash" do
|
105
|
+
should "not split short data" do
|
106
|
+
@notifier.instance_variable_set('@hash', HASH)
|
107
|
+
datagrams = @notifier.__send__(:datagrams_from_hash)
|
108
|
+
assert_equal 1, datagrams.count
|
109
|
+
assert_equal "\170\234", datagrams[0][0..1]
|
110
|
+
end
|
111
|
+
|
112
|
+
should "split long data" do
|
113
|
+
srand(1) # for stable tests
|
114
|
+
hash = HASH.merge('something' => (0..3000).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join) # or it will be compressed too good
|
115
|
+
@notifier.instance_variable_set('@hash', hash)
|
116
|
+
datagrams = @notifier.__send__(:datagrams_from_hash)
|
117
|
+
assert_equal 2, datagrams.count
|
118
|
+
assert_equal "\036\017", datagrams[0][0..1]
|
119
|
+
assert_equal "\036\017", datagrams[1][0..1]
|
120
|
+
end
|
86
121
|
end
|
87
122
|
|
88
|
-
context "
|
89
|
-
|
90
|
-
|
91
|
-
@notifier.notify!(HASH)
|
123
|
+
context "level threshold" do
|
124
|
+
setup do
|
125
|
+
@notifier.level = GELF::WARN
|
92
126
|
end
|
93
127
|
|
94
|
-
should "
|
95
|
-
|
96
|
-
|
97
|
-
@notifier.notify!(HASH.merge('something' => (0..3000).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join)) # or it will be compressed too good
|
128
|
+
should "not send notifications with level below threshold" do
|
129
|
+
@sender.expects(:send_datagrams).never
|
130
|
+
@notifier.notify!(HASH.merge('level' => GELF::DEBUG))
|
98
131
|
end
|
99
132
|
|
100
|
-
should "
|
101
|
-
|
102
|
-
|
103
|
-
port == @notifier.port &&
|
104
|
-
data[0..1] == "\170\234"
|
105
|
-
end
|
106
|
-
@notifier.notify!(HASH)
|
133
|
+
should "not notifications with level equal or above threshold" do
|
134
|
+
@sender.expects(:send_datagrams).once
|
135
|
+
@notifier.notify!(HASH.merge('level' => GELF::WARN))
|
107
136
|
end
|
137
|
+
end
|
108
138
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
139
|
+
should "pass valid data to sender" do
|
140
|
+
@sender.expects(:send_datagrams).with do |datagrams|
|
141
|
+
datagrams.is_a?(Array) && datagrams[0].is_a?(String)
|
142
|
+
end
|
143
|
+
@notifier.notify!(HASH)
|
144
|
+
end
|
145
|
+
|
146
|
+
GELF::Levels.constants.each do |const|
|
147
|
+
should "call notify with level #{const} from method name" do
|
148
|
+
@notifier.expects(:notify_with_level).with(GELF.const_get(const), HASH)
|
149
|
+
@notifier.__send__(const.downcase, HASH)
|
117
150
|
end
|
118
151
|
end
|
119
152
|
|
@@ -122,26 +155,9 @@ class TestNotifier < Test::Unit::TestCase
|
|
122
155
|
end
|
123
156
|
|
124
157
|
should "rescue from invalid invocation of #notify" do
|
125
|
-
@notifier.expects(:
|
126
|
-
@notifier.expects(:
|
158
|
+
@notifier.expects(:notify_with_level!).with(nil, instance_of(Hash)).raises(ArgumentError)
|
159
|
+
@notifier.expects(:notify_with_level!).with(GELF::UNKNOWN, instance_of(ArgumentError))
|
127
160
|
assert_nothing_raised { @notifier.notify(:no_short_message => 'too bad') }
|
128
161
|
end
|
129
|
-
|
130
|
-
should "chunk data" do
|
131
|
-
UDPSocket.any_instance.stubs(:send)
|
132
|
-
srand(1) # for stable tests
|
133
|
-
data = (0..3000).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join
|
134
|
-
datagrams = @notifier.__send__(:do_notify, {'short_message' => data, 'host' => 'localhost'})
|
135
|
-
assert_equal 2, datagrams.count
|
136
|
-
datagrams.each_index do |i|
|
137
|
-
datagram = datagrams[i]
|
138
|
-
assert datagram[0..1] == "\x1e\x0f"
|
139
|
-
# datagram[2..33] is a message id
|
140
|
-
assert_equal 0, datagram[34].ord
|
141
|
-
assert_equal i, datagram[35].ord
|
142
|
-
assert_equal 0, datagram[36].ord
|
143
|
-
assert_equal datagrams.count, datagram[37].ord
|
144
|
-
end
|
145
|
-
end
|
146
162
|
end
|
147
163
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestRubySender < Test::Unit::TestCase
|
4
|
+
context "with ruby sender" do
|
5
|
+
setup do
|
6
|
+
@host, @port = 'localhost', 12201
|
7
|
+
@sender = GELF::RubySender.new('localhost', 12201)
|
8
|
+
@datagrams = %w(d1 d2 d3)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "send_datagrams" do
|
12
|
+
setup do
|
13
|
+
@sender.send_datagrams(@datagrams)
|
14
|
+
end
|
15
|
+
|
16
|
+
before_should "be called with 3 times correct parameters" do
|
17
|
+
UDPSocket.any_instance.expects(:send).times(3).with(instance_of(String), 0, @host, @port).returns(@datagrams)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gelf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: -1848230059
|
5
|
+
prerelease: true
|
5
6
|
segments:
|
6
7
|
- 1
|
8
|
+
- 1
|
7
9
|
- 0
|
8
|
-
-
|
9
|
-
version: 1.0.
|
10
|
+
- beta1
|
11
|
+
version: 1.1.0.beta1
|
10
12
|
platform: ruby
|
11
13
|
authors:
|
12
14
|
- Alexey Palazhchenko
|
@@ -15,7 +17,7 @@ autorequire:
|
|
15
17
|
bindir: bin
|
16
18
|
cert_chain: []
|
17
19
|
|
18
|
-
date: 2010-11-
|
20
|
+
date: 2010-11-25 00:00:00 +03:00
|
19
21
|
default_executable:
|
20
22
|
dependencies:
|
21
23
|
- !ruby/object:Gem::Dependency
|
@@ -26,6 +28,7 @@ dependencies:
|
|
26
28
|
requirements:
|
27
29
|
- - ">="
|
28
30
|
- !ruby/object:Gem::Version
|
31
|
+
hash: 3
|
29
32
|
segments:
|
30
33
|
- 0
|
31
34
|
version: "0"
|
@@ -39,6 +42,7 @@ dependencies:
|
|
39
42
|
requirements:
|
40
43
|
- - ">="
|
41
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
42
46
|
segments:
|
43
47
|
- 0
|
44
48
|
version: "0"
|
@@ -52,6 +56,7 @@ dependencies:
|
|
52
56
|
requirements:
|
53
57
|
- - ">="
|
54
58
|
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
55
60
|
segments:
|
56
61
|
- 0
|
57
62
|
version: "0"
|
@@ -73,13 +78,21 @@ files:
|
|
73
78
|
- README.rdoc
|
74
79
|
- Rakefile
|
75
80
|
- VERSION
|
81
|
+
- benchmarks/notifier.rb
|
76
82
|
- gelf.gemspec
|
77
83
|
- lib/gelf.rb
|
78
84
|
- lib/gelf/deprecations.rb
|
85
|
+
- lib/gelf/em_sender.rb
|
86
|
+
- lib/gelf/logger.rb
|
79
87
|
- lib/gelf/notifier.rb
|
88
|
+
- lib/gelf/ruby_sender.rb
|
89
|
+
- lib/gelf/severity.rb
|
80
90
|
- test/helper.rb
|
81
91
|
- test/test_deprecations.rb
|
92
|
+
- test/test_logger.rb
|
82
93
|
- test/test_notifier.rb
|
94
|
+
- test/test_ruby_sender.rb
|
95
|
+
- test/test_severity.rb
|
83
96
|
has_rdoc: true
|
84
97
|
homepage: http://github.com/Graylog2/gelf-rb
|
85
98
|
licenses: []
|
@@ -94,17 +107,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
107
|
requirements:
|
95
108
|
- - ">="
|
96
109
|
- !ruby/object:Gem::Version
|
110
|
+
hash: 3
|
97
111
|
segments:
|
98
112
|
- 0
|
99
113
|
version: "0"
|
100
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
115
|
none: false
|
102
116
|
requirements:
|
103
|
-
- - "
|
117
|
+
- - ">"
|
104
118
|
- !ruby/object:Gem::Version
|
119
|
+
hash: 25
|
105
120
|
segments:
|
106
|
-
-
|
107
|
-
|
121
|
+
- 1
|
122
|
+
- 3
|
123
|
+
- 1
|
124
|
+
version: 1.3.1
|
108
125
|
requirements: []
|
109
126
|
|
110
127
|
rubyforge_project:
|
@@ -115,4 +132,7 @@ summary: Library to send GELF messages to Graylog2 logging server
|
|
115
132
|
test_files:
|
116
133
|
- test/helper.rb
|
117
134
|
- test/test_deprecations.rb
|
135
|
+
- test/test_logger.rb
|
118
136
|
- test/test_notifier.rb
|
137
|
+
- test/test_ruby_sender.rb
|
138
|
+
- test/test_severity.rb
|