Click here to Skip to main content
15,897,518 members
Articles / Desktop Programming / Windows Forms

Clog: Client Logging, WPF Edition

Rate me:
Please Sign up or sign in to vote.
4.51/5 (68 votes)
25 Dec 2008LGPL312 min read 156.2K   965   114  
A customizable log provider system that allows you to harness your existing logging system to log client side messages to your server using WCF. Includes WPF sample applications.
/*
<File>
	<Copyright>Copyright © 2007, Daniel Vaughan. All rights reserved.</Copyright>
	<License see="prj:///Documentation/License.txt"/>
	<Owner Name="Daniel Vaughan" Email="dbvaughan@gmail.com"/>
	<CreationDate>2007/11/19 20:00</CreationDate>
	<LastSubmissionDate>$Date: $</LastSubmissionDate>
	<Version>$Revision: $</Version>
</File>
*/

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Browser;
using Orpius.Logging.ClientLogging;

namespace Orpius.Logging
{
	/// <summary>
	/// The default implementation of an <see cref="ILog"/>.
	/// </summary>
	class Log : ILog
	{
		readonly Type hostType;
		readonly string name;
		JsonLoggingService loggingServiceField;
		readonly object loggingServiceLock = new object();
		ClientConfigurationData clientConfig;
		readonly object loggingConfigLock = new object();
		static Uri pageUri = HtmlPage.DocumentUri;

		#region event InternalError

		event EventHandler<InternalErrorEventArgs> internalError;

		public event EventHandler<InternalErrorEventArgs> InternalError
		{
			add
			{
				internalError += value;
			}
			remove
			{
				internalError -= value;
			}
		}

		protected void OnInternalError(InternalErrorEventArgs e)
		{
			if (internalError != null)
			{
				internalError(this, e);
			}
		}

		#endregion

		public string Name
		{
			get
			{
				if (hostType != null)
				{
					return GetName(hostType);
				}
				return name;
			}
		}

		/// <summary>
		/// Gets the name for the log using the specified hostType.
		/// <seealso cref="Log.GetUrlForLogName"/>
		/// </summary>
		/// <param name="hostType">Type of the host where the log is located.</param>
		/// <returns>The name of the log.</returns>
		public static string GetName(Type hostType)
		{
			return string.Format("{0},{1}", hostType.FullName, GetUrlForLogName());
		}

		/// <summary>
		/// Gets the name of the URL for the log data.
		/// If we are logging from localhost then we do 
		/// not use the port number, because it is assumed
		/// that we are debugging.
		/// </summary>
		/// <returns>The url of the page without the querystring.</returns>
		static string GetUrlForLogName()
		{
			string result;
//			Uri uri;
//			try
//			{
//				uri = HtmlPage.DocumentUri;
//			}
//			catch (InvalidOperationException ex)
//			{	/* We can't retrieve the document uri 
//				 if the log is in*/
//				return string.Empty;
//			}
			
			if (pageUri.Host.ToLower() == "localhost" || pageUri.Host.Contains("127.0.0."))
			{
				result = string.Format("{0}", pageUri.LocalPath);
			}
			else
			{
				if (pageUri.Port == 80 || pageUri.Port == 443)
				{
					result = string.Format("{0}://{1}{2}", pageUri.Scheme, pageUri.Host, pageUri.LocalPath);
				}
				else
				{
					result = string.Format("{0}://{1}:{2}{3}", pageUri.Scheme, pageUri.Host, pageUri.Port, pageUri.LocalPath);
				}
			}
			
			return result.ToLower();				
		}

		const int webserviceTimeout = 15000;
		const int webserviceSleep = 500;

		ClientConfigurationData GetConfigurationData(ClientInfo clientInfo)
		{
			if (clientConfig == null 
				|| clientConfig.RetrievedOn.AddSeconds(clientConfig.ExpiresInSeconds) < DateTime.Now)
			{
				lock (loggingConfigLock)
				{
					if (clientConfig == null 
						|| clientConfig.RetrievedOn.AddSeconds(clientConfig.ExpiresInSeconds) < DateTime.Now)
					{
						try
						{
							clientConfig = LoggingService.GetConfiguration(clientInfo);
							clientConfig.RetrievedOn = DateTime.Now;
						}
						catch (InvalidOperationException)
						{
							clientConfig = null;
							SynchronizationContext.Current.Send(delegate
                            	{
                            		clientConfig = LoggingService.GetConfiguration(clientInfo);
                            		clientConfig.RetrievedOn = DateTime.Now;
                            	}, null);
							int accumulator = webserviceTimeout;
							while (clientConfig == null && accumulator > 0)
							{
								accumulator -= webserviceSleep;
								Thread.Sleep(webserviceSleep);
							}
						}
					}
				}
			}
			return clientConfig;
		}

		JsonLoggingService LoggingService
		{
			get
			{
				if (loggingServiceField == null)
				{
					lock (loggingServiceLock)
					{
						if (loggingServiceField == null)
						{
							loggingServiceField = new JsonLoggingService();
							/* TODO: create setting for url of logging service. 
							 * Maybe useful when we have cross domain web service support. */
							loggingServiceField.Url = "JsonClogService.asmx";
						}
					}
				}
				return loggingServiceField;
			}
		}

		#region LogEntrySent
		event EventHandler<LogEventArgs> logEntrySent;

		public event EventHandler<LogEventArgs> LogEntrySent
		{
			add
			{
				logEntrySent += value;
			}
			remove
			{
				logEntrySent -= value;
			}
		}

		protected void OnLogEntrySent(LogEventArgs e)
		{
			if (logEntrySent != null)
			{
				logEntrySent(this, e);
			}
		}
		#endregion

		#region WriteRequested
		event EventHandler<LogEventArgs> logEntrySendAttempt;

		public event EventHandler<LogEventArgs> WriteRequested
		{
			add
			{
				logEntrySendAttempt += value;
			}
			remove
			{
				logEntrySendAttempt -= value;
			}
		}

		protected void OnLogEntrySendAttempt(LogEventArgs e)
		{
			if (logEntrySendAttempt != null)
			{
				logEntrySendAttempt(this, e);
			}
		}
		#endregion

		public Log(Type hostType)
		{
			this.hostType = hostType;
		}

		public Log(string name)
		{
			this.name = name;
		}

		#region Logging Overloads
		public void Debug(string message)
		{
			WriteLogEntry(LogLevel.Debug, message, null);
		}

		public void Debug(string message, Exception exception)
		{
			WriteLogEntry(LogLevel.Debug, message, exception);
		}

		public void Info(string message)
		{
			WriteLogEntry(LogLevel.Info, message, null);
		}

		public void Info(string message, Exception exception)
		{
			WriteLogEntry(LogLevel.Info, message, exception);
		}

		public void Warn(string message)
		{
			WriteLogEntry(LogLevel.Warn, message, null);
		}

		public void Warn(string message, Exception exception)
		{
			WriteLogEntry(LogLevel.Warn, message, exception);
		}

		public void Error(string message)
		{
			WriteLogEntry(LogLevel.Error, message, null);
		}

		public void Error(string message, Exception exception)
		{
			WriteLogEntry(LogLevel.Error, message, exception);
		}

		public void Fatal(string message)
		{
			WriteLogEntry(LogLevel.Fatal, message, null);
		}

		public void Fatal(string message, Exception exception)
		{
			WriteLogEntry(LogLevel.Fatal, message, exception);
		}
		#endregion

		void WriteLogEntry(LogLevel logLevel, string message, Exception exception)
		{
			try
			{
				WriteLogEntryAux(logLevel, message, exception);
			}
			catch (Exception ex)
			{
				string errorMessage = string.Format("Unable to write to log: {0} {1}{2}",
				                                    ex, Environment.NewLine, ex.StackTrace);
				Console.WriteLine(errorMessage);
				OnInternalError(new InternalErrorEventArgs(ex));
			}
		}
		
		void WriteLogEntryAux(LogLevel logLevel, string message, Exception exception)
		{
			ExceptionMemento memento = null;
			if (exception != null)
			{
				memento = CreateMemento(exception);
			}

			LogEntryData entry = new LogEntryData()
			{
				LogLevel = logLevel,
				Message = message,
				ExceptionMemento = memento,
				CodeLocation = GetLocation(),
				LogName = Name,
				ThreadName = System.Threading.Thread.CurrentThread.Name,
				ManagedThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId,
				Url = pageUri.ToString(),
				OccuredAt = DateTime.Now
			};

			OnLogEntrySendAttempt(new LogEventArgs(entry));
			
			ClientInfo info = new ClientInfo();
			info.LogName = entry.LogName;
			info.MachineName = entry.MachineName;
			info.Url = entry.Url;
			info.UserName = entry.UserName;

			ClientConfigurationData config = GetConfigurationData(info);
			
			if (config == null || !config.LogEnabled
				/* Enable enum and remove cast 
				 * when Silverlight supports Enum serialization. */
					|| (int)entry.LogLevel < config.LogLevel)
			{
				return;
			}
			/* Send of the log entry to the web service. */
			try
			{
				LoggingService.BeginWriteEntry(entry, delegate(IAsyncResult result)
				                                      	{
				                                      		OnLogEntrySent(new LogEventArgs(entry));
				                                      	}, null);
			}
			catch (InvalidOperationException)
			{
				SynchronizationContext.Current.Send(delegate
				{
					LoggingService.BeginWriteEntry(entry, delegate(IAsyncResult result)
					{
						OnLogEntrySent(new LogEventArgs(entry));
					}, null);
				}, null);
			}
		}
 
		static ExceptionMemento CreateMemento(Exception exception)
		{
			ExceptionMemento memento = new ExceptionMemento()
			{
				TypeName = exception.GetType().AssemblyQualifiedName,
				Message = exception.Message,
				Source = string.Empty,//exception.Source, No Source property in Silverlight BCL.
				StackTrace = exception.StackTrace ?? string.Empty,
				HelpLink = string.Empty
			};
			return memento;
		}

		CodeLocation GetLocation()
		{
			CodeLocation location = new CodeLocation()
			{
				ClassName = hostType != null ? hostType.AssemblyQualifiedName : string.Empty,
				MethodName = string.Empty,
				FileName = string.Empty,
				LineNumber = -1
			};
			return location;
		}

		/// <summary>
		/// Gets the caller location.
		/// Throws MethodAccessException. 
		/// We can't do this in Silverlight, so don't use it.
		/// Left here for your amusement.
		/// </summary>
		/// <returns>The source code location that the call 
		/// to log was made.</returns>
		static CodeLocation GetCallerLocation()
		{
			StackTrace trace;
			string className;
			string methodName;
			string fileName;
			int lineNumber;
			StackFrame frame = null;
			
			try
			{
				trace = new System.Diagnostics.StackTrace(true);
				frame = trace.GetFrame(2);
			}
			catch (MethodAccessException ex)
			{
				string stackTrace = ex.StackTrace;
			}

			if (frame != null)
			{
				className = frame.GetMethod().ReflectedType.FullName;
				methodName = frame.GetMethod().Name;
				fileName = frame.GetFileName();
				lineNumber = frame.GetFileLineNumber();
			}
			else
			{
				className = string.Empty;
				methodName = string.Empty;
				fileName = string.Empty;
				lineNumber = 0;
			}

			CodeLocation location = new CodeLocation()
			{
				ClassName = className,
				MethodName = methodName,
				FileName = fileName,
				LineNumber = lineNumber
			};
			return location;
		}
	}
}

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
Engineer
Switzerland Switzerland
Daniel is a former senior engineer in Technology and Research at the Office of the CTO at Microsoft, working on next generation systems.

Previously Daniel was a nine-time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Codon.

Would you like Daniel to bring value to your organisation? Please contact

Blog | Twitter


Xamarin Experts
Windows 10 Experts

Comments and Discussions