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
