Click here to Skip to main content
15,886,518 members
Articles / Desktop Programming / XAML

Diagnostic Explorer

Rate me:
Please Sign up or sign in to vote.
4.93/5 (41 votes)
29 Nov 2010LGPL315 min read 82.3K   1.5K   120  
A .NET library and website which allows developers to expose and view arbitrary diagnostic information about their .NET processes.
#region Copyright

// Diagnostic Explorer, a .Net diagnostic toolset
// Copyright (C) 2010 Cameron Elliot
// 
// This file is part of Diagnostic Explorer.
// 
// Diagnostic Explorer is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Diagnostic Explorer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Diagnostic Explorer.  If not, see <http://www.gnu.org/licenses/>.
// 
// http://diagexplorer.sourceforge.net/

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using DiagnosticExplorer.Util;

namespace DiagnosticExplorer
{
	public class EventSink : IDisposable
	{
		private static Timer timer;
		private static TimeSpan messageLife = TimeSpan.FromMinutes(1);
		public const int MaxMessages = 1000;
		private static WeakReferenceHash<EventSink> sinks = new WeakReferenceHash<EventSink>();
		private const int MaxLength = 102400;

		public static WeakReferenceHash<EventSink> Sinks
		{
			get { return sinks; }
		}

		static EventSink()
		{
			timer = new Timer(Purge, null, 0, 5000);
		}

		private EventSink(string name, string category)
		{
			Identifier = Guid.NewGuid().ToString("N");
			Count = 1;
			Events = new List<SystemEvent>();
			Name = name;
			Category = category;
		}

		~EventSink()
		{
			Dispose(false);
		}

		public void Info(string message)
		{
			LogEvent(EventSeverity.Low, message);
		}

		public void Info(string message, Exception ex)
		{
			LogEvent(EventSeverity.Low, ex, message);
		}

		public void InfoFormat(string format, params object[] args)
		{
			LogEvent(EventSeverity.Low, FormatMessage(format, args));
		}

		public void Warning(string message)
		{
			LogEvent(EventSeverity.Medium, message);
		}

		public void Warning(string message, Exception ex)
		{
			LogEvent(EventSeverity.Medium, ex, message);
		}

		public void WarningFormat(string format, params object[] args)
		{
			LogEvent(EventSeverity.Medium, FormatMessage(format, args));
		}

		public void Error(string message)
		{
			LogEvent(EventSeverity.High, message);
		}

		public void Error(string message, Exception ex)
		{
			LogEvent(EventSeverity.High, ex, message);
		}

		public void ErrorFormat(string format, params object[] args)
		{
			LogEvent(EventSeverity.High, FormatMessage(format, args));
		}

		private static string FormatMessage(string format, object[] args)
		{
			try
			{
				if (args == null || args.Length == 0)
					return format;

				return string.Format(format, args);
			}
			catch
			{
				return string.Format("Error formatting error message [{0}]", format);
			}
		}

		public void Dispose()
		{
			Dispose(true);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
				sinks.Remove(Name);
		}

		public string Name { get; private set; }

		public string Category { get; private set; }


		public long Count { get; set; }

		public string Identifier { get; private set; }

		public List<SystemEvent> Events { get; private set; }

		public static EventSink GetSink(string name)
		{
			return GetSink(name, null);
		}

		public static EventSink GetSink(string name, string category)
		{
			string key = string.Format("{0}.{1}", name, category);
			lock (sinks)
			{
				EventSink existing = sinks.GetItem(key);
				if (existing == null)
				{
					existing = new EventSink(name, category);
					sinks.Add(key, existing);
				}
				return existing;
			}
		}

		private static void Purge(object sender)
		{
			try
			{
				foreach (EventSink sink in sinks.GetItems())
					sink.Purge();
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		private void Purge()
		{
			try
			{
				lock (Events)
				{
					while (Events.Count != 0 && ShouldPurgeFirst(Events))
						Events.RemoveAt(0);
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		private bool ShouldPurgeFirst(IList<SystemEvent> events)
		{
			if (events.Count == 0) return false;
			if (events.Count > MaxMessages) return true;
			if (DateTime.Now - events[0].Date > messageLife) return true;

			return false;
		}

		public void LogEvent(EventSeverity severity, string message)
		{
			LogEvent(severity, message, null);
		}

		public void LogEvent(EventSeverity severity, Exception ex)
		{
			LogEvent(severity, ex.Message, ex.ToString());
		}

		public void LogEvent(EventSeverity severity, Exception ex, string message)
		{
			LogEvent(severity, message, ex == null ? null : ex.ToString());
		}

		public void LogEvent(EventSeverity severity, string message, string detail)
		{
			try
			{
				CleanMessageAndDetail(ref message, ref detail);

				SystemEvent evt = new SystemEvent();
				evt.Date = DateTime.Now;
				evt.Severity = severity;
				evt.Message = MaxLengthString(message, MaxLength);
				evt.Detail = MaxLengthString(detail, MaxLength);
				lock (Events)
				{
					evt.Id = Count++;
					Events.Add(evt);
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
			finally
			{
				RemoveExcessMessages();
			}
		}

		/// <summary>
		/// If there is no detail but a massive message, put the whole message into detail
		/// and leave only the first line in message
		/// </summary>
		private void CleanMessageAndDetail(ref string message, ref string detail)
		{
			if (!string.IsNullOrEmpty(detail)) return;
			if (string.IsNullOrEmpty(message)) return;

			int index = message.IndexOf("\n");
			if (index != -1)
			{
				detail = message;
				message = message.Substring(0, index);
			}
		}

		private void RemoveExcessMessages()
		{
			try
			{
				lock (Events)
				{
					if (Events.Count > MaxMessages)
						Events.RemoveRange(0, Events.Count - MaxMessages);
				}
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex);
			}
		}

		private static string MaxLengthString(string s, int maxLength)
		{
			if (s == null) return s;
			if (s.Length <= maxLength) return s;

			return s.Substring(0, maxLength);
		}

		public EventResponse GetEvents(SinkContext req)
		{
			if (req == null) throw new ArgumentNullException("req");

			EventResponse response = new EventResponse(Name, Category);

			lock (Events)
			{
				if (req.Identifier != Identifier)
				{
					response.Events.AddRange(Events);
				}
				else
				{
					for (int i = Events.Count - 1; i >= 0; i--)
					{
						SystemEvent evt = Events[i];
						if (evt.Id > req.LastEventId)
							response.Events.Add(evt);
					}
					response.Events.Reverse();
				}

				req.Identifier = Identifier;
				if (response.Events.Count != 0)
				{
					req.LastEventId = response.Events[response.Events.Count - 1].Id;
					req.LastEventDate = response.Events[response.Events.Count - 1].Date;
				}

				return response;
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer
United Kingdom United Kingdom
I am a software developer originally from Auckland, New Zealand. I have lived and worked in London since 2005.

Comments and Discussions