public
Last active

An EventMachine Syslog Protocol

  • Download Gist
em_syslog2.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
# this is a configurable version of an EventMachine syslogger
# It was build to replace https://github.com/melito/em-syslog
#
# You can instantiate many of them (no global variables) as
# well as configure the facility. It also quacks like a ruby
# logger so it can be interchanged with a ruby logger.
#
# Example:
#
# # 11 is the facility code for an FTP daemon
# # 514 is the default UDP port for SYSLOG
# syslog = EM::Protocols::Syslog2.connect('localhost', 514, 11)
# # later ...
# syslog.info "Hello world"
# syslog.error "Goodbye world :-("
#
# Quick note: on my system, syslog was not listening to UDP by
# default, I had to enable it in its config. Because it's UDP
# you won't get an error :-)
class EventMachine::Protocols::Syslog2 < EventMachine::Connection
def self.connect(host, port, facility)
# open a null socket for sending
EM.open_datagram_socket('0.0.0.0', 0, self, facility, host, port)
end
 
attr_reader :facility, :host, :port
 
# Initialize accepts the log `facility` as defined
# by the rfc:
#
# Numerical Facility
# Code
# 0 kernel messages
# 1 user-level messages
# 2 mail system
# 3 system daemons
# 4 security/authorization messages
# 5 messages generated internally by syslogd
# 6 line printer subsystem
# 7 network news subsystem
# 8 UUCP subsystem
# 9 clock daemon
# 10 security/authorization messages
# 11 FTP daemon
# 12 NTP subsystem
# 13 log audit
# 14 log alert
# 15 clock daemon (note 2)
# 16 local use 0 (local0)
# 17 local use 1 (local1)
# 18 local use 2 (local2)
# 19 local use 3 (local3)
# 20 local use 4 (local4)
# 21 local use 5 (local5)
# 22 local use 6 (local6)
# 23 local use 7 (local7)
#
# Also sets the host and port to send UDP datagrams to.
def initialize(facility, host, port)
@facility = facility
@host = host
@port = port
end
 
# Error helper to behave like a ruby logger
def error(message, time = Time.now) log(severity(:error), message, time) end
# Warn helper to behave like a ruby logger
def warn (message, time = Time.now) log(severity(:warning), message, time) end
# Info helper to behave like a ruby logger
def info (message, time = Time.now) log(severity(:info), message, time) end
# Debug helper to behave like a ruby logger
def debug(message, time = Time.now) log(severity(:debug), message, time) end
 
private
 
# We don't use all of these, but if we wanted to they are here
# From the RFC:
#
# Numerical Severity
# Code
# 0 Emergency: system is unusable
# 1 Alert: action must be taken immediately
# 2 Critical: critical conditions
# 3 Error: error conditions
# 4 Warning: warning conditions
# 5 Notice: normal but significant condition
# 6 Informational: informational messages
# 7 Debug: debug-level messages
def self.severity(sym)
{ :emergency => 0,
:alert => 1,
:critical => 2,
:error => 3,
:warning => 4,
:notice => 5,
:informational => 6,
:info => 6,
:debug => 7
}[sym]
end
delegate :severity, :to => self
 
# severity: integer indicating severity level (NOT a symbol)
# message: message to log
# time: time of the event to report
#
# ruby message encoding from: http://github.com/kpumuk/ruby_syslog
def log(severity, message, time)
day = time.strftime('%b %d').sub(/0(\d)/, ' \\1')
tag = "#{$0.split('/').last.split('.').first}[#{Process.pid}]"
send_datagram "<#{facility * 8 + severity}>#{day} #{time.strftime('%T')} #{tag}: #{message}", host, port
end
end
em_syslog2_test.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
require 'test/test_helper'
 
class EM::SyslogTest < ActiveSupport::TestCase
class FakeSyslog < EM::Connection
attr_accessor :message
def self.listen(host, port)
EM::open_datagram_socket(host, port, self)
end
 
def receive_data(data)
# <22>Dec 12 15:54:09 syslog_test[11792]: hello world
data =~ /^<(\d+)>/
level = $1.to_i
data =~ / ([^:]+)$/
msg = $1
self.message = [level,msg]
EM.stop
end
end
 
[
[:error, 19],
[:warn, 20],
[:info, 22],
[:debug, 23]
].each do |level_name, level|
test "log #{level_name} message" do
host = "127.0.0.1"
port = "1514"
server = nil
syslog = nil
 
eventmachine do
server = FakeSyslog.listen(host, port)
syslog = EM::Protocols::Syslog2.connect(host, port, 2)
syslog.send(level_name, "hello world")
end
 
assert_equal [level, "hello world"], server.message
end
end
end

also that magic "eventmachine" function is this:

  def eventmachine(timeout = 1)
    Timeout::timeout(timeout) do
      EM.run do
        EM.epoll
        yield
      end
    end
  end

Also nice for writing specs requiring EventMachine is the https://rubygems.org/gems/evented-spec gem

Except that's an rspec library and I use T::U and MiniTest :-(

An excellent point - stealing the API is also an option :)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.