For those who prefer Log4Net…

This is just a quick post that augments my previous one on non-intrusive logging. Many of you out there have chosen the other logging framework due to prior experience with Log4J or similar (I am sure that there are some out there with other logging experience with other frameworks, however the process and goal remains the same).

The waving is achieved by defining an attribute based on the PostSharp OnMethodBoundaryAspect class. This class along with the PostSharp post-compiler that is installed into the Visual Studio compile chain which builds and injects the AOP code directly into the normal code flow. For usage examples, other than those given here, please refer to my previous blog.

Here is the code for the attribute:

    /// <summary>
    /// The trace attribute. 
    /// </summary>
    [Serializable]
    public sealed class LoggingAttribute : OnMethodBoundaryAspect
    {
        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// The category.
        /// </summary>
        private readonly string category;

        /// <summary>
        /// The is log 4 net active.
        /// </summary>
        private static bool isLog4NetActive;

        /// <summary>
        /// The message written to log when entering a method.
        /// This field is initialized at runtime. It does not need to be serialized.
        /// </summary>
        [NonSerialized]
        private string enteringMessage;

        /// <summary>
        /// The message written to log when an exception is thrown in a method.
        /// This field is initialized at runtime. It does not need to be serialized.
        /// </summary>
        [NonSerialized]
        private string exceptionMessage;

        /// <summary>
        /// The message written to log when exiting a method.
        /// This field is initialized at runtime. It does not need to be serialized.
        /// </summary>
        [NonSerialized]
        private string exitingMessage;

        /// <summary>
        /// The message written to log when a method is successful.
        /// This field is initialized at runtime. It does not need to be serialized.
        /// </summary>
        [NonSerialized]
        private string successMessage;

        /// <summary>
        /// Initializes a new instance of the <see cref="LoggingAttribute"/> class.
        /// </summary>
        public LoggingAttribute()
        {
            this.Priority = 5;
            this.EventId = 1000;
            this.Message = "Test Default Logging Message";
            this.Severity = TraceEventType.Information;
            this.category = "Some category";
            ConfigureLog4Net();
        }

        /// <summary>
        /// Gets or sets the priority.
        /// This field is initialized and serialized at build time, then deserialized at runtime.
        /// </summary>
        /// <value>The priority.</value>
        public int Priority { get; set; }

        /// <summary>
        /// Gets or sets the severity.
        /// This field is initialized and serialized at build time, then deserialized at runtime.
        /// </summary>
        /// <value>The severity.</value>
        public TraceEventType Severity { get; set; }

        /// <summary>
        /// Gets or sets the message.
        /// This field is initialized and serialized at build time, then deserialized at runtime.
        /// </summary>
        /// <value>The message.</value>
        public string Message { get; set; }

        /// <summary>
        /// Gets or sets the event id.
        /// This field is initialized and serialized at build time, then deserialized at runtime.
        /// </summary>
        /// <value>The event id.</value>
        public int EventId { get; set; }

        /// <summary>
        /// Gets the category.
        /// </summary>
        /// <value>The category.</value>
        public string Category
        {
            get { return this.category; }
        }

        /// <summary>
        ///  Gets the config file.
        /// </summary>
        /// <value>The config file.</value>
        private static string ConfigFile
        {
            get
            {
                // First, get the path for this executing assembly. 
                var a = Assembly.GetExecutingAssembly();
                var path = Path.GetDirectoryName(a.Location);
                var relativeReference = a.GetName().Name;

                // if the file exists in this Path - prepend the path 
                if (path != null)
                {
                    var fullReference = Path.Combine(path, relativeReference);
                    if (File.Exists(fullReference))
                    {
                        return fullReference;
                    }

                    // Strip off any trailing ".dll" if present. 
                    fullReference = string.Compare(relativeReference.Substring(relativeReference.Length - 4), Resources.Dll, true) == 0 ? relativeReference.Substring(0, relativeReference.Length - 4) : relativeReference;

                    // See if the required assembly is already present in our current AppDomain 
                    foreach (var currAssembly in AppDomain.CurrentDomain.GetAssemblies())
                    {
                        if (string.Compare(currAssembly.GetName().Name, fullReference, true) == 0)
                        {
                            // Found it, return the location as the full reference. 
                            return currAssembly.Location;
                        }
                    }
                }

                return string.Empty;
            }
        }

        /// <summary>
        /// Gets the log location.
        /// </summary>
        /// <value>The log location.</value>
        private static string LogLocation
        {
            get
            {
                string val;
                try
                {
                    var configName = ConfigFile;

                    if (!string.IsNullOrEmpty(configName))
                    {
                        var conf = ConfigurationManager.OpenExeConfiguration(configName);
                        val = conf.AppSettings.Settings[Resources.LogLocation].Value;
                        if ((val == null) || val.Equals(string.Empty))
                        {
                            val = Resources.DefaultLogLocation;
                        }

                        return val;
                    }

                    val = Resources.DefaultLogLocation;
                    return val;
                }
                catch (Exception)
                {
                    val = Resources.DefaultLogLocation;
                    return val;
                }
            }
        }

        /// <summary>
        /// Initializes the current aspect.
        /// Invoked only once at runtime from the static constructor of type declaring the target method.
        /// </summary>
        /// <param name="method">
        /// Method to which the current aspect is applied.
        /// </param>
        public override void RuntimeInitialize(MethodBase method)
        {
            this.enteringMessage = Resources.OnEntry;
            this.exitingMessage = Resources.OnExit;
            this.exceptionMessage = Resources.At;
            this.successMessage = Resources.Success;
        }

        /// <summary>
        /// Method executed <b>before</b> the body of methods to which this aspect is applied.
        /// </summary>
        /// <param name="args">
        /// Event arguments specifying which method
        /// is being executed, which are its arguments, and how should the execution continue
        /// after the execution of <see cref="M:PostSharp.Laos.IOnMethodBoundaryAspect.OnEntry(PostSharp.Laos.MethodExecutionEventArgs)"/>.
        /// </param>
        /// <remarks>
        /// If the aspect is applied to a constructor, the current method is invoked
        /// after the <b>this</b> pointer has been initialized, that is, after
        /// the base constructor has been called.
        /// </remarks>
        public override void OnEntry(MethodExecutionArgs args)
        {
            this.PerpareMessage(this.enteringMessage, args);
        }

        /// <summary>
        /// Method executed when an exception occurs in the methods to which the current
        /// custom attribute has been applied.
        /// </summary>
        /// <param name="args">
        /// Event arguments specifying which method
        /// is being called and with which parameters.
        /// </param>
        public override void OnException(MethodExecutionArgs args)
        {
            this.PerpareMessage(this.exceptionMessage, args);
        }

        /// <summary>
        /// Method executed <b>after</b> the body of methods to which this aspect is applied,
        /// even when the method exists with an exception (this method is invoked from
        /// the <b>finally</b> block).
        /// </summary>
        /// <param name="args">
        /// Event arguments specifying which method
        /// is being executed and which are its arguments.
        /// </param>
        public override void OnExit(MethodExecutionArgs args)
        {
            this.PerpareMessage(this.exitingMessage, args);
        }

        /// <summary>
        /// Method executed <b>after</b> the body of methods to which this aspect is applied,
        /// but only when the method succesfully returns (i.e. when no exception flies out
        /// the method.).
        /// </summary>
        /// <param name="args">
        /// Event arguments specifying which method
        /// is being executed and which are its arguments.
        /// </param>
        public override void OnSuccess(MethodExecutionArgs args)
        {
            this.PerpareMessage(this.successMessage, args);
        }

        /// <summary>
        /// Configures the log4 net.
        /// </summary>
        private static void ConfigureLog4Net()
        {
            var pl = new PatternLayout(Resources.PatternLayout);
            var fa = new FileAppender { File = LogLocation, Layout = pl };

            pl.ActivateOptions();
            fa.ActivateOptions();

            BasicConfigurator.Configure(fa);
        }

        /// <summary>
        /// Perpares the message.
        /// </summary>
        /// <param name="resourceString">The resource string.</param>
        /// <param name="args">The method args.</param>
        private void PerpareMessage(string resourceString, MethodExecutionArgs args)
        {
            if (args.Method == null)
            {
                return;
            }

            if (!isLog4NetActive)
            {
                ConfigureLog4Net();
                isLog4NetActive = true;
            }

            Logger.InfoFormat(
                string.Format(
                    resourceString,
                    args.Method.DeclaringType.Name,
                    args.Method.Name),
                this.category);
        }
    }

Once again the test program is very simple (in fact it is exactly the same):

/// <summary>
/// The test program.
/// </summary>
internal static class Program
{
    /// <summary>
    /// Mains this instance.
    /// </summary>
    private static void Main()
    {
        try
        {
            SayHello();
            SayGoodBye();
            Console.WriteLine(Strings.Program_Main_Success);
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            var str = ex.Message;
            Console.WriteLine(str);
            Console.ReadLine();
        }
    }
    /// <summary>
    /// Says the hello.
    /// </summary>
    [Logging(Priority = 2, Severity = TraceEventType.Information, Message = “Some message for SayHello”, EventId = 3)]
    private static void SayHello()
    {
        Console.WriteLine(Resources.World);
    }
    /// <summary>
    /// Says the good bye. for SayGoodbye
    /// </summary>
    [Logging(Priority = 2, Severity = TraceEventType.Information, Message = “Some message for SayGoodbye”, EventId = 3)]
    private static void SayGoodBye()
    {
        Console.WriteLine(Resources.ByeWorld);
    }
}
 
As the output takes up space I have omitted it this time, however the configuration is nothing more than what is currently used within your project.

What can be taken away is:

  1. The solution is simple and very flexible.
  2. Logging has only been defined in one place and, hence, is very maintainable.
  3. Simple to add the logging facility of choice.

Once again PostSharp can be found here, and V5 of the Enterprise Library here.

That’s all folks…

Advertisements

~ by Intelligence4 on January 28, 2011.

 
%d bloggers like this: