Click here to Skip to main content
15,878,814 members
Articles / Programming Languages / C#

Syslog daemon for Windows Eventlog

Rate me:
Please Sign up or sign in to vote.
4.92/5 (20 votes)
22 Apr 2007CPOL4 min read 174.5K   2.8K   64   43
Syslogd is an installable Windows service which receives and translates syslog messages to Windows Eventlog logs

Screenshot - Syslogd.gif

Introduction

Syslog has become the standard logging solution on Unix and Linux systems. There are many applications which can send and receive syslog messages on windows computers. I have this wireless router (WRT type) and after a firmware upgrade, there was a syslog option. Working on a Windows XP machine, I wrote a Syslog daemon of my own entirely in C#.

Background

For logging on Windows we have Eventlog. To make use of Eventlog we have to make a service which translates syslog messages and sends them to Eventlog. Syslog messages are transported as UDP packets on port number 514, and have a very simple structure. For more on Syslog packets, please read http://www.ietf.org/rfc/rfc3164.txt

Using the demo project

The demo project contains the Syslogd.exe service. If you want to use it, you have to install the service by using the .NET tool installutil.exe which is located in the framework directory (watch version numbering).

In my src project I use two post-build steps, one for uninstall, and one for installing.
Note: If you build the project for the first time, the first step will break. So remove it, and add it later.

c:\windows\microsoft.net\framework\v2.0.50727\installutil.exe 
         /u "$(TargetPath)"
c:\windows\microsoft.net\framework\v2.0.50727\installutil.exe
         "$(TargetPath)"

Note: The demo project contains a simple install.cmd that does install your service.

Because services are not nice to debug(!), the project contains a simple Form which can Start and Stop the Syslogd process. You can set breakpoints, and do single steps from here.

When using installutil.exe for registering .NET services, the tool looks for any [RunInstaller(true)] attribute on a class, and executes that piece of code.

C#
[RunInstaller(true)]
public class SyslogdInstaller : Installer
{
.......
}    

SyslogdInstaller.cs

This installer code is located in SyslogdInstaller.cs which does two things. It installs the syslogd service and opens up the internal firewall of Windows XP, for more on this, see 'Points of Interest'.

C#
.......
this.serviceProcessInstaller1.Account = ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
.......
this.serviceInstaller1.ServiceName = settings.ServiceName;
this.serviceInstaller1.Description = settings.Description;
this.serviceInstaller1.DisplayName = settings.DisplayName;
this.serviceInstaller1.StartType = ServiceStartMode.Automatic;

Properties.Settings

Most of the properties in this project are configurable by its Properties.Settings class.

In Visual Studio you can use the nice properties editor:

Image 2

When saving these settings, an XML file is saved (Syslogd.exe.config) which can be changed according to one's own wishes afterwards.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
.....
    <userSettings>
        <Syslogd.Properties.Settings>
            <setting name="Address" serializeAs="String">
                <value>*</value>
            </setting>
            <setting name="Port" serializeAs="String">
                <value>514</value>
            </setting>
            <setting name="ServiceName" serializeAs="String">
                <value>Syslogd</value>
            </setting>
            <setting name="Description" serializeAs="String">
                <value>Syslogd service logs syslog messages to
                     windows eventlog</value>
            </setting>
            <setting name="DisplayName" serializeAs="String">
                <value>Syslogd service</value>
            </setting>
            <setting name="EventLog" serializeAs="String">
                <value>Syslog</value>
            </setting>
        </Syslogd.Properties.Settings>
    </userSettings>
</configuration>

SyslogdService.cs

The syslogd service code itself is located in SyslogdService.cs. This is more or less a standard service code and inherits from ServiceBase. There is only a Start and a Stop service method.

C#
...
private Syslogd syslogd;
...
public SyslogdService()
{
    ...
    syslogd = new Syslogd();
}

protected override void OnStart(string[] args)
{
    syslogd.Start();
} 
...
protected override void OnStop()
{
    syslogd.Stop();
}
...

Syslogd.cs

When syslogd service is started, a new class syslogd is instantiated, and contains a worker thread for receiving syslog messages. This worker class is located in Syslogd.cs

C#
private void Worker()
{
    Properties.Settings settings = new Properties.Settings();

    m_EventLog = settings.EventLog;

    IPAddress ipAddress = IPAddress.Any;
    if(settings.Address!="*")
        ipAddress = IPAddress.Parse(settings.Address);

    IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, settings.Port);

    m_socket = new Socket(
        AddressFamily.InterNetwork,
        SocketType.Dgram, ProtocolType.Udp);
    m_socket.Bind(ipEndPoint);

    // Recycling vars , i love it.
    EndPoint endPoint = ipEndPoint;

    // http://www.ietf.org/rfc/rfc3164.txt
    // 4.1 syslog Message Parts
    // The total length of the packet MUST be 1024 bytes or less.
    byte[] buffer = new byte[1024];

    while (m_running)
    {
        try
        {
            int intReceived = m_socket.ReceiveFrom(buffer, 0, 
                buffer.Length, SocketFlags.None, ref endPoint);
            string strReceived = Encoding.ASCII.GetString(buffer,
                0, intReceived);
            Log(endPoint, strReceived);
        }
        catch (Exception exception)
        {
            EventLog myLog = new EventLog();
            myLog.Source = settings.ServiceName;
            if (!m_running)
                myLog.WriteEntry("Stopping...",
                     EventLogEntryType.Information);
            else
                myLog.WriteEntry(exception.Message,
                     EventLogEntryType.Error);
            myLog.Close();
            myLog.Dispose();
        }
    }
}

Decoding Syslog PRI

Every syslog packet contains a PRI. This PRI is simply decoded and translated by making use of the appropriate regular expression.

C#
...
/*
 * The PRI part MUST have three, four, or five characters and will be
 * bound with angle brackets as the first and last characters.  The PRI
 * part starts with a leading "<" ('less-than' character), followed by a
 * number, which is followed by a ">" ('greater-than' character).  
 */
m_regex = new Regex("<([0-9]{1,3})>", RegexOptions.Compiled);
...

After decoding the decimal part of the PRI, I use a struct to translate it to Facility and Severity.

C#
/// <summary>
/// Facility  according to http://www.ietf.org/rfc/rfc3164.txt
///      4.1.1 PRI Part
/// </summary>
private enum FacilityEnum : int
{
    kernel      = 0,     // kernel messages
    user        = 1,     // user-level messages
    mail        = 2,     // mail system
    system      = 3,     // system daemons
    security    = 4,     // security/authorization messages (note 1)
    internally  = 5,     // messages generated internally by syslogd
    printer     = 6,     // line printer subsystem
    news        = 7,     // network news subsystem
    uucp        = 8,     // UUCP subsystem
    cron        = 9,     // clock daemon (note 2) changed to cron
    security2   = 10,    // security/authorization messages (note 1)
    ftp         = 11,    // FTP daemon
    ntp         = 12,    // NTP subsystem
    audit       = 13,    // log audit (note 1)
    alert       = 14,    // log alert (note 1)
    clock2      = 15,    // clock daemon (note 2)
    local0      = 16,    // local use 0  (local0)
    local1      = 17,    // local use 1  (local1)
    local2      = 18,    // local use 2  (local2)
    local3      = 19,    // local use 3  (local3)
    local4      = 20,    // local use 4  (local4)
    local5      = 21,    // local use 5  (local5)
    local6      = 22,    // local use 6  (local6)
    local7      = 23,    // local use 7  (local7)
}

/// <summary>
/// Severity  according to http://www.ietf.org/rfc/rfc3164.txt
///      4.1.1 PRI Part
/// </summary>
private enum SeverityEnum : int
{
    emergency  = 0,    // Emergency: system is unusable
    alert      = 1,    // Alert: action must be taken immediately
    critical   = 2,    // Critical: critical conditions
    error      = 3,    // Error: error conditions
    warning    = 4,    // Warning: warning conditions
    notice     = 5,    // Notice: normal but significant condition
    info       = 6,    // Informational: informational messages
    debug      = 7,    // Debug: debug-level messages
}

private struct Pri
{
    public FacilityEnum Facility;
    public SeverityEnum Severity;
    public Pri(string strPri)
    {
        int intPri = Convert.ToInt32(strPri);
        int intFacility = intPri >> 3;
        int intSeverity = intPri & 0x7;
        this.Facility = (FacilityEnum)Enum.Parse(typeof(FacilityEnum),
           intFacility.ToString());
        this.Severity = (SeverityEnum)Enum.Parse(typeof(SeverityEnum),
           intSeverity.ToString());
    }
    public override string ToString()
    {
        return string.Format("{0}.{1}", this.Facility, this.Severity);
    }
}

The first PRI match in the received message gets special attention.
See 'The logging in Eventlog' part for this.

C#
...
Pri pri = new Pri(m_regex.Match(strReceived).Groups[1].Value);
...

Because syslog messages can be relayed, and a relay can add more PRI parts, every message can have more than one PRI. Every PRI is replaced by its human readable Facility.Severity pair.

C#
/// <summary>
/// Evaluator is being used to translate every decimal Pri header in
/// a Syslog message to an 'Facility.Severity ' string.
/// </summary>
/// <param name="match">Any Pri header match in a message</param>
/// <returns />Translated Pri header to 'Facility.Severity '</returns>
private string evaluator(Match match)
{
    Pri pri = new Pri(match.Groups[1].Value);
    return pri.ToString()+" ";
}

private void Log(EndPoint endPoint, string strReceived)
{
...
string strMessage = string.Format("{0} : {1}", 
        endPoint, m_regex.Replace(strReceived, evaluator));
...
}

Translating Syslog Severity to EventLogEntryType

Windows Eventlog supports only a few useful EventLogEntryTypes: Error, Warning and Information. Every syslog Severity is mapped on one of these. This is only done to have a good discriminable part for any Sysadmin, or analyzer program which watches the Eventlog. Every syslog Severity is logged in full in the body of every Eventlog entry.

C#
private EventLogEntryType Severity2EventLogEntryType(SeverityEnum Severity)
{
    EventLogEntryType eventLogEntryType;

    switch (Severity)
    {
        case SeverityEnum.emergency:
            eventLogEntryType = EventLogEntryType.Error;
            break;
        case SeverityEnum.alert:
            eventLogEntryType = EventLogEntryType.Error;
            break;
        case SeverityEnum.critical:
            eventLogEntryType = EventLogEntryType.Error;
            break;
        case SeverityEnum.error:
            eventLogEntryType = EventLogEntryType.Error;
            break;
        case SeverityEnum.warning:
            eventLogEntryType = EventLogEntryType.Warning;
            break;
        case SeverityEnum.notice:
            eventLogEntryType = EventLogEntryType.Information;
            break;
        case SeverityEnum.info:
            eventLogEntryType = EventLogEntryType.Information;
            break;
        case SeverityEnum.debug:
            eventLogEntryType = EventLogEntryType.Information;
            break;
        default: // ?
            eventLogEntryType = EventLogEntryType.Error;
            break;
    }
    return eventLogEntryType;
}

The logging in Eventlog

Binding this all together, logging IP-address, Port-number, PRI translated message, logging in Windows Eventlog. Nice feature to use the first PRI as a eventlog Source, because new Eventlogs can (auto) register Source types, which you can filter etc.

C#
private void Log(EndPoint endPoint, string strReceived)
{
    Pri pri = new Pri(m_regex.Match(strReceived).Groups[1].Value);

    EventLogEntryType eventLogEntryType = 
        Severity2EventLogEntryType(pri.Severity);

    string strMessage = string.Format("{0} : {1}", 
        endPoint, m_regex.Replace(strReceived, evaluator));

    EventLog myLog = new EventLog(m_EventLog);
    myLog.Source = pri.ToString();
    myLog.WriteEntry(strMessage, eventLogEntryType);
    myLog.Close();
    myLog.Dispose();
}

Points of Interest

When building services which use the network on a Windows XP machine, you have to deal with the built-in Firewall. For opening up the internal firewall for the service, this piece of code is needed:

C#
private void FireWall()
{
...
    Type NetFwMgrType = Type.GetTypeFromProgID("HNetCfg.FwMgr", false);
    INetFwMgr mgr = (INetFwMgr)Activator.CreateInstance(NetFwMgrType);

    Type NetFwAuthorizedApplicationType =
        Type.GetTypeFromProgID("HNetCfg.FwAuthorizedApplication", false);
    INetFwAuthorizedApplication app =
        (INetFwAuthorizedApplication)
        Activator.CreateInstance(NetFwAuthorizedApplicationType);

    app.Name = settings.ServiceName;
    app.Enabled = true;
    app.ProcessImageFileName = Assembly.GetExecutingAssembly().Location;
    app.Scope = NET_FW_SCOPE_.NET_FW_SCOPE_ALL;

    mgr.LocalPolicy.CurrentProfile.AuthorizedApplications.Add(app);
...
}

Don't forget that you have to make a reference to c:\windows\system32\hnetcfg.dll - it makes an Interop.NetFwTypeLib.dll

Syslogd In action

I'm running a Dutch version of Windows XP, nice for all other programmers to see Services and Eventlog in Dutch!!

Syslogd2.gif - Standard Windows XP service menu, containing our Syslogd service

A sample Syslog entry of my router:

Syslogd3.gif - Syslog entry in Eventlog

History

  • As of writing, it is Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired Van der Heijden Holding BV
Netherlands Netherlands
I'm Alphons van der Heijden, living in Lelystad, Netherlands, Europa, Earth. And currently I'm retiring from hard working ( ;- ), owning my own company. Because I'm full of energy, and a little to young to relax ...., I don't sit down, but create and recreate software solutions, that I like. Reinventing the wheel is my second nature. My interest is in the area of Internet technologies, .NET etc. I was there in 1992 when Mosaic came out, and from that point, my life changed dramatically, and so did the world, in fact. (Y)

Comments and Discussions

 
QuestionFinding Facility Pin
RBKhadka4-Nov-12 23:57
RBKhadka4-Nov-12 23:57 
AnswerRe: Finding Facility Pin
Alphons van der Heijden7-Nov-12 1:10
professionalAlphons van der Heijden7-Nov-12 1:10 
QuestionPossibly help for someone Pin
GoldGoose3119-Aug-12 16:13
GoldGoose3119-Aug-12 16:13 
GeneralException: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram was smaller than the datagram itself Pin
__Gianluca__23-Mar-11 3:53
__Gianluca__23-Mar-11 3:53 
GeneralRe: Exception: A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram was smaller than the datagram itself Pin
Alphons van der Heijden5-Apr-11 13:32
professionalAlphons van der Heijden5-Apr-11 13:32 
GeneralInstalling on Server 2008 x64 Pin
Brian W. Spolarich1-Sep-10 3:05
Brian W. Spolarich1-Sep-10 3:05 
GeneralRe: Installing on Server 2008 x64 Pin
Alphons van der Heijden1-Sep-10 12:38
professionalAlphons van der Heijden1-Sep-10 12:38 
GeneralMySQL Pin
JP Encarnacion25-Mar-09 5:52
JP Encarnacion25-Mar-09 5:52 
GeneralRe: MySQL Pin
Alphons van der Heijden25-Mar-09 9:21
professionalAlphons van der Heijden25-Mar-09 9:21 
GeneralRe: MySQL Pin
JP Encarnacion27-Mar-09 0:03
JP Encarnacion27-Mar-09 0:03 
GeneralCrash with first message received. Pin
GreatOak7-Jan-09 3:12
professionalGreatOak7-Jan-09 3:12 
I compile it, install the service, start it but as soon as I get the first log it crashes.

The demo works correctly but neither the Debug nor Release compiled version works.

Here is the error.
Event Type: Error
Event Source: .NET Runtime 2.0 Error Reporting
Event Category: None
Event ID: 5000
Date: 01/06/2009
Time: 17:11:52
User: N/A
Computer: WHQTEC12
Description:
EventType clr20r3, P1 syslogd.exe, P2 1.0.3293.29108, P3 4963c8b8, P4 system, P5 2.0.0.0, P6 4889de7a, P7 2c0b, P8 40, P9 system.net.sockets.socket, P10 NIL.

Basically, what I saw in the debug is that the worker thread can't access the socket/port it is trying to.

System.Net.Sockets.SocketException was unhandled
Message="Only one usage of each socket address (protocol/network address/port) is normally permitted"
Source="System"
ErrorCode=10048
NativeErrorCode=10048
StackTrace:
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at Syslogd.Syslogd.Worker()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

Any suggestions would be greatly appreciated.

Thank you,
G
Confused | :confused:
GeneralRe: Crash with first message received. Pin
Alphons van der Heijden7-Jan-09 4:37
professionalAlphons van der Heijden7-Jan-09 4:37 
GeneralRe: Crash with first message received. Pin
GreatOak7-Jan-09 5:43
professionalGreatOak7-Jan-09 5:43 
GeneralRe: Crash with first message received. Pin
GreatOak7-Jan-09 10:17
professionalGreatOak7-Jan-09 10:17 
QuestionOn regular expression Pin
Elias Poulogiannis2-Jan-09 9:24
Elias Poulogiannis2-Jan-09 9:24 
AnswerRe: On regular expression Pin
Alphons van der Heijden2-Jan-09 12:17
professionalAlphons van der Heijden2-Jan-09 12:17 
GeneralInstallation Pin
nuander25-Jul-08 6:19
nuander25-Jul-08 6:19 
GeneralRe: Installation Pin
Alphons van der Heijden18-Sep-08 0:20
professionalAlphons van der Heijden18-Sep-08 0:20 
Generalsyslogd Pin
zagnut24-Apr-08 19:09
zagnut24-Apr-08 19:09 
GeneralI get more than one log for each event Pin
wyx200018-Oct-07 13:52
wyx200018-Oct-07 13:52 
GeneralRe: I get more than one log for each event Pin
Alphons van der Heijden19-Oct-07 0:39
professionalAlphons van der Heijden19-Oct-07 0:39 
Questionany idea how to ge host name and timestamp? Pin
wyx200018-Oct-07 13:30
wyx200018-Oct-07 13:30 
AnswerRe: any idea how to ge host name and timestamp? Pin
Alphons van der Heijden19-Oct-07 0:55
professionalAlphons van der Heijden19-Oct-07 0:55 
Generalsyslogs.... Pin
ankita luniya17-Jul-07 1:21
ankita luniya17-Jul-07 1:21 
GeneralRe: syslogs.... Pin
Alphons van der Heijden17-Jul-07 2:04
professionalAlphons van der Heijden17-Jul-07 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.