Click here to Skip to main content
15,888,454 members
Articles / Programming Languages / C#

Logging Method Entry and Exit Points Dynamically

Rate me:
Please Sign up or sign in to vote.
4.25/5 (3 votes)
16 Jan 2015CPOL10 min read 47.2K   762   20   7
Logging method entry and exit points

Introduction

I am writing and supporting software that is used worldwide. Often the machines run disconnected from the internet. So when something goes wrong, the only way to analyze the problem is by analyzing the log files. We use the log4net library for logging.

Problems are often a consequence of what happened before. Knowing the execution history helps to find out what was the root cause of the problem. So in order to pinpoint what went wrong, we often log the entry and exit points of methods. We log the method name and, if needed, the parameters values and the return value. Before .NET 4.5, logging the method name was hardcoded (unless you were ‘crawling’ the call stack). If you hardcode method name in the logging call, you have a problem when the method name changes: you will have to change log entry and log exit call as well. Since .NET 4.5, we can log the method name dynamically without investigating the stack explicitly. The intention is not to go as far as the Aspect Oriented approach.

In this article, we present some extension methods for the ILog interface of the log4net library to log entry and exit points in a dynamic way. We designed it in such a way that you can customize the way logging takes place. Tests are included and can be used as documentation on how to use the small library.

The rest of the article explains the interface and its implementation.

Implementation

We implemented the extension points in the (static) class LoggerExtension in the namespace log4net.Extension. We reference log4net assembly as we rely on its basic services. The sources for the class explained in this article are included. The solution also contains a second project in which unit tests for LoggerExtension are implemented. We use NUnit to implement the tests.

Here is the structure of the rest of the article. We will start with the simple method LogBlock and explain what is happening. The LogBlock method logs entry and exit points of a method in one call. The LogBlock method has several overloads that will be discussed.

If you want log for entry and exit points differently (for example different logger level), you can use DebugEntry and DebugExit or LogEntry and LogExit. DebugEntry and DebugExit methods log at debug level. These methods are explained after the LogBlock method. Next, we explain the LogEntry and LogExit methods that allow you the log at any logger level and the method parameters as well if needed. Finally, I will explain how to customize the library.

LogBlock

Before .NET 4.5, we logged the entry point and exit point of method MyMethod in the following way (the Logger property returns an initialized log4net logger):

C#
void MyMethod()
{
    Logger.Debug(">MyMethod");
    ...
    Logger.Debug("<MyMethod");
}

The method entry is prefixed with ‘>’, the exit with ‘<’. Changing the method name meant changing three lines of code. From .NET 4.5 on by using our extension methods, we can log the entry point and exit point of method MyMethod in the following way:

C#
void MyFunction()
{
    using(Logger.LogBlock())
    {
    ...
    }
}

As you can see, the string parameters are gone. When the name of the method is changed, the log entry will changed automatically. Here is how the LogBlock extension method is defined in order to achieve this:

C#
static public ExitLogger LogBlock(this ILog logger, [CallerMemberName]string name = null)

The method is defined as static methods with the first parameter the this parameter as the methods are extension methods. The type extended is the ILog interface of the log4net library. The next optional parameter is the name with default value null. This parameter is annotated with the CallerMemberName attribute. As a result of this annotation and if no value is passed for this parameter by the caller, this parameter will be assigned the name of the caller method.

As you can see, the method returns an object of type ExitLogger. This object executes the actual logging of the exit point at the moment it goes out of scope. By using the using statement, we guarantee that the exit point is logged even when an exception would be thrown in the block of code executed.

When you call LogBlock with a parameter value, the value of that parameter will be logged (instead of the method name of the caller) prefixed with the prefix defined (see later). When you call LogBlock with null as parameter value, only the prefix will be logged. So, the following code:

C#
void MyFunction()
{
    using(Logger.LogBlock("test"))
    {
    ...
    }
}

Results in the strings ">test" and "<test" to be logged at debug level.

The above definition logs at the debug level. In the following overload of the method with the extra Level parameter you can log at any level. The Level type is defined in the log4net library:

C#
static public ExitLogger LogBlock(this ILog logger, Level level, [CallerMemberName]string name = null)

As explained before, we like to log method parameters. This can be done using the following overloads of LogBlock:

C#
static public ExitLogger LogBlock(this ILog logger, Level level, object[] entryParameters,
              object[] exitParameters, [CallerMemberName]string name = null)

static public ExitLogger LogBlock(this ILog logger, Level level, object[] entryParameters,
              [CallerMemberName]string name = null)

These incarnations of the LogBlock method have extra parameters compared to the definitions above: entryParameters and exitParameters, both an array of objects. The array of objects can hold extra info to be logged. entryParameters holds info to be logged at method entry (typical the call parameters) while exitParameters holds info to be logged at method exit (typical the returned object). The array of objects is (by default) serialized to a string and separated by a space. This code logs at method entry ">MyFunction test" at info level and "<MyFunction" at method exit time:

C#
void MyFunction()
{
    object[] parameters = { "test" };
    using(Logger.LogBlock(Level.Info, parameters))
    {
    ...
    }
}

The entryParameters and exitParameters parameter are ignored when null value is passed to the LogBlock call.

If you want to have tighter control on what is logged at method entry or method exit, it is possible. This is what will be discussed in the rest of this article starting with the DebugEntry and DebugExit methods.

DebugEntry & DebugExit

Debug logging the method name at method entry can be done using DebugEntry. Logging the method name at method exit with DebugExit. Here is how the DebugEntry and DebugExit extension methods are defined in order to achieve this:

C#
static public void DebugEntry(this ILog logger, [CallerMemberName]string name = null)

static public ExitLogger DebugExit(this ILog logger, [CallerMemberName]string name = null)

The parameters are used in the same way as in the LogBlock discussed above. The code below has the same effect as using the LogBlock call without parameters:

C#
void MyFunction()
{
    Logger.DebugEntry();
    using(Logger.DebugExit())
    {
    ...
    }
}

LogEntry & LogExit

In order to log method entry at any other logger level than debug and to support logging of method parameters, we defined the LogEntry extension method:

C#
static public void LogEntry(this ILog logger,
    Level level,
    [CallerMemberName]string methodName = null)

static public void LogEntry(this ILog logger,
    Level level,
    object[] parameters,
    [CallerMemberName]string methodName = null)

The first incarnation of the LogEntry method has an extra parameter compared to the DebugEntry method: the logger level. The Level type is defined in the log4net library. This code logs ‘>MyFunction’ at info level (if the property EntryPrefix has not been changed, see later):

C#
void MyMethod()
{
    Logger.LogEntry(Level.Info);
    …
}

The second incarnation of the LogEntry method has two extra parameters compared to the DebugEntry method: the logger level and an array of objects. The array of objects can hold extra info to be logged, typically the call parameters. The array of objects is (by default) serialized to a string and separated by a space. This code logs ‘>MyMethod test’ at info level:

C#
void MyMethod    ()
{
    object[] parameters = { "test"};
    Logger.LogEntry(Level.Info, parameters);
    ...
}

The parameters parameter is ignored when null value is passed to the LogEntry call. The methodName parameter can be passed as in the DebugEntry method and has the same effect (see above).

For logging the exit of a method, we defined the LogExit methods that is used in the same way as the LogBlock methods explained before:

C#
static public ExitLogger LogExit(this ILog logger,
    Level level,
    [CallerMemberName]string methodName = null)

static public ExitLogger LogExit (this ILog logger,
    Level level,
    object[] parameters,
    [CallerMemberName]string methodName = null)

The only difference between LogExit and LogEntry methods is the prefix: for LogExit the prefix is "<", for LogEntry is ">" and LogExit returns an ExitLogger for the reason explained above.

Customizing the Library

The library has three customization points:

  • The prefix for entry and exit functions (resp. LogBlock, DebugEntry, LogEntry and LogBlock, DebugExit, LogExit) can be set.
  • The way parameters are serialized can be customized.
  • The way the method name inclusive the prefix is serialized can be customized.

In this section, the three customization points are explained one by one.

EntryPrefix & ExitPrefix

As explained before, the default prefix for the entry-calls (LogBlock, DebugEntry, LogEntry) is ">", for exit-calls (LogBlock, DebugExit, LogExit) it is "<". You can replace this defaults by any other string by setting the EntryPrefix and ExitPrefix properties of the LoggerExtension class. These properties are defined as follows:

C#
public static string EntryPrefix { get; set; }
public static string ExitPrefix { get; set; }

So the following example will add log entry "in MyFunction" at method entry and "out MyFunction" at method exit (both debug logger level):

C#
void MyFunction()
{
    LoggerExtension.EntryPrefix = "in ";
    LoggerExtension.ExitPrefix = "out ";
    using(Logger.LogBlock())
    {
    ...
    }
}

Be careful: the properties are static properties. So once set, they are applicable for all calls to the extension points until set to an other value. This is by design: you probably want a consistent way to indicate method entry/exit.

Customizing Parameter Logging

The way parameters are logged can be customized. The parameters are serialized by executing the action defined in the MsgParametersAction property of the LoggerExtension class. You can supply your own implementation by setting the MsgParametersAction property in this class and referring to your own code to be executed. The MsgParametersAction property is defined as follows:

C#
public static Action<StringBuilder, Severity, object[]> MsgParametersAction { get; set; }

The default implementation of this MsgParametersAction is to serialize the parameters is iterating over the object array (2nd parameter) and one by one appending a space, followed by appending the string representation of the object to the StringBuilder instance passed (1st parameter).

You can customize this functionality by supplying your own Action object. Again this property is defined statically so the action replacement will be in effect for all calls to the library. Here is an example, it will always log "test" instead of any values passed as parameter:

C#
const string firstParam = "test";
object[] parameters = { "xx" };

LoggerExtension.MsgParametersAction =
    delegate(StringBuilder builder, Level level, object[] prms)
        {
            builder.Append(" ");
            builder.Append(firstParam);
        };

Logger.LogEntry(Level.Info, parameters);

Customizing Method Name Logging

The way the method name (inclusive prefix) is logged can be customized. The method name is serialized by executing the action defined in the MsgPrefixAction property of the LoggerExtension class. Your can supply your own implementation by setting the MsgPrefixAction property in this class and referring to your own code to be executed. The MsgPrefixAction property is defined as follows:

C#
public static Action<StringBuilder, Severity, string, string> MsgPrefixAction { get; set; }

The default implementation of this MsgPrefixAction is to append the prefix (3rd parameter) followed by a appending the method name (4th parameter) to the StringBuilder instance passed (1st parameter).

You can customize this Action by supplying your own action. Again this property is defined statically so the action replacement will be in effect for all calls to the library. Here is an example, it will log the hardcoded "test" string instead of the prefix followed by the method name:

C#
LoggerExtension.MsgPrefixAction =
    delegate(StringBuilder bldr, Level lvl , string prefix, string name)
        {
            builder.Append("test");
        };

Logger.LogEntry(Level.Info, parameters);

Conclusion

This article discussed an implementation of extension point log log4net to log method entry and method exit point in a dynamic way using the .NET 4.5 CallerMemberName attribute. It was explained how to customize the library to your own needs. The projects include the latest released version of the log4net. It includes unit tests that can serve as working example. You can define your own interface and extension points and make use of the basics in this library. Applying the same functionality to other logging frameworks should be easy. After enjoying so much open source code, I hope other people find this small library useful.

Using the Code

This code will compile for .NET 4.5 (or higher) only. The code can be used by either referencing the log4net.Extension assembly produced by the project included. You will need to use the same log4net version as included in this package. You can you use your own version of log4net as well. In this case, you will need to recompile the assembly. The second option is to including the LoggerExtension and ExitLogger class in your project. A reference to the log4net library will be needed as well.

History

  • 2nd January, 2015: Initial version
  • 3rd January, 2015: Corrected typos
  • 11th January, 2015: Added LogBlock, simplified the tests

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiondemo project... Pin
zebula825-Sep-20 14:31
zebula825-Sep-20 14:31 
GeneralMy vote of 2 Pin
fraser addison5-Jan-15 8:30
fraser addison5-Jan-15 8:30 
QuestionCheck out TracerX Pin
MarkLTX2-Jan-15 6:16
MarkLTX2-Jan-15 6:16 
AnswerRe: Check out TracerX Pin
marc borgers2-Jan-15 22:17
marc borgers2-Jan-15 22:17 
SuggestionDid you consider using PostSharp? Pin
Aurimas2-Jan-15 6:02
Aurimas2-Jan-15 6:02 
GeneralRe: Did you consider using PostSharp? Pin
pt14012-Jan-15 7:38
pt14012-Jan-15 7:38 
Another vote for PostSharp. If you need a free framework then maybe SheepAspect would do it for you, though it's seems to be no longer actively developed.
GeneralRe: Did you consider using PostSharp? Pin
marc borgers2-Jan-15 22:00
marc borgers2-Jan-15 22:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.