syslog-sd 1.3.2 → 1.4.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/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.2
1
+ 1.4.0.beta1
@@ -0,0 +1,17 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ require 'syslog-sd'
7
+
8
+ TARGET_HOST = 'localhost'
9
+ TARGET_PORT = 5140
10
+ RANGE = 35000...36000
11
+
12
+ n = SyslogSD::Notifier.new(TARGET_HOST, TARGET_PORT)
13
+ RANGE.each do |size|
14
+ n.notify!('a' * size)
15
+ puts size if (size % 100) == 0
16
+ sleep 0.01
17
+ end
@@ -1,36 +1,48 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
- puts "Loading..."
4
-
5
3
  require 'benchmark'
4
+ require 'thread'
6
5
  require 'rubygems'
7
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
9
8
  require 'syslog-sd'
10
9
 
10
+ Thread.abort_on_exception = true
11
+
12
+ puts "Running on #{RUBY_DESCRIPTION}"
11
13
  puts "Generating random data..."
12
14
  srand(1)
13
15
  RANDOM_DATA = ('A'..'z').to_a
14
16
  k3_message = (1..3*1024).map { RANDOM_DATA[rand(RANDOM_DATA.count)] }.join
15
17
 
16
18
  TARGET_HOST = 'localhost'
17
- TARGET_PORT = 12201
19
+ TARGET_PORT = 5140
18
20
  DEFAULT_OPTIONS = { '_host' => 'localhost' }
19
- TIMES = 5000
21
+ MESSAGES_COUNT = 5000
22
+ THREADS_COUNT = 50
20
23
 
21
24
  SHORT_HASH = { 'short_message' => 'message' }
22
25
  LONG_HASH = { 'short_message' => 'message', 'long_message' => k3_message }
23
26
 
27
+ def notify(n, hash)
28
+ threads = []
29
+ THREADS_COUNT.times do
30
+ threads << Thread.new do
31
+ (MESSAGES_COUNT / THREADS_COUNT).times { n.notify!(hash) }
32
+ end
33
+ end
34
+ threads.each { |t| t.join }
35
+ end
24
36
 
25
- notifier = SyslogSD::Notifier.new(TARGET_HOST, TARGET_PORT, DEFAULT_OPTIONS)
37
+ n = SyslogSD::Notifier.new(TARGET_HOST, TARGET_PORT, DEFAULT_OPTIONS)
26
38
 
27
39
  # to create mongo collections, etc.
28
- notifier.notify!(LONG_HASH)
40
+ n.notify!(LONG_HASH)
29
41
  sleep(5)
30
42
 
31
- puts "Sending #{TIMES} notifications...\n"
43
+ puts "Sending #{MESSAGES_COUNT} notifications...\n"
32
44
  tms = Benchmark.bm(25) do |b|
33
- b.report('short data') { TIMES.times { notifier.notify!(SHORT_HASH) } }
45
+ b.report('short data') { notify(n, SHORT_HASH) }
34
46
  sleep(5)
35
- b.report('long data ') { TIMES.times { notifier.notify!(LONG_HASH) } }
47
+ b.report(' long data') { notify(n, LONG_HASH) }
36
48
  end
@@ -5,7 +5,7 @@ module SyslogSD
5
5
  def close
6
6
  end
7
7
 
8
- # Use it like Logger#add... or better not to use at all.
8
+ # Use it like Logger#add... or better not use directly at all.
9
9
  def add(level, *args)
10
10
  raise ArgumentError.new('Wrong arguments.') unless (0..2).include?(args.count)
11
11
 
@@ -50,6 +50,5 @@ module SyslogSD
50
50
  # config.colorize_logging = false
51
51
  class Logger < Notifier
52
52
  include LoggerCompatibility
53
- @last_chunk_id = 0
54
53
  end
55
54
  end
@@ -120,16 +120,18 @@ module SyslogSD
120
120
 
121
121
  def notify_with_level!(message_level, *args)
122
122
  return unless @enabled
123
- extract_hash(*args)
124
- @hash['level'] = message_level unless message_level.nil?
125
- if @hash['level'] >= level
126
- str = serialize_hash
123
+ hash = extract_hash(*args)
124
+ hash['level'] = message_level unless message_level.nil?
125
+ if hash['level'] >= level
126
+ str = serialize_hash(hash)
127
127
  @sender.send_datagram(str)
128
128
  str
129
129
  end
130
130
  end
131
131
 
132
132
  def extract_hash(object = nil, args = {})
133
+ args = self.class.stringify_keys(args)
134
+
133
135
  primary_data = if object.respond_to?(:to_hash)
134
136
  object.to_hash
135
137
  elsif object.is_a?(Exception)
@@ -140,74 +142,108 @@ module SyslogSD
140
142
  { 'short_message' => object.to_s }
141
143
  end
142
144
 
143
- @hash = default_options.merge(self.class.stringify_keys(args.merge(primary_data)))
144
- convert_hoptoad_keys_to_graylog2
145
- set_file_and_line if @collect_file_and_line
146
- set_timestamp
147
- check_presence_of_mandatory_attributes
148
- @hash
145
+ hash = self.class.stringify_keys(primary_data)
146
+ hash = default_options.merge(args.merge(hash))
147
+ hash = convert_airbrake_keys_to_graylog2(hash)
148
+ hash = set_file_and_line(hash)
149
+ hash = set_timestamp(hash)
150
+ check_presence_of_mandatory_attributes(hash)
151
+ hash
149
152
  end
150
153
 
154
+ CALLER_REGEXP = /^(.*):(\d+).*/
155
+
151
156
  def self.extract_hash_from_exception(exception)
152
- bt = exception.backtrace || ["Backtrace is not available."]
153
- { 'short_message' => "#{exception.class}: #{exception.message}", 'full_message' => "Backtrace:\n" + bt.join("\n") }
157
+ error_class = exception.class.name
158
+ error_message = exception.message
159
+
160
+ # always collect file and line there (ignore @collect_file_and_line)
161
+ # since we already know them, no need to call `caller`
162
+ file, line = nil, nil
163
+ bt = exception.backtrace
164
+ if bt
165
+ match = CALLER_REGEXP.match(bt[0])
166
+ if match
167
+ file = match[1]
168
+ line = match[2].to_i
169
+ end
170
+ else
171
+ bt = ["Backtrace is not available."]
172
+ end
173
+
174
+ { 'short_message' => "#{error_class}: #{error_message}", 'full_message' => "Backtrace:\n" + bt.join("\n"),
175
+ 'error_class' => error_class, 'error_message' => error_message,
176
+ 'file' => file, 'line' => line }
154
177
  end
155
178
 
156
- # Converts Hoptoad-specific keys in +@hash+ to Graylog2-specific.
157
- def convert_hoptoad_keys_to_graylog2
158
- if @hash['short_message'].to_s.empty?
159
- if @hash.has_key?('error_class') && @hash.has_key?('error_message')
160
- @hash['short_message'] = @hash.delete('error_class') + ': ' + @hash.delete('error_message')
179
+ # Converts Airbrake-specific keys in +hash+ to Graylog2-specific.
180
+ def convert_airbrake_keys_to_graylog2(hash)
181
+ if hash['short_message'].to_s.empty?
182
+ if hash.has_key?('error_class') && hash.has_key?('error_message')
183
+ hash['short_message'] = hash['error_class'] + ': ' + hash['error_message']
161
184
  end
162
185
  end
186
+ hash
163
187
  end
164
188
 
165
- CALLER_REGEXP = /^(.*):(\d+).*/
166
189
  LIB_PATTERN = File.join('lib', 'syslog-sd')
167
190
 
168
- def set_file_and_line
169
- stack = caller
170
- begin
171
- frame = stack.shift
172
- end while frame.include?(LIB_PATTERN)
173
- match = CALLER_REGEXP.match(frame)
174
- @hash['file'] = match[1]
175
- @hash['line'] = match[2].to_i
191
+ def set_file_and_line(hash)
192
+ return hash unless hash['file'].nil? || hash['line'].nil?
193
+
194
+ if @collect_file_and_line
195
+ stack = caller
196
+ begin
197
+ frame = stack.shift
198
+ end while frame.include?(LIB_PATTERN)
199
+
200
+ match = CALLER_REGEXP.match(frame)
201
+ if match
202
+ hash['file'] = match[1]
203
+ hash['line'] = match[2].to_i
204
+ else
205
+ hash['file'] = 'unknown'
206
+ hash['line'] = -1
207
+ end
208
+ end
209
+
210
+ hash
176
211
  end
177
212
 
178
- def set_timestamp
179
- @hash['timestamp'] = Time.now.utc.to_f if @hash['timestamp'].nil?
213
+ def set_timestamp(hash)
214
+ hash['timestamp'] ||= Time.now.utc.to_f
215
+ hash
180
216
  end
181
217
 
182
- def check_presence_of_mandatory_attributes
218
+ def check_presence_of_mandatory_attributes(hash)
183
219
  %w(short_message host).each do |attribute|
184
- if @hash[attribute].to_s.empty?
220
+ if hash[attribute].to_s.empty?
185
221
  raise ArgumentError.new("#{attribute} is missing. Options short_message and host must be set.")
186
222
  end
187
223
  end
188
224
  end
189
225
 
190
- def serialize_hash
191
- raise ArgumentError.new("Hash is empty.") if @hash.nil? || @hash.empty?
226
+ def serialize_hash(hash)
227
+ raise ArgumentError.new("Hash is empty.") if hash.nil? || hash.empty?
192
228
 
193
- @hash['level'] = @level_mapping[@hash['level']]
229
+ hash['level'] = @level_mapping[hash['level']]
194
230
 
195
- prival = 128 + @hash.delete('level') # 128 = 16(local0) * 8
231
+ prival = 128 + hash.delete('level') # 128 = 16(local0) * 8
196
232
  t = Time.now.utc
197
233
  timestamp = timestamp_as_float ? t.to_f : t.strftime("%Y-%m-%dT%H:%M:%S.#{t.usec.to_s[0,3]}Z")
198
- host = @hash.delete('host')
199
- facility = @hash.delete('facility') || '-'
200
- procid = @hash.delete('procid')
201
- msgid = @hash.delete('msgid') || '-'
202
- short_message = @hash.delete('short_message')
203
- "<#{prival}>1 #{timestamp} #{host} #{facility} #{procid} #{msgid} #{serialize_sd} #{short_message}"
234
+ host = hash.delete('host')
235
+ facility = hash.delete('facility') || '-'
236
+ procid = hash.delete('procid')
237
+ msgid = hash.delete('msgid') || '-'
238
+ short_message = hash.delete('short_message')
239
+ "<#{prival}>1 #{timestamp} #{host} #{facility} #{procid} #{msgid} #{serialize_sd(hash)} #{short_message}"
204
240
  end
205
241
 
206
- def serialize_sd
207
- return '-' if @hash.empty?
242
+ def serialize_sd(hash)
243
+ return '-' if hash.empty?
208
244
 
209
245
  res = @sd_id
210
- @hash.each_pair do |key, value|
246
+ hash.each_pair do |key, value|
211
247
  value = value.to_s.gsub(/([\\\]\"])/) { "\\#{$1}" }
212
248
  res += " #{key}=\"#{value}\""
213
249
  end
@@ -1,15 +1,29 @@
1
1
  module SyslogSD
2
2
  # Plain Ruby UDP sender.
3
3
  class RubyUdpSender
4
- attr_accessor :addresses
5
-
6
- def initialize(addresses)
7
- @addresses = addresses
8
- @i = 0
4
+ def initialize(addrs)
5
+ @mutex = ::Mutex.new
9
6
  @socket = UDPSocket.open
7
+ @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, 65507) # 65535 - 20 (ip header) - 8 (udp header)
8
+ self.addresses = addrs
9
+ end
10
+
11
+ def addresses
12
+ @mutex.synchronize do
13
+ @i = 0
14
+ @addresses
15
+ end
16
+ end
17
+
18
+ def addresses=(addrs)
19
+ @mutex.synchronize do
20
+ @i = 0
21
+ @addresses = addrs
22
+ end
10
23
  end
11
24
 
12
25
  def send_datagram(datagram)
26
+ # not thread-safe, but we don't care if round-robin algo fails sometimes
13
27
  host, port = @addresses[@i]
14
28
  @i = (@i + 1) % @addresses.length
15
29
  @socket.send(datagram, 0, host, port)
data/lib/syslog-sd.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'socket'
2
2
  require 'digest/md5'
3
+ require 'thread'
3
4
 
4
5
  require 'syslog-sd/severity'
5
6
  require 'syslog-sd/ruby_sender'
data/syslog-sd.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "syslog-sd"
8
- s.version = "1.3.2"
8
+ s.version = "1.4.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 = "2011-12-02"
12
+ s.date = "2012-01-17"
13
13
  s.description = "Super-Duper library to send syslog messages over UDP to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424."
14
14
  s.email = "alexey.palazhchenko@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  "README.rdoc",
25
25
  "Rakefile",
26
26
  "VERSION",
27
+ "benchmarks/incremental.rb",
27
28
  "benchmarks/notifier.rb",
28
29
  "lib/syslog-sd.rb",
29
30
  "lib/syslog-sd/logger.rb",
@@ -39,7 +40,7 @@ Gem::Specification.new do |s|
39
40
  ]
40
41
  s.homepage = "http://github.com/Graylog2/syslog-sd-rb"
41
42
  s.require_paths = ["lib"]
42
- s.rubygems_version = "1.8.11"
43
+ s.rubygems_version = "1.8.15"
43
44
  s.summary = "Library to send syslog messages over UDP to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424."
44
45
 
45
46
  if s.respond_to? :specification_version then
@@ -49,20 +49,30 @@ class TestNotifier < Test::Unit::TestCase
49
49
  e.set_backtrace(caller)
50
50
  hash = @notifier.__send__(:extract_hash, e)
51
51
  assert_equal 'RuntimeError: message', hash['short_message']
52
+ assert_equal 'RuntimeError', hash['error_class']
53
+ assert_equal 'message', hash['error_message']
54
+ assert_match /shoulda/, hash['file']
55
+ assert hash['line'] > 300 # 382 in shoulda 2.11.3
52
56
  assert_match /Backtrace/, hash['full_message']
53
57
  assert_equal SyslogSD::ERROR, hash['level']
54
58
  end
55
59
 
56
60
  should "work with exception without backtrace" do
57
61
  e = RuntimeError.new('message')
58
- hash = @notifier.__send__(:extract_hash, e)
62
+ hash, line = @notifier.__send__(:extract_hash, e), __LINE__
63
+ assert_equal __FILE__, hash['file']
64
+ assert_equal line, hash['line']
59
65
  assert_match /Backtrace is not available/, hash['full_message']
60
66
  end
61
67
 
62
68
  should "work with exception and hash" do
63
69
  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)
70
+ hash, line = @notifier.__send__(:extract_hash, e, h), __LINE__
65
71
  assert_equal 'RuntimeError: message', hash['short_message']
72
+ assert_equal 'RuntimeError', hash['error_class']
73
+ assert_equal 'message', hash['error_message']
74
+ assert_equal __FILE__, hash['file']
75
+ assert_equal line, hash['line']
66
76
  assert_equal SyslogSD::FATAL, hash['level']
67
77
  assert_equal 1, hash['param']
68
78
  end
@@ -77,6 +87,10 @@ class TestNotifier < Test::Unit::TestCase
77
87
  hash = @notifier.__send__(:extract_hash, 'message', 'level' => SyslogSD::WARN)
78
88
  assert_equal 'message', hash['short_message']
79
89
  assert_equal SyslogSD::WARN, hash['level']
90
+
91
+ hash = @notifier.__send__(:extract_hash, 'message', :level => SyslogSD::WARN)
92
+ assert_equal 'message', hash['short_message']
93
+ assert_equal SyslogSD::WARN, hash['level']
80
94
  end
81
95
 
82
96
  should "covert hash keys to strings" do
@@ -96,10 +110,12 @@ class TestNotifier < Test::Unit::TestCase
96
110
  assert_equal 'message', hash['short_message']
97
111
  end
98
112
 
99
- should "be compatible with HoptoadNotifier" do
100
- # https://github.com/thoughtbot/hoptoad_notifier/blob/master/README.rdoc, section Going beyond exceptions
113
+ should "be compatible with Airbrake" do
114
+ # https://github.com/airbrake/airbrake/blob/master/README.md, section Going beyond exceptions
101
115
  hash = @notifier.__send__(:extract_hash, :error_class => 'Class', :error_message => 'Message')
102
116
  assert_equal 'Class: Message', hash['short_message']
117
+ assert_equal 'Class', hash['error_class']
118
+ assert_equal 'Message', hash['error_message']
103
119
  end
104
120
 
105
121
  should "set file and line" do
@@ -143,8 +159,7 @@ class TestNotifier < Test::Unit::TestCase
143
159
 
144
160
  expected.each_pair do |key, value|
145
161
  should "be as #{value}" do
146
- @notifier.instance_variable_set('@hash', key)
147
- assert_equal value, @notifier.__send__(:serialize_hash)
162
+ assert_equal value, @notifier.__send__(:serialize_hash, key)
148
163
  end
149
164
  end
150
165
 
@@ -153,8 +168,7 @@ class TestNotifier < Test::Unit::TestCase
153
168
  hash = { 'short_message' => 'message', 'level' => SyslogSD::WARN, 'host' => 'host', 'facility' => 'facility', 'procid' => 123,
154
169
  'msgid' => 'msgid', 'user_id' => 123 }
155
170
  expected = '<132>1 1274011994.0 host facility 123 msgid [_@37797 user_id="123"] message'
156
- @notifier.instance_variable_set('@hash', hash)
157
- assert_equal expected, @notifier.__send__(:serialize_hash)
171
+ assert_equal expected, @notifier.__send__(:serialize_hash, hash)
158
172
  end
159
173
 
160
174
  teardown do
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syslog-sd
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
5
- prerelease:
4
+ hash: -3060412182
5
+ prerelease: 6
6
6
  segments:
7
7
  - 1
8
- - 3
9
- - 2
10
- version: 1.3.2
8
+ - 4
9
+ - 0
10
+ - beta
11
+ - 1
12
+ version: 1.4.0.beta1
11
13
  platform: ruby
12
14
  authors:
13
15
  - Alexey Palazhchenko
@@ -16,7 +18,7 @@ autorequire:
16
18
  bindir: bin
17
19
  cert_chain: []
18
20
 
19
- date: 2011-12-02 00:00:00 Z
21
+ date: 2012-01-17 00:00:00 Z
20
22
  dependencies:
21
23
  - !ruby/object:Gem::Dependency
22
24
  name: shoulda
@@ -77,6 +79,7 @@ files:
77
79
  - README.rdoc
78
80
  - Rakefile
79
81
  - VERSION
82
+ - benchmarks/incremental.rb
80
83
  - benchmarks/notifier.rb
81
84
  - lib/syslog-sd.rb
82
85
  - lib/syslog-sd/logger.rb
@@ -109,16 +112,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
112
  required_rubygems_version: !ruby/object:Gem::Requirement
110
113
  none: false
111
114
  requirements:
112
- - - ">="
115
+ - - ">"
113
116
  - !ruby/object:Gem::Version
114
- hash: 3
117
+ hash: 25
115
118
  segments:
116
- - 0
117
- version: "0"
119
+ - 1
120
+ - 3
121
+ - 1
122
+ version: 1.3.1
118
123
  requirements: []
119
124
 
120
125
  rubyforge_project:
121
- rubygems_version: 1.8.11
126
+ rubygems_version: 1.8.15
122
127
  signing_key:
123
128
  specification_version: 3
124
129
  summary: Library to send syslog messages over UDP to logging server such as Graylog2. Supports Structured Data elements as defined by RFC 5424.