Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ task :coverage do
sh "open coverage/index.html"
end

require 'rake/rdoctask'
require 'rdoc/task'
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "#{name} #{version}"
Expand Down
18 changes: 18 additions & 0 deletions lib/remote_syslog_logger/limit_bytesize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Adapted from http://stackoverflow.com/a/12536366/2778142
def limit_bytesize(str, size)
# Change to canonical unicode form (compose any decomposed characters).
# Works only if you're using active_support
str = str.mb_chars.compose.to_s if str.respond_to?(:mb_chars)

# Start with a string of the correct byte size, but
# with a possibly incomplete char at the end.
new_str = str.byteslice(0, size)

# We need to force_encoding from utf-8 to utf-8 so ruby will re-validate
# (idea from halfelf).
until new_str[-1].force_encoding(new_str.encoding).valid_encoding?
# remove the invalid char
new_str = new_str.slice(0..-2)
end
new_str
end
26 changes: 22 additions & 4 deletions lib/remote_syslog_logger/udp_sender.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
require 'socket'
require 'syslog_protocol'
require File.expand_path('../limit_bytesize', __FILE__)

module RemoteSyslogLogger
class UdpSender
def initialize(remote_hostname, remote_port, options = {})
@remote_hostname = remote_hostname
@remote_port = remote_port
@whinyerrors = options[:whinyerrors]

@max_packet_size = options[:max_packet_size] || 1024
@continuation_prefix = options[:continuation_prefix] || '... '

@socket = UDPSocket.new
@packet = SyslogProtocol::Packet.new

Expand All @@ -17,16 +20,31 @@ def initialize(remote_hostname, remote_port, options = {})

@packet.facility = options[:facility] || 'user'
@packet.severity = options[:severity] || 'notice'
@packet.tag = options[:program] || "#{File.basename($0)}[#{$$}]"
@packet.tag = options[:program] || default_tag
end

def default_tag
pid_suffix = "[#{$$}]"
max_basename_size = 32 - pid_suffix.size
"#{File.basename($0)}"[0...max_basename_size].gsub(/[^\x21-\x7E]/, '_') + pid_suffix
end

def transmit(message)
message.split(/\r?\n/).each do |line|
begin
next if line =~ /^\s*$/
packet = @packet.dup
packet.content = line
@socket.send(packet.assemble, 0, @remote_hostname, @remote_port)
max_content_size = @max_packet_size - packet.assemble(@max_packet_size).size
line_prefix = ''
remaining_line = line
until remaining_line.empty?
chunk_byte_size = max_content_size - line_prefix.bytesize
chunk = limit_bytesize(remaining_line, chunk_byte_size)
packet.content = line_prefix + chunk
@socket.send(packet.assemble(@max_packet_size), 0, @remote_hostname, @remote_port)
remaining_line = remaining_line[chunk.size..-1]
line_prefix = @continuation_prefix
end
rescue
$stderr.puts "#{self.class} error: #{$!.class}: #{$!}\nOriginal message: #{line}"
raise if @whinyerrors
Expand Down
4 changes: 4 additions & 0 deletions remote_syslog_logger.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Gem::Specification.new do |s|
## List your runtime dependencies here. Runtime dependencies are those
## that are needed for an end user to actually USE your code.
s.add_dependency('syslog_protocol')
s.add_dependency('activesupport', '>= 3.2.14')

s.add_development_dependency('rake')
s.add_development_dependency('test-unit')

## List your development dependencies here. Development dependencies are
## those that are only needed during development
Expand Down
2 changes: 1 addition & 1 deletion test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

require 'remote_syslog_logger'

require 'test/unit'
require 'minitest/autorun'
136 changes: 134 additions & 2 deletions test/test_remote_syslog_logger.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# encoding: utf-8

require File.expand_path('../helper', __FILE__)
require File.expand_path('../../lib/remote_syslog_logger/limit_bytesize', __FILE__)

class TestRemoteSyslogLogger < Test::Unit::TestCase
class TestRemoteSyslogLogger < MiniTest::Test
def setup
@server_port = rand(50000) + 1024
@socket = UDPSocket.new
Expand All @@ -25,4 +28,133 @@ def test_logger_multiline
message, addr = *@socket.recvfrom(1024)
assert_match /This is the second line/, message
end
end

def test_logger_default_tag
$0 = 'foo'
logger = RemoteSyslogLogger.new('127.0.0.1', @server_port)
logger.info ""

message, addr = *@socket.recvfrom(1024)
assert_match "foo[#{$$}]: I,", message
end

def test_logger_long_default_tag
$0 = 'x' * 64
pid_suffix = "[#{$$}]"
logger = RemoteSyslogLogger.new('127.0.0.1', @server_port)
logger.info ""

message, addr = *@socket.recvfrom(1024)
assert_match 'x' * (32 - pid_suffix.size) + pid_suffix + ': I,', message
end

TEST_TAG = 'foo'
TEST_HOSTNAME = 'bar'
TEST_FACILITY = 'user'
TEST_SEVERITY = 'notice'
TEST_MESSAGE = "abcdefg✓" * 512
TEST_MESSAGE_ASCII8 = "abcdefg".force_encoding('ASCII')

def test_logger_long_message
_test_msg_splitting_with(
tag: TEST_TAG,
hostname: TEST_HOSTNAME,
severity: TEST_SEVERITY,
facility: TEST_FACILITY,
message: TEST_MESSAGE,
max_packet_size: nil,
continuation_prefix: nil)
end

def test_logger_long_message_custom_packet_size
_test_msg_splitting_with(
tag: TEST_TAG,
hostname: TEST_HOSTNAME,
severity: TEST_SEVERITY,
facility: TEST_FACILITY,
message: TEST_MESSAGE,
max_packet_size: 2048,
continuation_prefix: nil)
end

def test_logger_long_message_custom_continuation
_test_msg_splitting_with(
tag: TEST_TAG,
hostname: TEST_HOSTNAME,
severity: TEST_SEVERITY,
facility: TEST_FACILITY,
message: TEST_MESSAGE,
max_packet_size: nil,
continuation_prefix: 'frobnicate')
end

def test_logger_ascii8_message
_test_msg_splitting_with(
tag: TEST_TAG,
hostname: TEST_HOSTNAME,
severity: TEST_SEVERITY,
facility: TEST_FACILITY,
message: TEST_MESSAGE_ASCII8,
max_packet_size: nil,
continuation_prefix: nil)
end

def test_logger_empty_message
_test_msg_splitting_with(
tag: TEST_TAG,
hostname: TEST_HOSTNAME,
severity: TEST_SEVERITY,
facility: TEST_FACILITY,
message: '',
max_packet_size: nil,
continuation_prefix: nil)
end

private

class MessageOnlyFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
msg
end
end

def _test_msg_splitting_with(options)
logger = RemoteSyslogLogger.new('127.0.0.1', @server_port,
program: options[:tag],
local_hostname: options[:hostname],
severity: options[:severity],
facility: options[:facility],
max_packet_size: options[:max_packet_size],
continuation_prefix: options[:continuation_prefix])
logger.formatter = MessageOnlyFormatter.new
logger.info options[:message]

packet_size = options[:max_packet_size] || 1024
continuation_prefix = options[:continuation_prefix] || '... '

test_packet = SyslogProtocol::Packet.new
test_packet.hostname = options[:hostname]
test_packet.tag = options[:tag]
test_packet.severity = options[:severity]
test_packet.facility = options[:facility]
test_packet.content = ''
max_content_size = packet_size - test_packet.assemble.size

line_prefix = ''
remaining_message = options[:message]
reassembled_message = ''
until remaining_message.empty?
chunk_size = max_content_size - line_prefix.bytesize
chunk = limit_bytesize(remaining_message, chunk_size)
message, = *@socket.recvfrom(packet_size * 2)
message.force_encoding('UTF-8')
match = Regexp.new(
': ' + line_prefix + '(' + Regexp.escape(chunk) + ')$').match(message)
assert !match.nil?
reassembled_message += match[1]
remaining_message = remaining_message[chunk.size..-1]
line_prefix = continuation_prefix
end
assert_equal(reassembled_message, options[:message])
end
end