syslog-sd 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ 1.2.0, 2010-05-17:
2
+ + Initial version, based on master branch of `gelf` gem.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 Lennart Koopmann, Alexey Palazhchenko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,21 @@
1
+ = syslog-sd
2
+
3
+ Super-Duper library to send syslog messages to logging server such as Graylog2 (http://graylog2.org).
4
+ Supports Structured Data elements as defined by RFC 5424.
5
+
6
+ Based on GELF gem (https://github.com/graylog2/gelf-rb).
7
+
8
+ Works with Ruby 1.8.7 and 1.9.2. 1.8.6 is not supported.
9
+
10
+ == Note on Patches/Pull Requests
11
+
12
+ * Fork the project.
13
+ * Make your feature addition or bug fix.
14
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
15
+ * Commit, do not mess with rakefile, version, or history.
16
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
17
+ * Send me a pull request. Bonus points for topic branches.
18
+
19
+ == Copyright
20
+
21
+ Copyright (c) 2010-2011 Lennart Koopmann and Alexey Palazhchenko. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,98 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'ci/reporter/rake/test_unit'
5
+ rescue LoadError
6
+ # nothing
7
+ end
8
+
9
+ begin
10
+ require 'jeweler'
11
+
12
+ Jeweler::Tasks.new do |gem|
13
+ gem.name = "syslog-sd"
14
+ gem.summary = 'Library to send syslog messages to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.'
15
+ gem.description = 'Super-Duper library to send syslog messages to logging server such as Graylog2. ' +
16
+ 'Supports Structured Data elements as defined by RFC 5424.'
17
+ gem.email = "alexey.palazhchenko@gmail.com"
18
+ gem.homepage = "http://github.com/AlekSi/syslog-sd-rb"
19
+ gem.authors = ["Alexey Palazhchenko", "Lennart Koopmann"]
20
+ gem.add_development_dependency "shoulda"
21
+ gem.add_development_dependency "mocha"
22
+ gem.add_development_dependency "timecop"
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+ rescue LoadError => e
26
+ puts e
27
+ abort "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
28
+ end
29
+
30
+ require 'rake/testtask'
31
+ Rake::TestTask.new(:test) do |test|
32
+ test.libs << 'lib' << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+
37
+ task :test => :check_dependencies
38
+ task :default => :test
39
+
40
+ begin
41
+ require 'rcov/rcovtask'
42
+ Rcov::RcovTask.new do |test|
43
+ test.libs << 'test'
44
+ test.pattern = 'test/**/test_*.rb'
45
+ test.rcov_opts << '--exclude gem'
46
+ test.verbose = true
47
+ end
48
+ rescue LoadError => e
49
+ task :rcov do
50
+ puts e
51
+ abort "rcov is not available. Run: gem install rcov"
52
+ end
53
+ end
54
+
55
+ begin
56
+ gem 'ruby_parser', '~> 2.0.6'
57
+ gem 'activesupport', '~> 3.0.0'
58
+ gem 'metric_fu', '~> 2.1.1'
59
+ require 'metric_fu'
60
+
61
+ MetricFu::Configuration.run do |config|
62
+ # Saikuro is useless
63
+ config.metrics -= [:saikuro]
64
+
65
+ config.flay = { :dirs_to_flay => ['lib'],
66
+ :minimum_score => 10 }
67
+ config.flog = { :dirs_to_flog => ['lib'] }
68
+ config.reek = { :dirs_to_reek => ['lib'] }
69
+ config.roodi = { :dirs_to_roodi => ['lib'] }
70
+ config.rcov = { :environment => 'test',
71
+ :test_files => ['test/test_*.rb'],
72
+ :rcov_opts => ["-I 'lib:test'",
73
+ "--sort coverage",
74
+ "--no-html",
75
+ "--text-coverage",
76
+ "--no-color",
77
+ "--exclude /test/,/gems/"]}
78
+ config.graph_engine = :gchart
79
+ end
80
+
81
+ rescue LoadError, NameError => e
82
+ desc 'Generate all metrics reports'
83
+ task :'metrics:all' do
84
+ puts e.inspect
85
+ # puts e.backtrace
86
+ abort "metric_fu is not available. Run: gem install metric_fu"
87
+ end
88
+ end
89
+
90
+ require 'rake/rdoctask'
91
+ Rake::RDocTask.new do |rdoc|
92
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
93
+
94
+ rdoc.rdoc_dir = 'rdoc'
95
+ rdoc.title = "syslog-sd #{version}"
96
+ rdoc.rdoc_files.include('README*')
97
+ rdoc.rdoc_files.include('lib/**/*.rb')
98
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.0
@@ -0,0 +1,36 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ puts "Loading..."
4
+
5
+ require 'benchmark'
6
+ require 'rubygems'
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'syslog-sd'
10
+
11
+ puts "Generating random data..."
12
+ srand(1)
13
+ RANDOM_DATA = ('A'..'z').to_a
14
+ k3_message = (1..3*1024).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join
15
+
16
+ TARGET_HOST = 'localhost'
17
+ TARGET_PORT = 12201
18
+ DEFAULT_OPTIONS = { '_host' => 'localhost' }
19
+ TIMES = 5000
20
+
21
+ SHORT_HASH = { 'short_message' => 'message' }
22
+ LONG_HASH = { 'short_message' => 'message', 'long_message' => k3_message }
23
+
24
+
25
+ notifier = SyslogSD::Notifier.new(TARGET_HOST, TARGET_PORT, DEFAULT_OPTIONS)
26
+
27
+ # to create mongo collections, etc.
28
+ notifier.notify!(LONG_HASH)
29
+ sleep(5)
30
+
31
+ puts "Sending #{TIMES} notifications...\n"
32
+ tms = Benchmark.bm(25) do |b|
33
+ b.report('short data') { TIMES.times { notifier.notify!(SHORT_HASH) } }
34
+ sleep(5)
35
+ b.report('long data ') { TIMES.times { notifier.notify!(LONG_HASH) } }
36
+ end
data/lib/syslog-sd.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'socket'
2
+ require 'digest/md5'
3
+
4
+ require 'syslog-sd/severity'
5
+ require 'syslog-sd/ruby_sender'
6
+ require 'syslog-sd/notifier'
7
+ require 'syslog-sd/logger'
@@ -0,0 +1,55 @@
1
+ module SyslogSD
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, progname = if args.count == 2
14
+ [args[0], args[1]]
15
+ elsif args.count == 0
16
+ [yield, default_options['facility']]
17
+ elsif block_given?
18
+ [yield, args[0]]
19
+ else
20
+ [args[0], default_options['facility']]
21
+ end
22
+
23
+ hash = {'short_message' => message, 'facility' => progname}
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
+ # Redefines methods in +Notifier+.
29
+ SyslogSD::Levels.constants.each do |const|
30
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
31
+ def #{const.downcase}(*args) # def debug(*args)
32
+ args.unshift(yield) if block_given? # args.unshift(yield) if block_given?
33
+ add(SyslogSD::#{const}, *args) # add(SyslogSD::DEBUG, *args)
34
+ end # end
35
+
36
+ def #{const.downcase}? # def debug?
37
+ SyslogSD::#{const} >= level # SyslogSD::DEBUG >= level
38
+ end # end
39
+ EOT
40
+ end
41
+
42
+ def <<(message)
43
+ notify_with_level(SyslogSD::UNKNOWN, 'short_message' => message)
44
+ end
45
+ end
46
+
47
+ # Graylog2 notifier, compatible with Ruby Logger.
48
+ # You can use it with Rails like this:
49
+ # config.logger = SyslogSD::Logger.new("localhost", 12201, "WAN", { :facility => "appname" })
50
+ # config.colorize_logging = false
51
+ class Logger < Notifier
52
+ include LoggerCompatibility
53
+ @last_chunk_id = 0
54
+ end
55
+ end
@@ -0,0 +1,214 @@
1
+ module SyslogSD
2
+ # syslog notifier.
3
+ class Notifier
4
+ attr_accessor :enabled, :collect_file_and_line
5
+ attr_reader :level, :default_options, :level_mapping
6
+
7
+ # +host+ and +port+ are host/ip and port of syslog server.
8
+ # +default_options+ is used in notify!
9
+ def initialize(host = 'localhost', port = 514, default_options = {})
10
+ @enabled = true
11
+ @collect_file_and_line = true
12
+ @sd_id = "_@37797"
13
+
14
+ self.level = SyslogSD::DEBUG
15
+
16
+ self.default_options = default_options
17
+ self.default_options['host'] ||= Socket.gethostname
18
+ self.default_options['level'] ||= SyslogSD::UNKNOWN
19
+ self.default_options['facility'] ||= 'syslog-sd-rb'
20
+ self.default_options['procid'] ||= Process.pid
21
+
22
+ @sender = RubyUdpSender.new([[host, port]])
23
+ self.level_mapping = :logger
24
+ end
25
+
26
+ # Get a list of receivers.
27
+ # notifier.addresses # => [['localhost', 12201], ['localhost', 12202]]
28
+ def addresses
29
+ @sender.addresses
30
+ end
31
+
32
+ # Set a list of receivers.
33
+ # notifier.addresses = [['localhost', 12201], ['localhost', 12202]]
34
+ def addresses=(addrs)
35
+ @sender.addresses = addrs
36
+ end
37
+
38
+ def host
39
+ warn "SyslogSD::Notifier#host is deprecated. Use #addresses instead."
40
+ self.addresses.first[0]
41
+ end
42
+
43
+ def port
44
+ warn "SyslogSD::Notifier#port is deprecated. Use #addresses instead."
45
+ self.addresses.first[1]
46
+ end
47
+
48
+ def level=(new_level)
49
+ @level = if new_level.is_a?(Fixnum)
50
+ new_level
51
+ else
52
+ SyslogSD.const_get(new_level.to_s.upcase)
53
+ end
54
+ end
55
+
56
+ def default_options=(options)
57
+ @default_options = self.class.stringify_keys(options)
58
+ end
59
+
60
+ # +mapping+ may be a hash, 'logger' (SyslogSD::LOGGER_MAPPING) or 'direct' (SyslogSD::DIRECT_MAPPING).
61
+ # Default (compatible) value is 'logger'.
62
+ def level_mapping=(mapping)
63
+ case mapping.to_s.downcase
64
+ when 'logger'
65
+ @level_mapping = SyslogSD::LOGGER_MAPPING
66
+ when 'direct'
67
+ @level_mapping = SyslogSD::DIRECT_MAPPING
68
+ else
69
+ @level_mapping = mapping
70
+ end
71
+ end
72
+
73
+ def disable
74
+ @enabled = false
75
+ end
76
+
77
+ def enable
78
+ @enabled = true
79
+ end
80
+
81
+ # Same as notify!, but rescues all exceptions (including +ArgumentError+)
82
+ # and sends them instead.
83
+ def notify(*args)
84
+ notify_with_level(nil, *args)
85
+ end
86
+
87
+ # Sends message to Graylog2 server.
88
+ # +args+ can be:
89
+ # - hash-like object (any object which responds to +to_hash+, including +Hash+ instance):
90
+ # notify!(:short_message => 'All your rebase are belong to us', :user => 'AlekSi')
91
+ # - exception with optional hash-like object:
92
+ # notify!(SecurityError.new('ALARM!'), :trespasser => 'AlekSi')
93
+ # - string-like object (anything which responds to +to_s+) with optional hash-like object:
94
+ # notify!('Plain olde text message', :scribe => 'AlekSi')
95
+ # Resulted fields are merged with +default_options+, the latter will never overwrite the former.
96
+ # This method will raise +ArgumentError+ if arguments are wrong. Consider using notify instead.
97
+ def notify!(*args)
98
+ notify_with_level!(nil, *args)
99
+ end
100
+
101
+ SyslogSD::Levels.constants.each do |const|
102
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
103
+ def #{const.downcase}(*args) # def debug(*args)
104
+ notify_with_level(SyslogSD::#{const}, *args) # notify_with_level(SyslogSD::DEBUG, *args)
105
+ end # end
106
+ EOT
107
+ end
108
+
109
+ private
110
+ def notify_with_level(message_level, *args)
111
+ notify_with_level!(message_level, *args)
112
+ rescue Exception => exception
113
+ notify_with_level!(SyslogSD::UNKNOWN, exception)
114
+ end
115
+
116
+ def notify_with_level!(message_level, *args)
117
+ return unless @enabled
118
+ extract_hash(*args)
119
+ @hash['level'] = message_level unless message_level.nil?
120
+ if @hash['level'] >= level
121
+ @sender.send_datagram(serialize_hash)
122
+ end
123
+ end
124
+
125
+ def extract_hash(object = nil, args = {})
126
+ primary_data = if object.respond_to?(:to_hash)
127
+ object.to_hash
128
+ elsif object.is_a?(Exception)
129
+ args['level'] ||= SyslogSD::ERROR
130
+ self.class.extract_hash_from_exception(object)
131
+ else
132
+ args['level'] ||= SyslogSD::INFO
133
+ { 'short_message' => object.to_s }
134
+ end
135
+
136
+ @hash = default_options.merge(self.class.stringify_keys(args.merge(primary_data)))
137
+ convert_hoptoad_keys_to_graylog2
138
+ set_file_and_line if @collect_file_and_line
139
+ check_presence_of_mandatory_attributes
140
+ @hash
141
+ end
142
+
143
+ def self.extract_hash_from_exception(exception)
144
+ bt = exception.backtrace || ["Backtrace is not available."]
145
+ { 'short_message' => "#{exception.class}: #{exception.message}", 'full_message' => "Backtrace:\n" + bt.join("\n") }
146
+ end
147
+
148
+ # Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
149
+ def convert_hoptoad_keys_to_graylog2
150
+ if @hash['short_message'].to_s.empty?
151
+ if @hash.has_key?('error_class') && @hash.has_key?('error_message')
152
+ @hash['short_message'] = @hash.delete('error_class') + ': ' + @hash.delete('error_message')
153
+ end
154
+ end
155
+ end
156
+
157
+ CALLER_REGEXP = /^(.*):(\d+).*/
158
+ LIB_PATTERN = File.join('lib', 'syslog-sd')
159
+
160
+ def set_file_and_line
161
+ stack = caller
162
+ begin
163
+ frame = stack.shift
164
+ end while frame.include?(LIB_PATTERN)
165
+ match = CALLER_REGEXP.match(frame)
166
+ @hash['file'] = match[1]
167
+ @hash['line'] = match[2].to_i
168
+ end
169
+
170
+ def check_presence_of_mandatory_attributes
171
+ %w(short_message host).each do |attribute|
172
+ if @hash[attribute].to_s.empty?
173
+ raise ArgumentError.new("#{attribute} is missing. Options short_message and host must be set.")
174
+ end
175
+ end
176
+ end
177
+
178
+ def serialize_hash
179
+ raise ArgumentError.new("Hash is empty.") if @hash.nil? || @hash.empty?
180
+
181
+ @hash['level'] = @level_mapping[@hash['level']]
182
+
183
+ prival = 128 + @hash.delete('level') # 128 = 16(local0) * 8
184
+ t = Time.now.utc
185
+ timestamp = t.strftime("%Y-%m-%dT%H:%M:%S.#{t.usec.to_s[0,3]}Z")
186
+ host = @hash.delete('host')
187
+ facility = @hash.delete('facility') || '-'
188
+ procid = @hash.delete('procid')
189
+ msgid = @hash.delete('msgid') || '-'
190
+ short_message = @hash.delete('short_message')
191
+ "<#{prival}>1 #{timestamp} #{host} #{facility} #{procid} #{msgid} #{serialize_sd} #{short_message}"
192
+ end
193
+
194
+ def serialize_sd
195
+ return '-' if @hash.empty?
196
+
197
+ res = "@sd_id"
198
+ @hash.each_pair do |key, value|
199
+ value = value.to_s.gsub(/([\\\]\"])/) { "\\#{$1}" }
200
+ res += " #{key}=\"#{value}\""
201
+ end
202
+ '[' + res + ']'
203
+ end
204
+
205
+ def self.stringify_keys(hash)
206
+ hash.keys.each do |key|
207
+ value, key_s = hash.delete(key), key.to_s
208
+ raise ArgumentError.new("Both #{key.inspect} and #{key_s} are present.") if hash.has_key?(key_s)
209
+ hash[key_s] = value
210
+ end
211
+ hash
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,18 @@
1
+ module SyslogSD
2
+ # Plain Ruby UDP sender.
3
+ class RubyUdpSender
4
+ attr_accessor :addresses
5
+
6
+ def initialize(addresses)
7
+ @addresses = addresses
8
+ @i = 0
9
+ @socket = UDPSocket.open
10
+ end
11
+
12
+ def send_datagram(datagram)
13
+ host, port = @addresses[@i]
14
+ @i = (@i + 1) % @addresses.length
15
+ @socket.send(datagram, 0, host, port)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ module SyslogSD
2
+ # There are two things you should know about log levels/severity:
3
+ # - syslog defines levels from 0 (Emergency) to 7 (Debug).
4
+ # 0 (Emergency) and 1 (Alert) levels are reserved for OS kernel.
5
+ # - Ruby default Logger defines levels from 0 (DEBUG) to 4 (FATAL) and 5 (UNKNOWN).
6
+ # Note that order is inverted.
7
+ # For compatibility we define our constants as Ruby Logger, and convert values before
8
+ # generating syslog message, using defined mapping.
9
+
10
+ module Levels
11
+ DEBUG = 0
12
+ INFO = 1
13
+ WARN = 2
14
+ ERROR = 3
15
+ FATAL = 4
16
+ UNKNOWN = 5
17
+ end
18
+
19
+ include Levels
20
+
21
+ # Maps Ruby Logger levels to syslog levels as SyslogLogger and syslogger gems. This one is default.
22
+ LOGGER_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
+
29
+ # Maps Ruby Logger levels to syslog levels as is.
30
+ DIRECT_MAPPING = {DEBUG => 7, # Debug
31
+ INFO => 6, # Info
32
+ # skip 5 Notice
33
+ WARN => 4, # Warning
34
+ ERROR => 3, # Error
35
+ FATAL => 2, # Critical
36
+ UNKNOWN => 1} # Alert – shouldn't be used
37
+ end
data/syslog-sd.gemspec ADDED
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{syslog-sd}
8
+ s.version = "1.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Alexey Palazhchenko}, %q{Lennart Koopmann}]
12
+ s.date = %q{2011-05-17}
13
+ s.description = %q{Super-Duper library to send syslog messages to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.}
14
+ s.email = %q{alexey.palazhchenko@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "CHANGELOG",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "benchmarks/notifier.rb",
26
+ "lib/syslog-sd.rb",
27
+ "lib/syslog-sd/logger.rb",
28
+ "lib/syslog-sd/notifier.rb",
29
+ "lib/syslog-sd/ruby_sender.rb",
30
+ "lib/syslog-sd/severity.rb",
31
+ "syslog-sd.gemspec",
32
+ "test/helper.rb",
33
+ "test/test_logger.rb",
34
+ "test/test_notifier.rb",
35
+ "test/test_ruby_sender.rb",
36
+ "test/test_severity.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/AlekSi/syslog-sd-rb}
39
+ s.require_paths = [%q{lib}]
40
+ s.rubygems_version = %q{1.8.2}
41
+ s.summary = %q{Library to send syslog messages to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.}
42
+
43
+ if s.respond_to? :specification_version then
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
48
+ s.add_development_dependency(%q<mocha>, [">= 0"])
49
+ s.add_development_dependency(%q<timecop>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<shoulda>, [">= 0"])
52
+ s.add_dependency(%q<mocha>, [">= 0"])
53
+ s.add_dependency(%q<timecop>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<shoulda>, [">= 0"])
57
+ s.add_dependency(%q<mocha>, [">= 0"])
58
+ s.add_dependency(%q<timecop>, [">= 0"])
59
+ end
60
+ end
61
+
data/test/helper.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'timecop'
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'syslog-sd'
10
+
11
+ class Test::Unit::TestCase
12
+ end
@@ -0,0 +1,139 @@
1
+ require 'helper'
2
+
3
+ class TestLogger < Test::Unit::TestCase
4
+ context "with logger with mocked sender" do
5
+ setup do
6
+ Socket.stubs(:gethostname).returns('stubbed_hostname')
7
+ @logger = SyslogSD::Logger.new
8
+ @sender = mock
9
+ @logger.instance_variable_set('@sender', @sender)
10
+ end
11
+
12
+ should "respond to #close" do
13
+ assert @logger.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, facility from defaults" do
19
+ @logger.expects(:notify_with_level!).with do |level, hash|
20
+ level == SyslogSD::INFO &&
21
+ hash['short_message'] == 'Message' &&
22
+ hash['facility'] == 'syslog-sd-rb'
23
+ end
24
+ @logger.add(SyslogSD::INFO, 'Message')
25
+ end
26
+
27
+ # logger.add(Logger::INFO, RuntimeError.new('Boom!'))
28
+ should "implement add method with level and exception from parameters, facility from defaults" do
29
+ @logger.expects(:notify_with_level!).with do |level, hash|
30
+ level == SyslogSD::INFO &&
31
+ hash['short_message'] == 'RuntimeError: Boom!' &&
32
+ hash['full_message'] =~ /^Backtrace/ &&
33
+ hash['facility'] == 'syslog-sd-rb'
34
+ end
35
+ @logger.add(SyslogSD::INFO, RuntimeError.new('Boom!'))
36
+ end
37
+
38
+ # logger.add(Logger::INFO) { 'Message' }
39
+ should "implement add method with level from parameter, message from block, facility from defaults" do
40
+ @logger.expects(:notify_with_level!).with do |level, hash|
41
+ level == SyslogSD::INFO &&
42
+ hash['short_message'] == 'Message' &&
43
+ hash['facility'] == 'syslog-sd-rb'
44
+ end
45
+ @logger.add(SyslogSD::INFO) { 'Message' }
46
+ end
47
+
48
+ # logger.add(Logger::INFO) { RuntimeError.new('Boom!') }
49
+ should "implement add method with level from parameter, exception from block, facility from defaults" do
50
+ @logger.expects(:notify_with_level!).with do |level, hash|
51
+ level == SyslogSD::INFO &&
52
+ hash['short_message'] == 'RuntimeError: Boom!' &&
53
+ hash['full_message'] =~ /^Backtrace/ &&
54
+ hash['facility'] == 'syslog-sd-rb'
55
+ end
56
+ @logger.add(SyslogSD::INFO) { RuntimeError.new('Boom!') }
57
+ end
58
+
59
+ # logger.add(Logger::INFO, 'Message', 'Facility')
60
+ should "implement add method with level, message and facility from parameters" do
61
+ @logger.expects(:notify_with_level!).with do |level, hash|
62
+ level == SyslogSD::INFO &&
63
+ hash['short_message'] == 'Message' &&
64
+ hash['facility'] == 'Facility'
65
+ end
66
+ @logger.add(SyslogSD::INFO, 'Message', 'Facility')
67
+ end
68
+
69
+ # logger.add(Logger::INFO, RuntimeError.new('Boom!'), 'Facility')
70
+ should "implement add method with level, exception and facility from parameters" do
71
+ @logger.expects(:notify_with_level!).with do |level, hash|
72
+ level == SyslogSD::INFO &&
73
+ hash['short_message'] == 'RuntimeError: Boom!' &&
74
+ hash['full_message'] =~ /^Backtrace/ &&
75
+ hash['facility'] == 'Facility'
76
+ end
77
+ @logger.add(SyslogSD::INFO, RuntimeError.new('Boom!'), 'Facility')
78
+ end
79
+
80
+ # logger.add(Logger::INFO, 'Facility') { 'Message' }
81
+ should "implement add method with level and facility from parameters, message from block" do
82
+ @logger.expects(:notify_with_level!).with do |level, hash|
83
+ level == SyslogSD::INFO &&
84
+ hash['short_message'] == 'Message' &&
85
+ hash['facility'] == 'Facility'
86
+ end
87
+ @logger.add(SyslogSD::INFO, 'Facility') { 'Message' }
88
+ end
89
+
90
+ # logger.add(Logger::INFO, 'Facility') { RuntimeError.new('Boom!') }
91
+ should "implement add method with level and facility from parameters, exception from block" do
92
+ @logger.expects(:notify_with_level!).with do |level, hash|
93
+ level == SyslogSD::INFO &&
94
+ hash['short_message'] == 'RuntimeError: Boom!' &&
95
+ hash['full_message'] =~ /^Backtrace/ &&
96
+ hash['facility'] == 'Facility'
97
+ end
98
+ @logger.add(SyslogSD::INFO, 'Facility') { RuntimeError.new('Boom!') }
99
+ end
100
+ end
101
+
102
+ SyslogSD::Levels.constants.each do |const|
103
+ # logger.error "Argument #{ @foo } mismatch."
104
+ should "call add with level #{const} from method name, message from parameter" do
105
+ @logger.expects(:add).with(SyslogSD.const_get(const), 'message')
106
+ @logger.__send__(const.downcase, 'message')
107
+ end
108
+
109
+ # logger.fatal { "Argument 'foo' not given." }
110
+ should "call add with level #{const} from method name, message from block" do
111
+ @logger.expects(:add).with(SyslogSD.const_get(const), 'message')
112
+ @logger.__send__(const.downcase) { 'message' }
113
+ end
114
+
115
+ # logger.info('initialize') { "Initializing..." }
116
+ should "call add with level #{const} from method name, facility from parameter, message from block" do
117
+ @logger.expects(:add).with(SyslogSD.const_get(const), 'message', 'facility')
118
+ @logger.__send__(const.downcase, 'facility') { 'message' }
119
+ end
120
+
121
+ should "respond to #{const.downcase}?" do
122
+ @logger.level = SyslogSD.const_get(const) - 1
123
+ assert @logger.__send__(const.to_s.downcase + '?')
124
+ @logger.level = SyslogSD.const_get(const)
125
+ assert @logger.__send__(const.to_s.downcase + '?')
126
+ @logger.level = SyslogSD.const_get(const) + 1
127
+ assert !@logger.__send__(const.to_s.downcase + '?')
128
+ end
129
+ end
130
+
131
+ should "support Logger#<<" do
132
+ @logger.expects(:notify_with_level!).with do |level, hash|
133
+ level == SyslogSD::UNKNOWN &&
134
+ hash['short_message'] == "Message"
135
+ end
136
+ @logger << "Message"
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,214 @@
1
+ require 'helper'
2
+
3
+ RANDOM_DATA = ('A'..'Z').to_a
4
+
5
+ class TestNotifier < Test::Unit::TestCase
6
+ should "allow access to host, port and default_options" do
7
+ Socket.expects(:gethostname).returns('default_hostname')
8
+ n = SyslogSD::Notifier.new
9
+ assert_equal [['localhost', 514]], n.addresses
10
+ assert_equal( { 'level' => SyslogSD::UNKNOWN,
11
+ 'host' => 'default_hostname', 'facility' => 'syslog-sd-rb',
12
+ 'procid' => Process.pid },
13
+ n.default_options )
14
+ n.addresses, n.default_options = [['graylog2.org', 7777]], {:host => 'grayhost'}
15
+ assert_equal [['graylog2.org', 7777]], n.addresses
16
+ assert_equal({'host' => 'grayhost'}, n.default_options)
17
+ end
18
+
19
+ context "with notifier with mocked sender" do
20
+ setup do
21
+ Socket.stubs(:gethostname).returns('stubbed_hostname')
22
+ @notifier = SyslogSD::Notifier.new('host', 12345)
23
+ @sender = mock
24
+ @notifier.instance_variable_set('@sender', @sender)
25
+ end
26
+
27
+ context "extract_hash" do
28
+ should "check arguments" do
29
+ assert_raise(ArgumentError) { @notifier.__send__(:extract_hash) }
30
+ assert_raise(ArgumentError) { @notifier.__send__(:extract_hash, 1, 2, 3) }
31
+ end
32
+
33
+ should "work with hash" do
34
+ hash = @notifier.__send__(:extract_hash, { 'version' => '1.0', 'short_message' => 'message' })
35
+ assert_equal '1.0', hash['version']
36
+ assert_equal 'message', hash['short_message']
37
+ end
38
+
39
+ should "work with any object which responds to #to_hash" do
40
+ o = Object.new
41
+ o.expects(:to_hash).returns({ 'version' => '1.0', 'short_message' => 'message' })
42
+ hash = @notifier.__send__(:extract_hash, o)
43
+ assert_equal '1.0', hash['version']
44
+ assert_equal 'message', hash['short_message']
45
+ end
46
+
47
+ should "work with exception with backtrace" do
48
+ e = RuntimeError.new('message')
49
+ e.set_backtrace(caller)
50
+ hash = @notifier.__send__(:extract_hash, e)
51
+ assert_equal 'RuntimeError: message', hash['short_message']
52
+ assert_match /Backtrace/, hash['full_message']
53
+ assert_equal SyslogSD::ERROR, hash['level']
54
+ end
55
+
56
+ should "work with exception without backtrace" do
57
+ e = RuntimeError.new('message')
58
+ hash = @notifier.__send__(:extract_hash, e)
59
+ assert_match /Backtrace is not available/, hash['full_message']
60
+ end
61
+
62
+ should "work with exception and hash" do
63
+ e, h = RuntimeError.new('message'), {'param' => 1, 'level' => SyslogSD::FATAL, 'short_message' => 'will be hidden by exception'}
64
+ hash = @notifier.__send__(:extract_hash, e, h)
65
+ assert_equal 'RuntimeError: message', hash['short_message']
66
+ assert_equal SyslogSD::FATAL, hash['level']
67
+ assert_equal 1, hash['param']
68
+ end
69
+
70
+ should "work with plain text" do
71
+ hash = @notifier.__send__(:extract_hash, 'message')
72
+ assert_equal 'message', hash['short_message']
73
+ assert_equal SyslogSD::INFO, hash['level']
74
+ end
75
+
76
+ should "work with plain text and hash" do
77
+ hash = @notifier.__send__(:extract_hash, 'message', 'level' => SyslogSD::WARN)
78
+ assert_equal 'message', hash['short_message']
79
+ assert_equal SyslogSD::WARN, hash['level']
80
+ end
81
+
82
+ should "covert hash keys to strings" do
83
+ hash = @notifier.__send__(:extract_hash, :short_message => :message)
84
+ assert hash.has_key?('short_message')
85
+ assert !hash.has_key?(:short_message)
86
+ end
87
+
88
+ should "not overwrite keys on convert" do
89
+ assert_raise(ArgumentError) { @notifier.__send__(:extract_hash, :short_message => :message1, 'short_message' => 'message2') }
90
+ end
91
+
92
+ should "use default_options" do
93
+ @notifier.default_options = {:foo => 'bar', 'short_message' => 'will be hidden by explicit argument', 'host' => 'some_host'}
94
+ hash = @notifier.__send__(:extract_hash, { 'version' => '1.0', 'short_message' => 'message' })
95
+ assert_equal 'bar', hash['foo']
96
+ assert_equal 'message', hash['short_message']
97
+ end
98
+
99
+ should "be compatible with HoptoadNotifier" do
100
+ # https://github.com/thoughtbot/hoptoad_notifier/blob/master/README.rdoc, section Going beyond exceptions
101
+ hash = @notifier.__send__(:extract_hash, :error_class => 'Class', :error_message => 'Message')
102
+ assert_equal 'Class: Message', hash['short_message']
103
+ end
104
+
105
+ should "set file and line" do
106
+ line = __LINE__
107
+ hash = @notifier.__send__(:extract_hash, { 'version' => '1.0', 'short_message' => 'message' })
108
+ assert_match /test_notifier.rb/, hash['file']
109
+ assert_equal line + 1, hash['line']
110
+ end
111
+ end
112
+
113
+ context "serialize_hash" do
114
+ setup do
115
+ @notifier.level_mapping = :direct
116
+ Timecop.freeze(Time.utc(2010, 5, 16, 12, 13, 14))
117
+ end
118
+
119
+ expected = {
120
+ {'short_message' => 'message', 'level' => SyslogSD::WARN,
121
+ 'host' => 'host', 'facility' => 'facility', 'procid' => 123,
122
+ 'msgid' => 'msgid'} => '<132>1 2010-05-16T12:13:14.0Z host facility 123 msgid - message',
123
+ {'short_message' => 'message', 'level' => SyslogSD::WARN,
124
+ 'host' => 'host', 'facility' => 'facility', 'procid' => 123,
125
+ 'msgid' => 'msgid', 'user_id' => 123} => '<132>1 2010-05-16T12:13:14.0Z host facility 123 msgid [@sd_id user_id="123"] message',
126
+ {'short_message' => 'message', 'level' => SyslogSD::WARN,
127
+ 'host' => 'host', 'facility' => 'facility', 'procid' => 123,
128
+ 'msgid' => 'msgid', 'user_id' => '\\"]'} => '<132>1 2010-05-16T12:13:14.0Z host facility 123 msgid [@sd_id user_id="\\\\\"\]"] message'
129
+ }
130
+
131
+ expected.each_pair do |key, value|
132
+ should "be as #{value}" do
133
+ @notifier.instance_variable_set('@hash', key)
134
+ assert_equal value, @notifier.__send__(:serialize_hash)
135
+ end
136
+ end
137
+
138
+ teardown do
139
+ Timecop.return
140
+ end
141
+ end
142
+
143
+ context "level threshold" do
144
+ setup do
145
+ @notifier.level = SyslogSD::WARN
146
+ @hash = { 'version' => '1.0', 'short_message' => 'message' }
147
+ end
148
+
149
+ ['debug', 'DEBUG', :debug].each do |l|
150
+ should "allow to set threshold as #{l.inspect}" do
151
+ @notifier.level = l
152
+ assert_equal SyslogSD::DEBUG, @notifier.level
153
+ end
154
+ end
155
+
156
+ should "not send notifications with level below threshold" do
157
+ @sender.expects(:send_datagram).never
158
+ @notifier.notify!(@hash.merge('level' => SyslogSD::DEBUG))
159
+ end
160
+
161
+ should "not notifications with level equal or above threshold" do
162
+ @sender.expects(:send_datagram).once
163
+ @notifier.notify!(@hash.merge('level' => SyslogSD::WARN))
164
+ end
165
+ end
166
+
167
+ context "when disabled" do
168
+ setup do
169
+ @notifier.disable
170
+ end
171
+
172
+ should "not send datagram" do
173
+ @sender.expects(:send_datagram).never
174
+ @notifier.expects(:extract_hash).never
175
+ @notifier.notify!({ 'version' => '1.0', 'short_message' => 'message' })
176
+ end
177
+
178
+ context "and enabled again" do
179
+ setup do
180
+ @notifier.enable
181
+ end
182
+
183
+ should "send datagram" do
184
+ @sender.expects(:send_datagram)
185
+ @notifier.notify!({ 'version' => '1.0', 'short_message' => 'message' })
186
+ end
187
+ end
188
+ end
189
+
190
+ should "pass valid data to sender" do
191
+ @sender.expects(:send_datagram).with do |datagram|
192
+ datagram.is_a?(String)
193
+ end
194
+ @notifier.notify!({ 'version' => '1.0', 'short_message' => 'message' })
195
+ end
196
+
197
+ SyslogSD::Levels.constants.each do |const|
198
+ should "call notify with level #{const} from method name" do
199
+ @notifier.expects(:notify_with_level).with(SyslogSD.const_get(const), { 'version' => '1.0', 'short_message' => 'message' })
200
+ @notifier.__send__(const.downcase, { 'version' => '1.0', 'short_message' => 'message' })
201
+ end
202
+ end
203
+
204
+ should "not rescue from invalid invocation of #notify!" do
205
+ assert_raise(ArgumentError) { @notifier.notify!(:no_short_message => 'too bad') }
206
+ end
207
+
208
+ should "rescue from invalid invocation of #notify" do
209
+ @notifier.expects(:notify_with_level!).with(nil, instance_of(Hash)).raises(ArgumentError)
210
+ @notifier.expects(:notify_with_level!).with(SyslogSD::UNKNOWN, instance_of(ArgumentError))
211
+ assert_nothing_raised { @notifier.notify(:no_short_message => 'too bad') }
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ class TestRubyUdpSender < Test::Unit::TestCase
4
+ context "with ruby sender" do
5
+ setup do
6
+ @addresses = [['localhost', 12201], ['localhost', 12202]]
7
+ @sender = SyslogSD::RubyUdpSender.new(@addresses)
8
+ end
9
+
10
+ context "send_datagram" do
11
+ setup do
12
+ @sender.send_datagram("d1")
13
+ @sender.send_datagram("e1")
14
+ @sender.send_datagram("d2")
15
+ @sender.send_datagram("e2")
16
+ end
17
+
18
+ before_should "be called 2 times with 1st and 2nd address" do
19
+ UDPSocket.any_instance.expects(:send).times(2).with do |datagram, _, host, port|
20
+ datagram.start_with?('d') && host == 'localhost' && port == 12201
21
+ end
22
+ UDPSocket.any_instance.expects(:send).times(2).with do |datagram, _, host, port|
23
+ datagram.start_with?('e') && host == 'localhost' && port == 12202
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ class TestSeverity < Test::Unit::TestCase
4
+ should "map Ruby Logger levels to syslog levels as SyslogLogger" do
5
+ SyslogSD::LOGGER_MAPPING.each do |ruby_level, syslog_level|
6
+ assert_not_equal syslog_level, ruby_level
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: syslog-sd
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 2
9
+ - 0
10
+ version: 1.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Palazhchenko
14
+ - Lennart Koopmann
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-05-17 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: shoulda
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: timecop
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: Super-Duper library to send syslog messages to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.
64
+ email: alexey.palazhchenko@gmail.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files:
70
+ - LICENSE
71
+ - README.rdoc
72
+ files:
73
+ - CHANGELOG
74
+ - LICENSE
75
+ - README.rdoc
76
+ - Rakefile
77
+ - VERSION
78
+ - benchmarks/notifier.rb
79
+ - lib/syslog-sd.rb
80
+ - lib/syslog-sd/logger.rb
81
+ - lib/syslog-sd/notifier.rb
82
+ - lib/syslog-sd/ruby_sender.rb
83
+ - lib/syslog-sd/severity.rb
84
+ - syslog-sd.gemspec
85
+ - test/helper.rb
86
+ - test/test_logger.rb
87
+ - test/test_notifier.rb
88
+ - test/test_ruby_sender.rb
89
+ - test/test_severity.rb
90
+ homepage: http://github.com/AlekSi/syslog-sd-rb
91
+ licenses: []
92
+
93
+ post_install_message:
94
+ rdoc_options: []
95
+
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project:
119
+ rubygems_version: 1.8.2
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Library to send syslog messages to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.
123
+ test_files: []
124
+