Click here to Skip to main content
15,892,059 members
Articles / Desktop Programming / WPF

Calcium: A modular application toolset leveraging PRISM – Part 1

Rate me:
Please Sign up or sign in to vote.
4.93/5 (70 votes)
1 Jun 2009BSD17 min read 251.5K   208  
Calcium provides much of what one needs to rapidly build a multifaceted and sophisticated modular application. Includes a host of modules and services, and an infrastructure that is ready to use in your next application.
/*
<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.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Security;
using System.Threading;
using System.Web.Management;
using System.Web.Security;
using System.Xml;

using DanielVaughan.Logging.Configuration;
using DanielVaughan.Logging.LogEntries;

namespace DanielVaughan.Logging
{
	/// <summary>
	/// The main application log.
	/// Use this static class to write to a
	/// <see cref="ILogStrategy"/> defined in the current 
	/// <see cref="ILogStrategyProvider"/>.
	/// </summary>
	public static partial class Log
	{
//		static bool useAspNetMembership;
//		static bool partialTrust = true;

		static Log()
		{
			SkipFrameCount = 4;

			try
			{
				InitFromConfig();
			}
			catch (Exception ex)
			{
				if (InternalLog.FatalEnabled)
				{
					InternalLog.Fatal("Unable to init from config.", ex);
				}
			}
		}

		public static int SkipFrameCount { get; set; }

		static bool ValidateFilters(ILogStrategy logStrategy, LogEntryOrigin origin, IClientInfo entry)
		{
			ArgumentValidator.AssertNotNull(logStrategy, "logStrategy");
			ArgumentValidator.AssertNotNull(entry, "entry");

			var filters = LogRepository.Instance.GetFilters(logStrategy);
			foreach (IFilter filter in filters)
			{
				if (!filter.IsValid(origin, entry))
				{
					return false;
				}
			}
			return true;
		}

		internal static LogLevel GetLogLevel(IClientInfo info)
		{	
			LogLevel minumLevel = LogLevel.None;

			foreach (var logStrategy in LogRepository.Instance.LogStrategies)
			{
				var level = logStrategy.GetLogLevel(info);
				if (level < minumLevel)
				{
					minumLevel = level;
				}
			}

			return minumLevel;
		}

		internal static void LogMessage(IClientLogEntry logEntry)
		{
			/* TODO: use parallel foreach. */
			foreach (var logStrategy in LogRepository.Instance.LogStrategies)
			{
				if (IsEnabled(logStrategy, LogEntryOrigin.Remote, logEntry))
				{
					logStrategy.Write(logEntry);
				}
			}
		}

		/// <summary>
		/// Loads the providers.
		/// </summary>
		static void InitFromConfig()
		{
			InternalLog.Debug("Initializing from config."); /* TODO: Make localizable resource. */

			var configurationElement = ConfigurationManager.GetSection("Clog") as XmlElement;
			if (configurationElement != null)
			{
				var logLevelAttribute = configurationElement.Attributes["InternalLogLevel"];
				LogLevel logLevel = LogLevel.None;
				bool configurationInvalid = false;
				if (logLevelAttribute != null && !string.IsNullOrEmpty(logLevelAttribute.Value))
				{
					try
					{
						logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), logLevelAttribute.Value);
					}
					catch (Exception ex)
					{
						configurationInvalid = true;
						InternalLog.Error("InternalLogLevel attribute is invalid.", ex); /* TODO: Make localizable resource. */
					}
				}
				InternalLog.LogLevel = logLevel;

				var skipFrameCountAttribute = configurationElement.Attributes["SkipFrameCount"];
				if (skipFrameCountAttribute != null && !string.IsNullOrEmpty(skipFrameCountAttribute.Value))
				{
					int frameCount;
					if (int.TryParse(skipFrameCountAttribute.Value, out frameCount))
					{
						SkipFrameCount = frameCount;
					}
					else
					{
						configurationInvalid = true;
						InternalLog.Error("SkipFrameCount attribute is invalid. Value should be an integer. Attribute value: " 
							+ skipFrameCountAttribute.Value);
					}
				}

				LogRepository.Instance.Load(configurationElement);
				InternalLog.Info(string.Format("Clog configuration loaded {0}.", 
					configurationInvalid ? "with errors" : "successfully")); /* TODO: Make localizable resource. */
			}
			else
			{
				InternalLog.Info("Clog config section not found."); /* TODO: Make localizable resource. */
			}
			

//			partialTrust = !IsPermissionGranted(new SecurityPermission(PermissionState.Unrestricted));
//			section = (ClientLoggingConfigSection)WebConfigurationManager.GetSection("ClientLogging");
//			useAspNetMembership = section.UseAspNetMembership && partialTrust;
		}

		static bool IsPermissionGranted(IPermission requestedPermission)
		{
			try
			{
				/* Try and get this permission. */
				requestedPermission.Demand();
				return true;
			}
			catch
			{
				return false;
			}
		}

		/// <summary>
		/// Determines whether logging to the specified origin is enabled.
		/// Checks whether the current provider is null 
		/// and if not, then whether all filters are validate.
		/// Each <see cref="IFilter"/> makes use of the origin
		/// parameter and decides whether it is, itself, valid.
		/// If any filters are not valid, then this method
		/// returns <code>false</code>.
		/// </summary>
		/// <param name="logStrategy">The log strategy to test for whether it is enabled.</param>
		/// <param name="origin">The origin.</param>
		/// <param name="info">Client information used during evaluation.</param>
		/// <returns>
		/// 	<c>true</c> if logging is enable; otherwise, <c>false</c>.
		/// </returns>
		public static bool IsEnabled(ILogStrategy logStrategy, LogEntryOrigin origin, IClientInfo info)
		{
			try
			{
				return IsEnabledAux(logStrategy, origin, info);
			}
			catch (Exception ex)
			{
				InternalLog.Error("Error determining enabled status of log.", ex);
			}
			return false;
		}

		public static bool IsEnabled(LogEntryOrigin origin, IClientInfo info)
		{
			foreach (var logStrategy in LogRepository.Instance.LogStrategies)
			{
				if (IsEnabledAux(logStrategy, origin, info))
				{
					/* If even one strategy is enabled, then logging is enabled. */
					return true;
				}
			}
			return false;
		}

		static bool IsEnabledAux(ILogStrategy logStrategy, LogEntryOrigin origin, IClientInfo info)
		{
			return ValidateFilters(logStrategy, origin, info);
		}

		static bool IsLevelEnabled(LogLevel logLevel)
		{
			try
			{
				return IsLevelEnabledAux(logLevel);
			}
			catch (Exception ex)
			{
				InternalLog.Error("IsLevelEnabledAux raised exception.", ex);
			}
			return false;
		}

		static bool IsLevelEnabledAux(LogLevel logLevel)
		{
			foreach (var logStrategy in LogRepository.Instance.LogStrategies)
			{
				LogLevel strategyLevel;
				try
				{
					strategyLevel = logStrategy.GetLogLevel(null);
				}
				catch (Exception ex)
				{
					InternalLog.Error("Problem evaluating LogStrategy LogLevel.", ex);
					continue;
				}

				if (strategyLevel < logLevel)
				{
					return true;
				}
			}
			return false;
		}

		static void WriteLogEntry(LogLevel logLevel, string message, Exception exception, IDictionary<string, object> properties)
		{
			try
			{
				WriteLogEntryAux(logLevel, message, exception, properties);
			}
			catch (Exception ex)
			{
				if (InternalLog.ErrorEnabled)
				{
					InternalLog.Error("Error writing log.", ex);
				}
			}			
		}

		static void WriteLogEntryAux(LogLevel logLevel, string message, Exception exception, IDictionary<string, object> properties)
		{
			var codeLocation = GetCallerLocation(SkipFrameCount);
			var entry = new ServerLogEntry(logLevel, message)
			                   	{
									CodeLocation = codeLocation,
			                   		LogName = codeLocation.ClassName,
			                   		MachineName = SafeEnvironmentValues.MachineName,
			                   		UserName = SafeEnvironmentValues.UserName,
									OccuredAt = DateTime.Now,
									Properties = properties
			                   	};

			PopulateMembershipInfo(entry);

			bool eventRaised = false;
			object eventRaisedLock = new object();

			foreach (var logStrategy in LogRepository.Instance.LogStrategies)
			{
				try
				{
					if (!IsEnabled(logStrategy, LogEntryOrigin.Local, entry))
					{
						continue;
					}
				}
				catch (Exception ex)
				{
					InternalLog.Error(string.Format("Log strategy {0} failed on call to IsEnabled.", logStrategy), ex);
					continue;
				}

				if (exception != null)
				{
					entry.Exception = exception;
				}

				ILogStrategy logStrategy1 = logStrategy;
				ThreadPool.QueueUserWorkItem(delegate { 
						try
						{
							logStrategy1.Write(entry);
							if (!eventRaised)
							{
								lock (eventRaisedLock)
								{
									if (!eventRaised)
									{
										OnLogEntrySent(new LogEventArgs(entry));
										eventRaised = true;
									}
								}
							}
						}
						catch (Exception ex)
						{
							if (InternalLog.ErrorEnabled)
							{
								InternalLog.Error("Problem writing log to ILogStrategy " + logStrategy1.GetType(), ex);
							}
						}
					});
			}
		}

		static bool membershipEnabled;

		static void PopulateMembershipInfo(LogEntryData data)
		{
			if (/*useAspNetMembership && */membershipEnabled && string.IsNullOrEmpty(data.UserName))
			{	/* Populate the UserName property using the Membership provider. */
				MembershipUser user = null;
				string errorMessage = null;

				try
				{
					user = Membership.GetUser(true);
				}
				catch (Exception ex)
				{
					if (ex is SqlExecutionException 
						|| ex is SecurityException
						|| ex.InnerException is SqlExecutionException)
					{
						membershipEnabled = false;
						/* TODO: Make localizable resource. */
						errorMessage = string.Format("Unable to user ASP.NET Membership to get username. Will not try again. Exception: {0}", ex);
					}
					else
					{
						/* TODO: Make localizable resource. */
						errorMessage = string.Format("Unable to user ASP.NET Membership to get username. Exception: {0}", ex);
					}
					//System.Diagnostics.Debug.WriteLine(errorMessage, traceCategory);
				}
				if (!string.IsNullOrEmpty(errorMessage))
				{
					Info(errorMessage);
				}
				if (user != null)
				{
					data.UserName = user.UserName;
				}
			}
		}

		/* TODO: Make an extensibility point for GetCallerLocation. */
		/// <summary>
		/// Gets the caller location from the <see cref="StackTrace"/>.
		/// </summary>
		/// <returns>The code location that the call to log originated.</returns>
		static CodeLocation GetCallerLocation(int methodCallCount)
		{
			string className;
			string methodName;
			string fileName;
			int lineNumber;

			var stackTrace = new StackTrace(methodCallCount, true);
			var stackFrame = stackTrace.GetFrame(0);

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

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

		#region Write event
		static event EventHandler<LogEventArgs> write;

		/// <summary>
		/// Occurs when the active <see cref="ILogStrategy"/>
		/// is called upon to write a log entry.
		/// </summary>
		public static event EventHandler<LogEventArgs> Write
		{
			add
			{
				write += value;
			}
			remove
			{
				write -= value;
			}
		}

		static void OnLogEntrySent(LogEventArgs e)
		{
			if (write != null)
			{
				write(null, e);
			}
		}
		#endregion
	}
}

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 BSD License


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