gelf 1.0.2 → 1.1.0.beta1

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