Multithreaded, Customizable SysLog Server - C#






3.67/5 (6 votes)
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!