Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Multithreaded, Customizable SysLog Server - C#

0.00/5 (No votes)
27 Mar 2013 1  
Multithreaded, customizable SysLog server in C#.

Introduction

For those of us managing multiple devices, keeping aware of issues and events can be a challenge. Many Linux, Unix, and Windows devices support the ability to send SysLog (System Log) events to a central server for notifications and/or logging. I decided to make this application using C# (Visual Studio Express 2010) to receive those messages, store them in CSV format and send me email notifications based on custom criteria I define. I chose CSV because it's light weight and can natively be opened by any spreadsheet software or brought in to another app as a DataTable with a simple OLEDB connection. For my purposes, multithreading this app was essential due to the volume of devices configured to send SysLogs to the server. I let this app run for a bit and looked through the output CSV file to determine which events I should be made aware of via email, and set those as email triggers. This is a relatively light weight console app that is very versatile for a number of uses. Check out the code and leave a comment with any questions or suggestions! You can also check out my blog at http://meta-struct.com/

Using the code

Configure your "devices" to aim their SysLog events to the IP of your computer (you'll probably want a static or reserved IP for this to work well). Create a Console application in Visual Studio, and use the following code:

using System;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace syslog
{
    class Program
    {
        static void Main(string[] args)
        {
            IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
            UdpClient udpListener = new UdpClient(514);
            byte[] bReceive; string sReceive; string sourceIP;

            /* Main Loop */
            /* Listen for incoming data on udp port 514 (default for SysLog events) */
            while (true)
            {
                try
                {
                    bReceive = udpListener.Receive(ref anyIP);
                    /* Convert incoming data from bytes to ASCII */
                    sReceive = Encoding.ASCII.GetString(bReceive);
                    /* Get the IP of the device sending the syslog */
                    sourceIP = anyIP.Address.ToString();
                    new Thread(new logHandler(sourceIP, sReceive).handleLog).Start();
                    /* Start a new thread to handle received syslog event */
                }
                catch (Exception ex) { Console.WriteLine(ex.ToString()); }
            }
        }
    }

    class logHandler
    {
        /* Phrases within the syslog that will trigger an email notification */
        private string[] emailTriggers = new string[] { "link loss", "help please" };
        private string outputPath = @"C:\Users\metastruct\Desktop\syslog.csv"; /* Location to store events */
        private string source; private string log;
        
        public logHandler(string sourceIP, string logData) /* Initialize object and clean up the raw data */
        {
            source = sourceIP.Trim(); /* Client IP */
            log = logData.Replace(Environment.NewLine, "").Trim(); /* Syslog data */
        }

        public void handleLog() /* Store the syslog and determine whether to trigger an email notification */
        {
            /* Store the syslog using a new thread */
            new Thread(new outputCsvRow(outputPath, new string[] { source, log }).addRow).Start();
            for (int i = 0; i < emailTriggers.Count(); i++) { if (log.Contains(emailTriggers[i])) { emailEvent(); } }
            /* Search for trigger strings and send email if found */

            return;
        }

        private void emailEvent() /* Send email notification */
        {
            try
            {
                MailMessage notificationEmail = new MailMessage();
                notificationEmail.Subject = "SysLog Event";
                notificationEmail.IsBodyHtml = true;
                notificationEmail.Body = "<b>SysLog Event Triggered:<br/><br/>Time: </b><br/>" + 
                    DateTime.Now.ToString() + "<br/><b>Source IP: </b><br/>” + 
                    source + “<br/><b>Event: </b><br/>" + log; /* Throw in some basic HTML for readability */
                notificationEmail.From = new MailAddress("SysLog@metastruct.com", "SysLog Server"); /* From Address */
                notificationEmail.To.Add(new MailAddress("metastructblog@gmail.com", "metastruct")); /* To Address */
                SmtpClient emailClient = new SmtpClient("10.10.10.10"); /* Address of your SMTP server of choice */
                //emailClient.UseDefaultCredentials = false; /* If your SMTP server requires credentials to send email */
                //emailClient.Credentials = new NetworkCredential(“username”, “password”); /* Supply User Name and Password */
                emailClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                emailClient.Send(notificationEmail); /* Send the email */
            }
            catch (Exception ex) { Console.WriteLine(ex.ToString()); }
            return;
        }
    }

    class outputCsvRow
    {
        private string formattedRow = null;
        private string outputPath = null;

        public outputCsvRow(string filePath, string[] columns) /* Initialize object */
        {
            outputPath = filePath;
            formattedRow = (char)34 + DateTime.Now.ToString() + (char)34; /* Construct csv row starting with the timestamp */
            for (int i = 0; i < columns.Count(); i++) { formattedRow += "," + (char)34 + columns[i] + (char)34; }
        }

        public void addRow()
        {
            int attempts = 0;
            bool canAccess = false;
            StreamWriter logWriter = null;
            if (!File.Exists(outputPath)) /* If the file doesn't exist, give it some column headers */
            {
                logWriter = new StreamWriter(outputPath, true);
                logWriter.WriteLine((char)34 + "Event_Time" + (char)34 + "," + 
                  (char)34 + "Device_IP" + (char)34 + "," + (char)34 + "SysLog" + (char)34);
                logWriter.Close();
            }
            /* Thread safety first! This is a poor man's SpinLock */
            while (true)
            {
                try
                {
                    logWriter = new StreamWriter(outputPath, true); /* Try to open the file for writing */
                    canAccess = true; /* Success! */
                    break;
                }
                catch (IOException ex)
                {
                    if (attempts < 15) { attempts++; Thread.Sleep(50); }
                    else { Console.WriteLine(ex.ToString()); break; } /* Give up after 15 attempts */
                }
            }
            if (canAccess) /* Write the line if the file is accessible */
            {
                logWriter.WriteLine(formattedRow);
                logWriter.Close();
            }
            return;
        }
    }
}

Points of Interest

An additional feature that may prove useful would be a function to cross reference IP addresses with a more "user friendly" device name. This would be most useful if the emails or logs are being used by multiple users, or if there are a large amount of devices and remembering the IP addresses of all of them isn't probable. For my "live" application, I also use that feature and separate log files for each device. Word of warning, some devices send tons of these events and your light weight CSV file can become enormous if you let it. I also added some code in the Main block to archive the logs on a rolling seven day basis. For that you'll probably have to halt receiving logs briefly to avoid an IOException.

History 

  • This is version 1, help me improve!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here