#region License
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Reflection;
using System.Text;
using AopAlliance.Intercept;
using Common.Logging;
namespace Spring.Aspects.Logging{
/// <summary>
/// Configurable advice for logging.
/// </summary>
/// <remarks>
///
/// </remarks>
/// <author>Mark Pollack</author>
[Serializable]
public class SimpleLoggingAdvice : AbstractLoggingAdvice
{
#region Fields
/// <summary>
/// Flag to indicate if unique identifier should be in the log message.
/// </summary>
private bool logUniqueIdentifier;
/// <summary>
/// Flag to indicate if the execution time should be in the log message.
/// </summary>
private bool logExecutionTime;
/// <summary>
/// Flag to indicate if the method arguments should be in the log message.
/// </summary>
private bool logMethodArguments;
/// <summary>
/// Flag to indicate if the return value should be in the log message.
/// </summary>
private bool logReturnValue;
/// <summary>
/// The separator string to use for delmiting log message fields.
/// </summary>
private string separator = ", ";
/// <summary>
/// The log level to use for logging the entry, exit, exception messages.
/// </summary>
private LogLevel logLevel = LogLevel.Trace;
#endregion
#region Constructor(s)
/// <summary>
/// Initializes a new instance of the <see cref="SimpleLoggingAdvice"/> class.
/// </summary>
public SimpleLoggingAdvice()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleLoggingAdvice"/> class.
/// </summary>
/// <param name="useDynamicLogger">if set to <c>true</c> to use dynamic logger, if
/// <c>false</c> use static logger.</param>
public SimpleLoggingAdvice(bool useDynamicLogger)
{
UseDynamicLogger = useDynamicLogger;
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleLoggingAdvice"/> class.
/// </summary>
/// <param name="defaultLogger">the default logger to use</param>
public SimpleLoggingAdvice(ILog defaultLogger)
: base(defaultLogger)
{}
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether to log a unique identifier with the log message.
/// </summary>
/// <value><c>true</c> if [log unique identifier]; otherwise, <c>false</c>.</value>
public bool LogUniqueIdentifier
{
get { return logUniqueIdentifier; }
set { logUniqueIdentifier = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to log execution time.
/// </summary>
/// <value><c>true</c> if log execution time; otherwise, <c>false</c>.</value>
public bool LogExecutionTime
{
get { return logExecutionTime; }
set { logExecutionTime = value; }
}
/// <summary>
/// Gets or sets a value indicating whether log method arguments.
/// </summary>
/// <value><c>true</c> if log method arguments]; otherwise, <c>false</c>.</value>
public bool LogMethodArguments
{
get { return logMethodArguments; }
set { logMethodArguments = value; }
}
/// <summary>
/// Gets or sets a value indicating whether log return value.
/// </summary>
/// <value><c>true</c> if log return value; otherwise, <c>false</c>.</value>
public bool LogReturnValue
{
get { return logReturnValue; }
set { logReturnValue = value; }
}
/// <summary>
/// Gets or sets the seperator string to use for delmiting log message fields.
/// </summary>
/// <value>The seperator.</value>
public string Separator
{
get { return separator; }
set { separator = value; }
}
/// <summary>
/// Gets or sets the entry log level.
/// </summary>
/// <value>The entry log level.</value>
public LogLevel LogLevel
{
get { return logLevel; }
set { logLevel = value; }
}
#endregion
#region Protected Methods
/// <summary>
/// Subclasses must override this method to perform any tracing around the supplied
/// IMethodInvocation.
/// </summary>
/// <param name="invocation">The method invocation to log</param>
/// <param name="log">The log to write messages to</param>
/// <returns>
/// The result of the call to IMethodInvocation.Proceed()
/// </returns>
/// <remarks>
/// Subclasses are resonsible for ensuring that the IMethodInvocation actually executes
/// by calling IMethodInvocation.Proceed().
/// <para>
/// By default, the passed-in ILog instance will have log level
/// "trace" enabled. Subclasses do not have to check for this again, unless
/// they overwrite the IsInterceptorEnabled method to modify
/// the default behavior.
/// </para>
/// </remarks>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
protected override object InvokeUnderLog(IMethodInvocation invocation, ILog log)
{
object returnValue = null;
bool exitThroughException = false;
DateTime startTime = DateTime.Now;
string uniqueIdentifier = null;
if (LogUniqueIdentifier)
{
uniqueIdentifier = CreateUniqueIdentifier();
}
try
{
WriteToLog(LogLevel, log, GetEntryMessage(invocation, uniqueIdentifier), null);
returnValue = invocation.Proceed();
return returnValue;
} catch (Exception e)
{
TimeSpan executionTimeSpan = DateTime.Now - startTime;
WriteToLog(LogLevel, log, GetExceptionMessage(invocation, e, executionTimeSpan, uniqueIdentifier), e);
exitThroughException = true;
throw;
}
finally
{
if (!exitThroughException)
{
TimeSpan executionTimeSpan = DateTime.Now - startTime;
WriteToLog(LogLevel, log, GetExitMessage(invocation, returnValue, executionTimeSpan, uniqueIdentifier), null);
}
}
}
/// <summary>
/// Determines whether the given log is enabled.
/// </summary>
/// <param name="log">The log instance to check.</param>
/// <returns>
/// <c>true</c> if log is for a given log level; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// Default is true when the trace level is enabled. Subclasses may override this
/// to change the level at which logging occurs, or return true to ignore level
/// checks.</remarks>
protected override bool IsLogEnabled(ILog log)
{
switch (LogLevel)
{
case LogLevel.All:
case LogLevel.Trace:
if (log.IsTraceEnabled)
{
return true;
}
break;
case LogLevel.Debug:
if (log.IsDebugEnabled)
{
return true;
}
break;
case LogLevel.Error:
if (log.IsErrorEnabled)
{
return true;
}
break;
case LogLevel.Fatal:
if (log.IsFatalEnabled)
{
return true;
}
break;
case LogLevel.Info:
if (log.IsInfoEnabled)
{
return true;
}
break;
case LogLevel.Warn:
if (log.IsWarnEnabled)
{
return true;
}
break;
case LogLevel.Off:
default:
break;
}
return false;
}
/// <summary>
/// Creates a unique identifier.
/// </summary>
/// <remarks>
/// Default implementation uses Guid.NewGuid(). Subclasses may override to provide an alternative
/// ID generation implementation.
/// </remarks>
/// <returns>A unique identifier</returns>
protected virtual string CreateUniqueIdentifier()
{
return Guid.NewGuid().ToString();
}
/// <summary>
/// Gets the entry message to log
/// </summary>
/// <param name="invocation">The invocation.</param>
/// <param name="idString">The id string.</param>
/// <returns>The entry log message</returns>
protected virtual string GetEntryMessage(IMethodInvocation invocation, string idString)
{
StringBuilder sb = new StringBuilder(128);
sb.Append("Entering ");
AppendCommonInformation(sb, invocation, idString);
if (logMethodArguments)
{
sb.Append(GetMethodArgumentAsString(invocation));
}
return RemoveLastSeparator(sb.ToString(), Separator);
}
/// <summary>
/// Gets the exception message.
/// </summary>
/// <param name="invocation">The method invocation.</param>
/// <param name="e">The thown exception.</param>
/// <param name="executionTimeSpan">The execution time span.</param>
/// <param name="idString">The id string.</param>
/// <returns>The exception log message.</returns>
protected virtual string GetExceptionMessage(IMethodInvocation invocation, Exception e, TimeSpan executionTimeSpan, string idString)
{
StringBuilder sb = new StringBuilder(128);
sb.Append("Exception thrown in ");
sb.Append(invocation.Method.Name).Append(Separator);
AppendCommonInformation(sb, invocation, idString);
if (LogExecutionTime)
{
sb.Append(executionTimeSpan.TotalMilliseconds).Append(" ms");
}
return RemoveLastSeparator(sb.ToString(), Separator);
}
/// <summary>
/// Gets the exit log message.
/// </summary>
/// <param name="invocation">The method invocation.</param>
/// <param name="returnValue">The return value.</param>
/// <param name="executionTimeSpan">The execution time span.</param>
/// <param name="idString">The id string.</param>
/// <returns>the exit log message</returns>
protected virtual string GetExitMessage(IMethodInvocation invocation, object returnValue, TimeSpan executionTimeSpan, string idString)
{
StringBuilder sb = new StringBuilder(128);
sb.Append("Exiting ");
AppendCommonInformation(sb, invocation, idString);
if (LogReturnValue && invocation.Method.ReturnType != typeof(void))
{
sb.Append("return=").Append(returnValue).Append(Separator);
}
if (LogExecutionTime)
{
sb.Append(executionTimeSpan.TotalMilliseconds).Append(" ms");
}
return RemoveLastSeparator(sb.ToString(), Separator);
}
/// <summary>
/// Appends common information across entry,exit, exception logging
/// </summary>
/// <remarks>Add method name and unique identifier if required.</remarks>
/// <param name="sb">The string buffer building logging message.</param>
/// <param name="invocation">The method invocation.</param>
/// <param name="idString">The unique identifier string.</param>
protected virtual void AppendCommonInformation(StringBuilder sb, IMethodInvocation invocation, string idString)
{
sb.Append(invocation.Method.Name);
if (LogUniqueIdentifier)
{
sb.Append(Separator).Append(idString);
}
sb.Append(Separator);
}
/// <summary>
/// Gets the method argument as argumen name/value pairs.
/// </summary>
/// <param name="invocation">The method invocation.</param>
/// <returns>string for logging method argument name and values.</returns>
protected virtual string GetMethodArgumentAsString(IMethodInvocation invocation)
{
StringBuilder sb = new StringBuilder(128);
ParameterInfo[] parameterInfos = invocation.Method.GetParameters();
object[] argValues = invocation.Arguments;
for (int i=0; i< parameterInfos.Length; i++)
{
sb.Append(parameterInfos[i].Name).Append("=").Append(argValues[i]);
if (i != parameterInfos.Length) sb.Append("; ");
}
return RemoveLastSeparator(sb.ToString(), "; ");
}
#endregion
#region Private Methods
private string RemoveLastSeparator(string str, string separator)
{
if (str.EndsWith(separator))
{
return str.Substring(0, str.Length - separator.Length);
}
else
{
return str;
}
}
private void WriteToLog(LogLevel logLevel, ILog log, string text, Exception e)
{
switch (logLevel)
{
case LogLevel.All:
case LogLevel.Trace:
if (log.IsTraceEnabled)
{
if (e == null) log.Trace(text); else log.Trace(text, e);
}
break;
case LogLevel.Debug:
if (log.IsDebugEnabled)
{
if (e == null) log.Debug(text); else log.Debug(text, e);
}
break;
case LogLevel.Error:
if (log.IsErrorEnabled)
{
if (e == null) log.Error(text); else log.Error(text, e);
}
break;
case LogLevel.Fatal:
if (log.IsFatalEnabled)
{
if (e == null) log.Fatal(text); else log.Fatal(text, e);
}
break;
case LogLevel.Info:
if (log.IsInfoEnabled)
{
if (e == null) log.Info(text); else log.Info(text, e);
}
break;
case LogLevel.Warn:
if (log.IsWarnEnabled)
{
if (e == null) log.Warn(text); else log.Warn(text, e);
}
break;
case LogLevel.Off:
default:
break;
}
}
#endregion
}
}
|