Arduino Syslog Client Library
Inhaltsverzeichnis
1 Project Definition
We intend to upgrade our ATV relay DB0MHB in a way that it logs every event right on the second over the network to a syslog server. Since we are going to do this with an Arduino system, I understood that there is no such thing as a Syslog client library for Arduino.
If something does not exist, you need to change that.
Please visit my Google Code site:
2 Advance Tests
2.1 Understand the format of a Syslog message
The format of a syslog message is defined in RFC5424. It is depicted as a transformational grammar:
SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG]
HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME
SP APP-NAME SP PROCID SP MSGID
PRI = "<" PRIVAL ">"
PRIVAL = 1*3DIGIT ; range 0 .. 191
VERSION = NONZERO-DIGIT 0*2DIGIT
HOSTNAME = NILVALUE / 1*255PRINTUSASCII
APP-NAME = NILVALUE / 1*48PRINTUSASCII
PROCID = NILVALUE / 1*128PRINTUSASCII
MSGID = NILVALUE / 1*32PRINTUSASCII
TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME
FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
DATE-FULLYEAR = 4DIGIT
DATE-MONTH = 2DIGIT ; 01-12
DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
FULL-TIME = PARTIAL-TIME TIME-OFFSET
PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND
[TIME-SECFRAC]
TIME-HOUR = 2DIGIT ; 00-23
TIME-MINUTE = 2DIGIT ; 00-59
TIME-SECOND = 2DIGIT ; 00-59
TIME-SECFRAC = "." 1*6DIGIT
TIME-OFFSET = "Z" / TIME-NUMOFFSET
TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE
STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT
SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"
SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34
SD-ID = SD-NAME
PARAM-NAME = SD-NAME
PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
; ']' MUST be escaped.
SD-NAME = 1*32PRINTUSASCII
; except '=', SP, ']', %d34 (")
MSG = MSG-ANY / MSG-UTF8
MSG-ANY = *OCTET ; not starting with BOM
MSG-UTF8 = BOM UTF-8-STRING
BOM = %xEF.BB.BF
UTF-8-STRING = *OCTET ; UTF-8 string as specified
; in RFC 3629
OCTET = %d00-255
SP = %d32
PRINTUSASCII = %d33-126
NONZERO-DIGIT = %d49-57
DIGIT = %d48 / NONZERO-DIGIT
NILVALUE = "-"
This grammar defines nonstructured as well as structured presentations. So very basically, the Syslog message consists of the following basic form:
<PRI> TIMESTAMP TAG MESSAGE
The PRI value is an integer number which calculates by the following metric:
8 x (facility code) + (severity code)
where the individual codes are those:
Facilities:
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)
Severities:
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
The TIMESTAMP may be the NILVALUE if there is no time available.
2.2 Enable my local Syslog daemon to receive log messages over the network
Running a NATTY Ubuntu, I noticed that modern Ubuntu distros use Rainer Gerhards' rsyslog implementation.
It has a section in the config file /etc/rsyslog.conf which just needs to be uncommented:
# provides UDP syslog reception $ModLoad imudp $UDPServerRun 514
Upon restarting the rsyslog service with the command service rsyslog restart you can check with netstat -tulpen if the service is listening on UDP port 514.
2.3 Trace the logger command
The logger command is a nice little commandline tool that permits local services and scripts to log to the system's syslog. It is not able to send messages over the network, but uses /dev/log instead. Since there are no network packages we can capture, we need to use strace to capture everything the logger is doing:
markus@note:~$ strace logger -p local0.warn -t test huhuasder345345sdfsdfhsdfee
execve("/usr/bin/logger", ["logger", "-p", "local0.warn", "-t", "test", "huhuasder345345sdfsdfhsdfee"], [/* 41 vars */]) = 0
brk(0) = 0x10f3000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f22b36b5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=152165, ...}) = 0
mmap(NULL, 152165, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f22b368f000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\360\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1638120, ...}) = 0
mmap(NULL, 3749080, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f22b3103000
mprotect(0x7f22b328d000, 2093056, PROT_NONE) = 0
mmap(0x7f22b348c000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x189000) = 0x7f22b348c000
mmap(0x7f22b3491000, 21720, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f22b3491000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f22b368e000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f22b368c000
arch_prctl(ARCH_SET_FS, 0x7f22b368c720) = 0
mprotect(0x7f22b348c000, 16384, PROT_READ) = 0
mprotect(0x601000, 4096, PROT_READ) = 0
mprotect(0x7f22b36b7000, 4096, PROT_READ) = 0
munmap(0x7f22b368f000, 152165) = 0
brk(0) = 0x10f3000
brk(0x1114000) = 0x1114000
open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=8310192, ...}) = 0
mmap(NULL, 8310192, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f22b2916000
close(3) = 0
close(1) = 0
open("/etc/localtime", O_RDONLY) = 1
fstat(1, {st_mode=S_IFREG|0644, st_size=2309, ...}) = 0
fstat(1, {st_mode=S_IFREG|0644, st_size=2309, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f22b36b4000
read(1, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10\0\0\0\10\0\0\0\0"..., 4096) = 2309
lseek(1, -1467, SEEK_CUR) = 842
read(1, "TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\t\0\0\0\t\0\0\0\0"..., 4096) = 1467
close(1) = 0
munmap(0x7f22b36b4000, 4096) = 0
socket(PF_FILE, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 1
connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0
sendto(1, "<132>Jun 23 20:46:53 test: huhua"..., 54, MSG_NOSIGNAL, NULL, 0) = 54
close(1) = 0
exit_group(0) = ?
Note the last four lines: The tool opens the /dev/log device and the following string is being sent to it:
<132>Jun 23 20:46:53 test: huhua (......)
2.4 Manually Sending a Syslog Message
Since we now know how a syslog message looks like, there is the assumption the the syslog server will just accept the message when we submit it via the network. We can do that with the Swiss Army Knife of network engineering, the tool netcat, also known as nc:
markus@note:~$ nc -u localhost 514 <132>Jun 23 20:46:53 test: huhua
and it just comes out in the /var/log/syslog file as expected:
Jun 23 20:46:53 localhost test: huhua
So we have the proof that a syslog client just needs to work accordingly.
3 Implementation and Tests
Since we have seen how Syslog works across the network, we will now implement the client library.