using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Text;
namespace Ingenious.Mvc.Util{
/// <summary>
/// Provides a thin wrapper around the <see cref="TraceSource"/> class to simplify logging code.
/// </summary>
/// <remarks>
/// <para>
/// The <c>Log</c> class provides a useful wrapper around the <see cref="TraceSource"/> class. It does so to simplify the
/// code required to log messages.
/// </para>
/// <para>
/// Instances of <c>Log</c> can be created based on the name of a <see cref="Type"/> (<see cref="CreateForType"/>),
/// <see cref="Assembly"/> (<see cref="CreateForAssembly"/>) or namespace (<see cref="CreateForNamespace"/>). Alternatively,
/// the <see cref="CreateNamed"/> method allows an arbitrary name to be used.
/// </para>
/// <para>
/// The <see cref="Debug"/>, <see cref="Verbose"/>, <see cref="Information"/>, <see cref="Warning"/> and <see cref="Error"/>
/// methods can be used to log messages. The <see cref="Debug"/> overloads are conditionally compiled for debug builds only.
/// All of these methods have overloads that accept a format string and arguments.
/// </para>
/// <para>
/// In addition, the <see cref="Write"/> method provides a general means of logging at any specified level. This can be
/// useful where the log level is not known until runtime, or when the required level has no corresponding method such as
/// the <see cref="LogLevel.Critical"/> level.
/// </para>
/// <para>
/// The <see cref="IsVerboseEnabled"/>, <see cref="IsInformationEnabled"/>, <see cref="IsWarningEnabled"/> and
/// <see cref="IsErrorEnabled"/> properties can be used to determine whether a specified log level is enabled. There is no
/// <c>IsDebugEnabled</c> property, since that is determined at compile-time based on the <c>DEBUG</c> compiler symbol.
/// </para>
/// <para>
/// In addition, the <see cref="ShouldWrite"/> method provides a general means of determining whether a specified log level
/// is enabled. This can be useful where the log level is not known until runtime, or when the required level has no
/// corresponding method such as the <see cref="LogLevel.Critical"/> level.
/// </para>
/// <para>
/// The <see cref="Performance"/> methods can be used to measure the performance of a block of code. These methods return
/// an <see cref="IDisposable"/> instance that will automatically log performance information when disposed.
/// </para>
/// <para>
/// The <see cref="PushLogicalOperation"/> and <see cref="PopLogicalOperation"/> methods can be used to begin and end
/// logical operations. <see cref="PushLogicalOperation"/> returns an <see cref="IDisposable"/> implementation that will
/// automatically invoke <see cref="PopLogicalOperation"/> when disposed. As such, it can be used effectively from a
/// <c>using</c> block.
/// </para>
/// <para>
/// Logical operations are maintained in a stack. The stack of logical operations in progress can optionally be included in
/// each logged message (except for debug messages). This allows you to diagnose the logical operations that were in progress
/// when a message was logged.
/// </para>
/// </remarks>
/// <example>
/// The following code shows how a <c>Log</c> instance can be created for a specified type:
/// <code>
/// namespace MyNamespace
/// {
/// public class MyClass
/// {
/// private static readonly Log _log = Log.CreateForType(typeof(MyClass));
/// }
/// }
/// </code>
/// </example>
/// <example>
/// The following code shows how a <c>Log</c> instance can be created for use by an assembly:
/// <code>
/// namespace MyNamespace
/// {
/// internal class MyClass
/// {
/// internal static readonly Log Log = Log.CreateForAssembly(typeof(MyClass));
/// }
/// }
/// </code>
/// </example>
/// <example>
/// The following code logs some information to the log:
/// <code>
/// _log.Information("Here is some information.");
/// </code>
/// </example>
/// <example>
/// The following code logs some formatted information to the log:
/// <code>
/// _log.Information("Here is some information with a parameter at the end: {0}.", someValue);
/// </code>
/// </example>
/// <example>
/// The following code checks whether information will be logged before it attempts to do so:
/// <code>
/// if (_log.IsInformationEnabled)
/// {
/// _log.Information("Here is some information with parameters: {0}, {1}, {2}.", val1, val2, val3);
/// }
/// </code>
/// </example>
/// <example>
/// The following code logs a critical message:
/// <code>
/// _log.Write(LogLevel.Critical, "Some critical message.");
/// </code>
/// </example>
/// <example>
/// The following code measures the performance of a block of code:
/// <code>
/// using (_log.Performance("Some code I want measured"))
/// {
/// //code to measure goes here
/// }
/// </code>
/// </example>
/// <example>
/// The following code begins a logical operation and logs from inside and outside the operation:
/// <code>
/// using (_log.PushLogicalOperation("My Operation"))
/// {
/// _log.Information("We're inside the logical operation.");
/// }
///
/// _log.Information("Now we're outside the logical operation.");
/// </code>
/// </example>
/// <example>
/// The following shows some example configuration for the <c><system.diagnostics></c> subsystem, assuming the
/// existence of the class <c>MyClass</c> defined in the example above:
/// <code>
/// <![CDATA[
/// <system.diagnostics>
/// <sources>
/// <source name="MyNamespace.MyClass"
/// switchName="MyNamespace.MyClass"
/// switchType="System.Diagnostics.SourceSwitch">
/// <listeners>
/// <add name="listener"/>
/// </listeners>
/// </source>
/// </sources>
/// <switches>
/// <add name="MyNamespace.MyClass" value="Verbose"/>
/// </switches>
/// <sharedListeners>
/// <add name="listener"
/// type="System.Diagnostics.ConsoleTraceListener"
/// initializeData="false"
/// traceOutputOptions="DateTime,ThreadId,Timestamp,LogicalOperationStack"/>
/// </sharedListeners>
/// <trace autoflush="true">
/// <listeners>
/// <add name="listener"/>
/// </listeners>
/// </trace>
/// </system.diagnostics>
/// ]]>
/// </code>
/// </example>
public sealed class Log
{
private readonly TraceSource _traceSource;
//defines the accepted log levels - used to increase the performance of argument checking
private static readonly LogLevel[] _logLevels = new LogLevel[] { LogLevel.Critical, LogLevel.Error, LogLevel.Information, LogLevel.Verbose, LogLevel.Warning };
/// <summary>
/// Gets the <see cref="TraceSource"/> that this <c>Log</c> wraps.
/// </summary>
/// <remarks>
/// This property facilitates unit testing.
/// </remarks>
internal TraceSource TraceSource
{
get
{
return _traceSource;
}
}
/// <summary>
/// Gets the name of the underlying <see cref="TraceSource"/>.
/// </summary>
/// <remarks>
/// This name is used to configure the <see cref="TraceSource"/> in the <c><system.diagnostics></c> section of the
/// application's configuration file.
/// </remarks>
public string Name
{
get
{
return _traceSource.Name;
}
}
/// <summary>
/// Gets a value indicating whether <see cref="LogLevel.Verbose"/> messages will be logged.
/// </summary>
public bool IsVerboseEnabled
{
get
{
return _traceSource.Switch.ShouldTrace(TraceEventType.Verbose);
}
}
/// <summary>
/// Gets a value indicating whether <see cref="LogLevel.Information"/> messages will be logged.
/// </summary>
public bool IsInformationEnabled
{
get
{
return _traceSource.Switch.ShouldTrace(TraceEventType.Information);
}
}
/// <summary>
/// Gets a value indicating whether <see cref="LogLevel.Warning"/> messages will be logged.
/// </summary>
public bool IsWarningEnabled
{
get
{
return _traceSource.Switch.ShouldTrace(TraceEventType.Warning);
}
}
/// <summary>
/// Gets a value indicating whether <see cref="LogLevel.Error"/> messages will be logged.
/// </summary>
public bool IsErrorEnabled
{
get
{
return _traceSource.Switch.ShouldTrace(TraceEventType.Error);
}
}
/// <summary>
/// Creates an instance of <c>Log</c> for the specified type.
/// </summary>
/// <param name="type">
/// The type for which the log is created.
/// </param>
/// <returns>
/// The <c>Log</c> instance.
/// </returns>
public static Log CreateForType(Type type)
{
ArgumentHelper.AssertNotNull(type, "type");
return new Log(type.FullName);
}
/// <summary>
/// Creates an instance of <c>Log</c> for the namespace of the specified type.
/// </summary>
/// <param name="type">
/// The type whose namespace is used to create the log.
/// </param>
/// <returns>
/// The <c>Log</c> instance.
/// </returns>
public static Log CreateForNamespace(Type type)
{
ArgumentHelper.AssertNotNull(type, "type");
return new Log(type.Namespace);
}
/// <summary>
/// Creates an instance of <c>Log</c> for the assembly of the specified type.
/// </summary>
/// <param name="type">
/// The type whose assembly is used to create the log.
/// </param>
/// <returns>
/// The <c>Log</c> instance.
/// </returns>
public static Log CreateForAssembly(Type type)
{
ArgumentHelper.AssertNotNull(type, "type");
return CreateForAssembly(type.Assembly);
}
/// <summary>
/// Creates an instance of <c>Log</c> for the specified assembly.
/// </summary>
/// <param name="assembly">
/// The assembly for which the log is created.
/// </param>
/// <returns>
/// The <c>Log</c> instance.
/// </returns>
public static Log CreateForAssembly(Assembly assembly)
{
ArgumentHelper.AssertNotNull(assembly, "assembly");
return new Log(assembly.GetName().Name);
}
/// <summary>
/// Creates an instance of <c>Log</c> with an arbitrary name.
/// </summary>
/// <param name="name">
/// The name for the log.
/// </param>
/// <returns>
/// The <c>Log</c> instance.
/// </returns>
public static Log CreateNamed(string name)
{
ArgumentHelper.AssertNotNull(name, "name");
return new Log(name);
}
private Log(string name)
{
_traceSource = new TraceSource(name);
}
/// <summary>
/// Logs a debug message.
/// </summary>
/// <remarks>
/// This method is conditional on the <c>DEBUG</c> compiler symbol. Therefore calls to the method will only be included
/// in debug builds.
/// </remarks>
/// <param name="message">
/// The message to log.
/// </param>
[Conditional("DEBUG")]
[SuppressMessage("Microsoft.Performance", "CA1822", Justification = "The member is supposed to be access via a Log instance, even if that is not strictly required.")]
public void Debug(string message)
{
ArgumentHelper.AssertNotNull(message, "message");
System.Diagnostics.Debug.WriteLine(message);
}
/// <summary>
/// Logs a debug message.
/// </summary>
/// <remarks>
/// This method is conditional on the <c>DEBUG</c> compiler symbol. Therefore calls to the method will only be included
/// in debug builds.
/// </remarks>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
[Conditional("DEBUG")]
[SuppressMessage("Microsoft.Performance", "CA1822", Justification = "The member is supposed to be access via a Log instance, even if that is not strictly required.")]
public void Debug(string format, params object[] args)
{
ArgumentHelper.AssertNotNull(format, "format");
ArgumentHelper.AssertNotNull(args, "args");
System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, format, args));
}
/// <summary>
/// Logs a verbose message.
/// </summary>
/// <param name="message">
/// The message to log.
/// </param>
public void Verbose(string message)
{
Write(LogLevel.Verbose, message);
}
/// <summary>
/// Logs a verbose message.
/// </summary>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
public void Verbose(string format, params object[] args)
{
Write(LogLevel.Verbose, format, args);
}
/// <summary>
/// Logs an informational message.
/// </summary>
/// <param name="message">
/// The message to log.
/// </param>
public void Information(string message)
{
Write(LogLevel.Information, message);
}
/// <summary>
/// Logs an informational message.
/// </summary>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
public void Information(string format, params object[] args)
{
Write(LogLevel.Information, format, args);
}
/// <summary>
/// Logs a warning message.
/// </summary>
/// <param name="message">
/// The message to log.
/// </param>
public void Warning(string message)
{
Write(LogLevel.Warning, message);
}
/// <summary>
/// Logs a warning message.
/// </summary>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
public void Warning(string format, params object[] args)
{
Write(LogLevel.Warning, format, args);
}
/// <summary>
/// Logs an error message.
/// </summary>
/// <param name="message">
/// The message to log.
/// </param>
public void Error(string message)
{
Write(LogLevel.Error, message);
}
/// <summary>
/// Logs an error message.
/// </summary>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
public void Error(string format, params object[] args)
{
Write(LogLevel.Error, format, args);
}
/// <summary>
/// Logs the details of an exception at the <see cref="LogLevel.Error"/> log level.
/// </summary>
/// <param name="ex">
/// The exception whose details are to be logged.
/// </param>
public void Exception(Exception ex)
{
Exception(LogLevel.Error, ex);
}
/// <summary>
/// Logs the details of an exception at a specified <see cref="LogLevel"/>.
/// </summary>
/// <param name="level">
/// The <see cref="LogLevel"/> at which the exception details will be logged.
/// </param>
/// <param name="ex">
/// The exception whose details are to be logged.
/// </param>
public void Exception(LogLevel level, Exception ex)
{
ArgumentHelper.AssertNotNull(ex, "ex");
ArgumentHelper.AssertEnumMember(level);
StringBuilder sb = new StringBuilder();
while (ex != null)
{
sb.Append(ex.GetType().FullName).Append(" : ").AppendLine(ex.Message);
sb.AppendLine(ex.StackTrace);
ex = ex.InnerException;
if (ex != null)
{
sb.AppendLine("--- INNER EXCEPTION ---");
}
}
Write(level, sb.ToString());
}
/// <summary>
/// Obtains an <see cref="IDisposable"/> instance that will log the performance of a block of code when disposed.
/// </summary>
/// <param name="message">
/// The message to log along with the performance information.
/// </param>
/// <returns>
/// An <see cref="IDisposable"/> that will log performance information when disposed.
/// </returns>
public IDisposable Performance(string message)
{
ArgumentHelper.AssertNotNullOrEmpty(message, "message", true);
return new PerformanceLogger(this, message);
}
/// <summary>
/// Obtains an <see cref="IDisposable"/> instance that will log the performance of a block of code when disposed.
/// </summary>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
/// <returns>
/// An <see cref="IDisposable"/> that will log performance information when disposed.
/// </returns>
public IDisposable Performance(string format, params object[] args)
{
ArgumentHelper.AssertNotNull(format, "format");
ArgumentHelper.AssertNotNull(args, "args");
return Performance(string.Format(CultureInfo.InvariantCulture, format, args));
}
/// <summary>
/// Determines whether messages of the specified <see cref="LogLevel"/> will be written.
/// </summary>
/// <param name="level">
/// The log level.
/// </param>
/// <returns>
/// <see langword="true"/> if messages of the specified level will be logged, otherwise <see langword="false"/>.
/// </returns>
public bool ShouldWrite(LogLevel level)
{
ArgumentHelper.AssertEnumMember(level, _logLevels);
return _traceSource.Switch.ShouldTrace((TraceEventType) level);
}
/// <summary>
/// Writes a message at the specified level.
/// </summary>
/// <param name="level">
/// The level at which the message will be logged.
/// </param>
/// <param name="message">
/// The message to log.
/// </param>
public void Write(LogLevel level, string message)
{
ArgumentHelper.AssertEnumMember(level, _logLevels);
ArgumentHelper.AssertNotNull(message, "message");
_traceSource.TraceEvent((TraceEventType) level, 0, message);
}
/// <summary>
/// Writes a formatted message at the specified level.
/// </summary>
/// <param name="level">
/// The level at which the message will be logged.
/// </param>
/// <param name="format">
/// The message format to log.
/// </param>
/// <param name="args">
/// Arguments to be substituted into <paramref name="format"/> prior to logging.
/// </param>
public void Write(LogLevel level, string format, params object[] args)
{
ArgumentHelper.AssertEnumMember(level, _logLevels);
ArgumentHelper.AssertNotNull(format, "format");
ArgumentHelper.AssertNotNull(args, "args");
_traceSource.TraceEvent((TraceEventType) level, 0, format, args);
}
/// <summary>
/// Pushes a named logical operation onto the logical operation stack.
/// </summary>
/// <remarks>
/// This method can be used to add a new logical operation to the logical operation stack. This stack can optionally be
/// included in any log messages of any level (except for debug messages).
/// </remarks>
/// <param name="operationName">
/// The name of the logical operation to begin.
/// </param>
/// <returns>
/// An <see cref="IDisposable"/> implementation that will automatically invoke <see cref="PopLogicalOperation"/> when
/// disposed.
/// </returns>
[SuppressMessage("Microsoft.Performance", "CA1822", Justification = "The member is supposed to be access via a Log instance, even if that is not strictly required.")]
public IDisposable PushLogicalOperation(string operationName)
{
ArgumentHelper.AssertNotNullOrEmpty(operationName, "operationName", true);
System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation(operationName);
return new LogicalOperationClosure();
}
/// <summary>
/// Pushes a named logical operation onto the logical operation stack.
/// </summary>
/// <param name="format">
/// The format of the operation name.
/// </param>
/// <param name="args">
/// Any arguments to substitute into the operation name.
/// </param>
/// <returns>
/// An <see cref="IDisposable"/> implementation that will automatically invoke <see cref="PopLogicalOperation"/> when
/// disposed.
/// </returns>
[SuppressMessage("Microsoft.Performance", "CA1822", Justification = "The member is supposed to be access via a Log instance, even if that is not strictly required.")]
public IDisposable PushLogicalOperation(string format, params object[] args)
{
ArgumentHelper.AssertNotNull(format, "format");
ArgumentHelper.AssertNotNull(args, "args");
return PushLogicalOperation(string.Format(CultureInfo.InvariantCulture, format, args));
}
/// <summary>
/// Pops the last logical operation off the logical operation stack.
/// </summary>
/// <exception cref="InvalidOperationException">
/// If there are no logical operations on the stack to be popped.
/// </exception>
[SuppressMessage("Microsoft.Performance", "CA1822", Justification = "The member is supposed to be access via a Log instance, even if that is not strictly required.")]
public void PopLogicalOperation()
{
System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation();
}
private sealed class LogicalOperationClosure : IDisposable
{
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation();
}
}
}
private sealed class PerformanceLogger : IDisposable
{
private Log _log;
private string _message;
private Stopwatch _stopwatch;
private bool _disposed;
public PerformanceLogger(Log log, string message)
{
System.Diagnostics.Debug.Assert(log != null);
System.Diagnostics.Debug.Assert(message != null);
_log = log;
_message = message;
_stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
if (!_disposed)
{
_stopwatch.Stop();
_disposed = true;
_log.Information("'{0}' took {1} to execute ({2}ms)", _message, _stopwatch.Elapsed, _stopwatch.ElapsedMilliseconds);
}
}
}
}
}
|