UdpTraceListener - a UDP TraceListener compatible with log4net/log4j
March 13, 2013
This class is a TraceListener implementation that uses the log4j XML format and sends the XML to a UDP socket. This means you can configure a trace listener to send all your logs to something like http://log2console.codeplex.com.. There is also Harvester which has a TraceListener implementation for streaming over a network.
Just configure a new UDP receiver in log4console and they messages will start to stream through. This works nicely with LightSpeed, where you can configure it to send its SQL statements to a TraceListener.
Feel free to include the source wherever you need it, it’s under an MIT licence.
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Trace.Listeners.Add(new UdpTraceListener()); | |
Thread thread1 = new Thread(() => { | |
Trace.WriteLine("I'm in a new thread 1"); | |
Trace.WriteLine("I'm in a new thread 2"); | |
Trace.WriteLine("I'm in a new thread 3"); | |
}); | |
Thread thread2 = new Thread(() => | |
{ | |
Trace.WriteLine("I'm in a new thread also 1"); | |
Trace.WriteLine("I'm in a new thread also 2"); | |
Trace.WriteLine("I'm in a new thread also 3"); | |
}); | |
thread1.Start(); | |
Trace.WriteLine("Hello world"); | |
Trace.WriteLine("Warning Will Robinson", "warning"); | |
thread2.Start(); | |
Trace.WriteLine("Catastrophic failure", "error"); | |
Trace.WriteLine(42, "fatal"); | |
} | |
} |
public class UdpTraceListener : TraceListener | |
{ | |
// For 3.5/2 you can use a List but the class is no longer thread safe. | |
//private Stack<string> _messageBuffer; | |
private UdpClient _udpClient; | |
private ConcurrentStack<string> _messageBuffer; | |
private string _loggerName; | |
private static readonly string _xmlPrefix = "log4j"; | |
private static readonly string _xmlNamespace = "http://jakarta.apache.org/log4j/"; | |
public override bool IsThreadSafe | |
{ | |
get | |
{ | |
return true; | |
} | |
} | |
public UdpTraceListener() | |
: this("localhost", 7071, "UdpTraceListener") | |
{ | |
} | |
public UdpTraceListener(string host, int port, string loggerName) | |
{ | |
_loggerName = loggerName; | |
_messageBuffer = new ConcurrentStack<string>(); | |
_udpClient = new UdpClient(); | |
_udpClient.Connect(host, port); | |
} | |
public override void Write(string message) | |
{ | |
Write(message, "info"); | |
} | |
public override void WriteLine(string message) | |
{ | |
Write(message, "info"); | |
Flush(); | |
} | |
public override void WriteLine(string message, string category) | |
{ | |
_messageBuffer.Push(GetEventXml(message, category)); | |
Flush(); | |
} | |
public override void Write(string message, string category) | |
{ | |
_messageBuffer.Push(GetEventXml(message, category)); | |
} | |
private string GetEventXml(string message, string category) | |
{ | |
// The format: | |
//<log4j:event logger="{LOGGER}" level="{LEVEL}" thread="{THREAD}" timestamp="{TIMESTAMP}"> | |
// <log4j:message><![CDATA[{ERROR}]]></log4j:message> | |
// <log4j:NDC><![CDATA[{MESSAGE}]]></log4j:NDC> | |
// <log4j:throwable><![CDATA[{EXCEPTION}]]></log4j:throwable> | |
// <log4j:locationInfo class="org.apache.log4j.chainsaw.Generator" method="run" file="Generator.java" line="94"/> | |
// <log4j:properties> | |
// <log4j:data name="log4jmachinename" value="{SOURCE}"/> | |
// <log4j:data name="log4japp" value="{APP}"/> | |
// </log4j:properties> | |
//</log4j:event> | |
string level = "INFO"; | |
if (string.IsNullOrEmpty(category)) | |
category = "info"; | |
switch (category.ToLower()) | |
{ | |
case "fatal": | |
level = "FATAL"; | |
break; | |
case "warning": | |
case "warn": | |
level = "WARN"; | |
break; | |
case "error": | |
level = "ERROR"; | |
break; | |
case "debug": | |
level = "DEBUG"; | |
break; | |
case "trace": | |
level = "TRACE"; | |
break; | |
default: | |
break; | |
} | |
StringBuilder builder = new StringBuilder(); | |
XmlWriterSettings settings = new XmlWriterSettings(); | |
settings.OmitXmlDeclaration = true; | |
XmlWriter writer = XmlWriter.Create(builder, settings); | |
WriteLog4jElement(writer, "event"); | |
writer.WriteAttributeString("logger", _loggerName); | |
writer.WriteAttributeString("level", level); | |
writer.WriteAttributeString("thread", Thread.CurrentThread.ManagedThreadId.ToString()); | |
writer.WriteAttributeString("timestamp", XmlConvert.ToString(ConvertToUnixTimestamp(DateTime.Now))); | |
WriteLog4jElement(writer, "message"); | |
writer.WriteCData(RemoveInvalidXmlChars(message)); | |
writer.WriteEndElement(); | |
WriteLog4jElementString(writer, "NDC", ""); | |
WriteLog4jElementString(writer, "throwable", ""); | |
WriteLog4jElement(writer, "locationInfo"); | |
writer.WriteAttributeString("class", ""); | |
writer.WriteAttributeString("run", ""); | |
writer.WriteAttributeString("file", ""); | |
writer.WriteAttributeString("line", "1"); | |
writer.WriteEndElement(); | |
WriteLog4jElement(writer, "properties"); | |
WriteLog4jElement(writer, "data"); | |
writer.WriteAttributeString("name", "log4jmachinename"); | |
writer.WriteAttributeString("value", Environment.MachineName); | |
writer.WriteEndElement(); | |
WriteLog4jElement(writer, "data"); | |
writer.WriteAttributeString("name", "log4japp"); | |
writer.WriteAttributeString("value", Assembly.GetCallingAssembly().FullName); | |
writer.WriteEndElement(); | |
writer.WriteEndElement(); | |
writer.WriteEndElement(); | |
writer.Flush(); | |
return builder.ToString(); | |
} | |
private string RemoveInvalidXmlChars(string text) | |
{ | |
var validXmlChars = text.Where(x => XmlConvert.IsXmlChar(x)).ToArray(); | |
return new string(validXmlChars); | |
} | |
private void WriteLog4jElement(XmlWriter writer, string name) | |
{ | |
writer.WriteStartElement(_xmlPrefix, name, _xmlNamespace); | |
} | |
private void WriteLog4jElementString(XmlWriter writer, string name, string value) | |
{ | |
writer.WriteElementString(_xmlPrefix, name, _xmlNamespace, value); | |
} | |
private double ConvertToUnixTimestamp(DateTime date) | |
{ | |
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); | |
TimeSpan sinceEpoch = date.ToUniversalTime() - epoch; | |
return Math.Floor(sinceEpoch.TotalMilliseconds); | |
} | |
public override void Flush() | |
{ | |
foreach (string xmlMessage in _messageBuffer) | |
{ | |
byte[] payload = Encoding.UTF8.GetBytes(xmlMessage); | |
_udpClient.Send(payload, payload.Length); | |
} | |
_messageBuffer.Clear(); | |
base.Flush(); | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
Flush(); | |
_udpClient.Close(); | |
base.Dispose(disposing); | |
} | |
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) | |
{ | |
switch (eventType) | |
{ | |
case TraceEventType.Critical: | |
case TraceEventType.Error: | |
WriteLine(string.Format(format, args), "error"); | |
break; | |
case TraceEventType.Verbose: | |
WriteLine(string.Format(format, args), "debug"); | |
break; | |
case TraceEventType.Warning: | |
WriteLine(string.Format(format, args), "warn"); | |
break; | |
case TraceEventType.Information: | |
case TraceEventType.Resume: | |
case TraceEventType.Start: | |
case TraceEventType.Stop: | |
case TraceEventType.Suspend: | |
case TraceEventType.Transfer: | |
default: | |
WriteLine(string.Format(format, args), "error"); | |
break; | |
} | |
} | |
} |
I'm Chris Small, a software engineer working in London. This is my tech blog. Find out more about me via Github, Stackoverflow, Resume