Logging in Microsoft Azure custom applications

Logging is highly important if you want to build maintainable solutions. There are frameworks like Log4Net that you can use for it, but I prefer to use the out of the box Azure logging mechanism:

System.Diagnostics.Trace.WriteLine("Writes a verbose message.");
System.Diagnostics.Trace.TraceInformation("Writes an info message.");
System.Diagnostics.Trace.TraceWarning("Writes a warning message.");
System.Diagnostics.Trace.TraceError("Writes an error message.");

Azure Diagnostics logging supports these 4 loglevels. You can configure your application to log only messages which are e.g. at least warnings. This can be done via the Azure portal:

logging

I decided to log to the Blob storage because this logging will remain. The filesystem logging turns itself off after 12 hours.

Activity Id

You can already use these statements for logging. If you have a webapplication or an api app, then you can additionally add the following lines to the Globals.asax:

protected void Application_BeginRequest()
{
    System.Diagnostics.Trace.CorrelationManager.ActivityId = Guid.NewGuid();
}

This generates a new Guid which will be written as ActivityId to the log file. It helps to correlate all log entries that come from a single request.

With the settings above, azure will create CSV files which contains the log information. In my case, the output looks as follows:

logging output

More advanced solution

I like to use interfaces and factories so that I can easily replace my logger by another one – e.g. a file logger or a console logger. It could also be that we change our logging strategy in the future to use a cloud based logging service like loggly. In that case, it’s much easier to replace the logging class and return the new one by the factory (LogManager) than changing all System.Diagnostics.Trace statements by the new one. So let’s build an interface (ILog), implement that interface (AbstractBaseLogger, AzureLogger) and create a very simple factory (LogManager):

ILog.cs:

using System;
 
namespace CodeHollow.Samples.Azure.Logging
{
    /// <summary>
    /// Interface to log messages. Inspired by log4net.
    /// </summary>
    public interface ILog
    {
        /// <summary>
        /// Logs a message object with debug/verbose level.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        void Debug(object message);
 
        /// <summary>
        /// Logs a message object with debug level including the stack trace and the message of the exception.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        /// <param name="exception">The exception to log.</param>
        void Debug(object message, Exception exception);
 
        /// <summary>
        /// Logs a formatted message string with debug/verbose level.
        /// </summary>
        /// <param name="message">A string with format items.</param>
        /// <param name="args">The object to format.</param>
        void DebugFormat(string message, params object[] args);
 
        /// <summary>
        /// Logs a message object with info level.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        void Info(object message);
 
        /// <summary>
        /// Logs a message object with info level including the stack trace and the message of the exception.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        /// <param name="exception">The exception to log.</param>
        void Info(object message, Exception exception);
 
        /// <summary>
        /// Logs a formatted message string with info level.
        /// </summary>
        /// <param name="message">A string with format items.</param>
        /// <param name="args">The object to format.</param>
        void InfoFormat(string message, params object[] args);
 
        /// <summary>
        /// Logs a message object with warning level.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        void Warn(object message);
 
        /// <summary>
        /// Logs a message object with warning level including the stack trace and the message of the exception.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        /// <param name="exception">The exception to log.</param>
        void Warn(object message, Exception exception);
 
        /// <summary>
        /// Logs a formatted message string with warning level.
        /// </summary>
        /// <param name="message">A string with format items.</param>
        /// <param name="args">The object to format.</param>
        void WarnFormat(string message, params object[] args);
 
        /// <summary>
        /// Logs a message object with error level.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        void Error(object message);
 
        /// <summary>
        /// Logs a message object with error level including the stack trace and the message of the exception.
        /// </summary>
        /// <param name="message">The message object to log.</param>
        /// <param name="exception">The exception to log.</param>
        void Error(object message, Exception exception);
 
        /// <summary>
        /// Logs a formatted message string with error level.
        /// </summary>
        /// <param name="message">A string with format items.</param>
        /// <param name="args">The object to format.</param>
        void ErrorFormat(string message, params object[] args);
    }
}

AbstractBaseLogger.cs:

using System;
 
namespace CodeHollow.Samples.Azure.Logging
{
    public abstract class AbstractBaseLogger : ILog
    {
        public abstract void Debug(object message);
        public abstract void Info(object message);
        public abstract void Warn(object message);
        public abstract void Error(object message);
 
        public void Debug(object message, Exception exception)
        {
            Debug(GetMessage(message, exception));
        }
 
        public void DebugFormat(string message, params object[] args)
        {
            Debug(string.Format(message, args));
        }
 
        public void Info(object message, Exception exception)
        {
            Info(GetMessage(message, exception));
        }
 
        public void InfoFormat(string message, params object[] args)
        {
            Info(string.Format(message, args));
        }
 
        public void Warn(object message, Exception exception)
        {
            Warn(GetMessage(message, exception));
        }
 
        public void WarnFormat(string message, params object[] args)
        {
            Warn(string.Format(message, args));
        }
 
        public void Error(object message, Exception exception)
        {
            Error(GetMessage(message, exception));
        }
 
        public void ErrorFormat(string message, params object[] args)
        {
            Error(string.Format(message, args));
        }
 
        private string GetMessage(object message, Exception exception)
        {
            return string.Format("{0}{1}{2}", message, Environment.NewLine, exception);
        }
    }
}

AzureLogger.cs

using System;
using System.Diagnostics;
 
namespace CodeHollow.Samples.Azure.Logging
{
    public class AzureLogger : AbstractBaseLogger
    {
        public override void Debug(object message)
        {
            Trace.WriteLine(message);
        }
 
        public override void Error(object message)
        {
            Trace.TraceError(Get(message));
        }
 
        public override void Info(object message)
        {
            Trace.TraceInformation(Get(message));
        }
 
        public override void Warn(object message)
        {
            Trace.TraceWarning(Get(message));
        }
 
        private string Get(object message)
        {
            return message == null ? String.Empty : message.ToString();
        }
    }
}

LogManager.cs

namespace CodeHollow.Samples.Azure.Logging
{
    /// <summary>
    /// Use this class to get the logger.
    /// </summary>
    public static class LogManager
    {
        private static ILog logger = new AzureLogger();
        /// <summary>
        /// Gets logger object
        /// </summary>
        /// <returns>Instance of <see cref="ILog" />.</returns>
        public static ILog GetLogger()
        {
            return logger;
        }
    }
}

Usage of the code above is very simple:

ILog logger = LogManager.GetLogger();
 
logger.Debug("This is my debug message");
try
{
    logger.InfoFormat("This is my {0} message!", "Info");
    MethodThatThrowsAnException();
}
catch(Exception ex)
{
    logger.Warn("Exception occurred :)", ex);
    logger.Error("Error occurred ...", ex);
}

Additional information

https://blogs.msdn.microsoft.com/microsoft_press/2014/01/29/from-the-mvps-diagnostics-and-logging-in-windows-azure-web-sites/
https://azure.microsoft.com/en-us/documentation/articles/web-sites-enable-diagnostic-log/
http://blog.amitapple.com/post/2014/06/azure-website-logging/#.VvFTXOZGT1I

Categories:

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *

About
about armin

Armin Reiter
Blockchain/Web3, IT-Security & Azure
Vienna, Austria

Reiter ITS Logo

Cryptix Logo

Legal information