SysLog Client

Recently I was consuming a binary log format and was wanting to send messages through to a syslog server from a Windows machine. I was looking up the RFC specification (RFC3164) for sending a message - below is a summary if anyone was interested in doing something similar.

Structure

A syslog message is made from 3 components, PRI (priority), HEADER and the MESSAGE. Example from the specification with some annotation

 PRI           HEADER                            MESSAGE
  |  <-----------------------> <-------------------------------------------->
  |     TIMESTAMP       HOST   TAG                 CONTENT
<---><-------------> <-------> <-> <---------------------------------------->
<134>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8

PRI (Priority)

The priority of a syslog message is made up to two components; the facility and the severity.

  • Encoded as a seven-bit ASCII in an eight-bit field
  • The priority is encapsulated between angled brackets <0>
  • It is calculated using the following formula
Priority = Facility x 8 + Severity

Facilities

Numerical Code Facility
0 kernel messages
1 user-level messages
2 mail system
3 system daemons
4 security/authorization messages (note 1)
5 messages generated internally by syslogd
6 line printer subsystem
7 network news subsystem
8 UUCP subsystem
9 clock daemon (note 2)
10 security/authorization messages (note 1)
11 FTP daemon
12 NTP subsystem
13 log audit (note 1)
14 log alert (note 1)
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)

Severity

Numerical Code Severity
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
  • Header is made up of two components - the timestamp and the host which generated the message.
  • Encoded as a seven-bit ASCII in an eight-bit field

Timestamp

  • The time must directly follow the close of the angled brackets.
  • Must be formatted as below
    • Months formatted as Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
    • If the day of the month is less than 10 then use a space to pad not a leading zero
    • Single space following the timestamp
Mmm dd hh:mm:ss
Jun 30 18:10:55
Jun  1 09:10:55
# Ruby example 
Time.now.strftime("%b %e %k:%M:%S")

Hostname / IP Address

  • Encoded as a seven-bit ASCII in an eight-bit field
  • Either the hostname or an IP address can be used but not both.
  • A space represents the end of the hostname field so the hostname should not contain one.

Message

  • Generally encoded as a seven-bit ASCII - however encoding set is not enforced.
  • Contains two parts, the TAG and the CONTENT

Tag

  • Normally the name of the program or process which generated the message
  • Alphanumeric
  • Most not exceed 32 characters
  • End delimiter is one of the following
    • Left square bracket ‘[’
    • Colon ‘:’
    • Space

Content

  • No ending delimiter

Basic Ruby Client

class Client
  def initialize(host, port)
    @host = host
    @port = port

    @sock = UDPSocket.new
  end

  def calculate_priority(severity, facility)
    return facility * 8 + severity
  end

  def send(severity, facility, epoch, host, application, content)
    priority  = calculate_priority(severity, facility)
    timestamp = Time.at(epoch).strftime("%b %e %k:%M:%S")

    host      = host.encode("ASCII-8BIT", :invalid => :replace, :undef => :replace, :replace => "?")
    app       = application.encode("ASCII-8BIT", :invalid => :replace, :undef => :replace, :replace => "?")
    con       = content.encode("ASCII-8BIT", :invalid => :replace, :undef => :replace, :replace => "?")

    # structure packet
    packet = [
      "<#{priority}>",                        # PRI
      "#{timestamp} #{host}",                 # HEADER
      " #{application}: #{content}"           # CONTENT
    ].join("").encode(Encoding::ASCII_8BIT)

    @sock.send(packet, 0, @host, @port)
  end
end