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 CHANGED
@@ -1,5 +1,6 @@
1
1
  pkg/
2
2
  coverage/
3
3
  rdoc/
4
+ tmp/
4
5
 
5
6
  .DS_Store
data/CHANGELOG CHANGED
@@ -1,6 +1,21 @@
1
- 1.0.1, 2010-11-29:
2
- - fix bug in chunking.
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
- - initial stable version;
6
- - deprecated Gelf class is still there.
20
+ + initial stable version;
21
+ * deprecated Gelf class is still there.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Lennart Koopmann
1
+ Copyright (c) 2010 Lennart Koopmann, Aleksey Palazhchenko
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -17,4 +17,4 @@ Works with Ruby 1.8.7 and 1.9.2. 1.8.6 is not supported.
17
17
 
18
18
  == Copyright
19
19
 
20
- Copyright (c) 2010 Lennart Koopmann. See LICENSE for details.
20
+ Copyright (c) 2010 Lennart Koopmann and Aleksey Palazhchenko. See LICENSE for details.
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.2
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.2"
8
+ s.version = "1.1.0.beta1"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
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-29}
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/test_notifier.rb"
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/test_notifier.rb"
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
@@ -3,5 +3,8 @@ require 'socket'
3
3
  require 'zlib'
4
4
  require 'digest/sha2'
5
5
 
6
+ require 'gelf/severity'
7
+ require 'gelf/ruby_sender'
6
8
  require 'gelf/notifier'
9
+ require 'gelf/logger'
7
10
  require 'gelf/deprecations'
@@ -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 |a|
23
- define_method a do
24
- deprecate("GELF::Message##{a}")
25
- @message[a]
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 "#{a}=" do |value|
29
- deprecate("GELF::Message##{a} = value")
30
- @message[a] = value
28
+ define_method "#{attribute}=" do |value|
29
+ deprecate("GELF::Message##{attribute} = value")
30
+ @message[attribute] = value
31
31
  end
32
32
  end
33
33
 
@@ -0,0 +1,10 @@
1
+ module GELF
2
+ # Sender for EventMachine.
3
+ class EMSender
4
+ def initialize(host, port)
5
+ end
6
+
7
+ def send_datagrams(datagrams)
8
+ end
9
+ end
10
+ end
@@ -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
- @@id = 0
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
- def initialize(host = 'localhost', port = 12201, max_size = 'WAN')
10
- @host, @port, self.max_chunk_size = host, port, max_size
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
- s = size.to_s.downcase
17
- if s == 'wan'
32
+ size_s = size.to_s.downcase
33
+ if size_s == 'wan'
18
34
  @max_chunk_size = 1420
19
- elsif s == 'lan'
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
- notify!(*args)
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
- do_notify(extract_hash(*args))
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 extract_hash(object_or_exception, args = {})
49
- primary_data = if object_or_exception.respond_to?(:to_hash)
50
- object_or_exception.to_hash
51
- elsif object_or_exception.is_a?(Exception)
52
- bt = object_or_exception.backtrace || ["Backtrace is not available."]
53
- { 'short_message' => "#{object_or_exception.class}: #{object_or_exception.message}",
54
- 'full_message' => "Backtrace:\n" + bt.join("\n") }
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
- { 'short_message' => object_or_exception.to_s }
96
+ args['level'] ||= GELF::INFO
97
+ { 'short_message' => object.to_s }
57
98
  end
58
99
 
59
- hash = args.merge(primary_data)
60
-
61
- hash.keys.each do |key|
62
- value, key_s = hash.delete(key), key.to_s
63
- raise ArgumentError.new("Both #{key.inspect} and #{key_s} are present.") if hash.has_key?(key_s)
64
- hash[key_s] = value
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
- hash['host'] ||= @this_host || detect_this_host
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
- # for compatibility with HoptoadNotifier
70
- if hash['short_message'].to_s.empty?
71
- if hash.has_key?('error_class') && hash.has_key?('error_message')
72
- hash['short_message'] = "#{hash['error_class']}: #{hash['error_message']}"
73
- hash.delete('error_class')
74
- hash.delete('error_message')
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
- %w(short_message host).each do |a|
79
- if hash[a].to_s.empty?
80
- raise ArgumentError.new("Attributes short_message and host must be set.")
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 do_notify(hash)
88
- data = Zlib::Deflate.deflate(hash.to_json).bytes
89
- sock = UDPSocket.open
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
- @@id += 1
95
- msg_id = Digest::SHA256.digest("#{Time.now.to_f}-#{@@id}")
96
- i, count = 0, (data.count / 1.0 / @max_chunk_size).ceil
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, i, count)
99
- i += 1
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, sequence_number, sequence_count)
110
- return "\036\017" + msg_id + [sequence_number, sequence_count, *data].pack('nnC*')
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 detect_this_host
114
- @this_host = Socket.gethostname
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
@@ -3,6 +3,11 @@ require 'test/unit'
3
3
  require 'shoulda'
4
4
  require 'mocha'
5
5
 
6
+ begin
7
+ require 'ruby-debug'
8
+ rescue LoadError
9
+ end
10
+
6
11
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
12
  $LOAD_PATH.unshift(File.dirname(__FILE__))
8
13
  require 'gelf'
@@ -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
@@ -1,24 +1,33 @@
1
1
  require 'helper'
2
2
 
3
- HASH = {'short_message' => 'message', 'host' => 'localhost'}
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 max_chunk_size" do
9
- notifier = GELF::Notifier.new
10
- assert_equal ['localhost', 12201, 1420], [notifier.host, notifier.port, notifier.max_chunk_size]
11
- notifier.host, notifier.port, notifier.max_chunk_size = 'graylog2.org', 7777, :lan
12
- assert_equal ['graylog2.org', 7777, 8154], [notifier.host, notifier.port, notifier.max_chunk_size]
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 number of arguments" do
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
- assert_equal 'message', @notifier.__send__(:extract_hash, 'message')['short_message']
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
- assert_equal HASH, @notifier.__send__(:extract_hash, 'message', 'host' => 'localhost')
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
- should "detect and cache host" do
83
- Socket.expects(:gethostname).once.returns("localhost")
84
- @notifier.expects(:do_notify).twice
85
- 2.times { @notifier.notify!('short_message' => 'message') }
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 "datagrams" do
89
- should "not split short datagram" do
90
- UDPSocket.any_instance.expects(:send).once
91
- @notifier.notify!(HASH)
123
+ context "level threshold" do
124
+ setup do
125
+ @notifier.level = GELF::WARN
92
126
  end
93
127
 
94
- should "split long datagram" do
95
- srand(1) # for stable tests
96
- UDPSocket.any_instance.expects(:send).twice
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 "send correct short datagram" do
101
- UDPSocket.any_instance.expects(:send).with do |data, flags, host, port|
102
- host == @notifier.host &&
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
- should "send correct long datagrams" do
110
- UDPSocket.any_instance.expects(:send).twice.with do |data, flags, host, port|
111
- host == @notifier.host &&
112
- port == @notifier.port &&
113
- data[0..1] == "\036\017"
114
- end
115
- srand(1) # for stable tests
116
- @notifier.notify!(HASH.merge('something' => (0..3000).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join)) # or it will be compressed too good
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(:notify!).with(instance_of(Hash)).raises(ArgumentError)
126
- @notifier.expects(:notify!).with(instance_of(ArgumentError))
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
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ class TestSeverity < Test::Unit::TestCase
4
+ should "map Ruby Logger levels to syslog levels" do
5
+ GELF::LEVELS_MAPPING.each do |ruby_level, syslog_level|
6
+ assert_not_equal syslog_level, ruby_level
7
+ end
8
+ end
9
+ 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
- prerelease: false
4
+ hash: -1848230059
5
+ prerelease: true
5
6
  segments:
6
7
  - 1
8
+ - 1
7
9
  - 0
8
- - 2
9
- version: 1.0.2
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-29 00:00:00 +03:00
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
- - 0
107
- version: "0"
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