Guidance for Logging in Application
Logging is an important aspect of your application, a good logging strategy allows you to gain insights into how your application is functioning at runtime. At a minimum, logging can be used for exceptions, helping you to determine the root cause of the problem and giving you better insight as to how to prevent this same problem from occurring in the future.
Logging can be used to provide a non-repudiation audit trail for security sensitive operations (see the caveat at the bottom of this page for more information).
Logging is an important aspect of your application, a good logging strategy allows you to gain insights into how your application is functioning at runtime. At a minimum, logging can be used for exceptions, helping you to determine the root cause of the problem and giving you better insight as to how to prevent this same problem from occurring in the future. Logging can also be used to provide a non-repudiation audit trail for security sensitive operations (see the caveat at the bottom of this page for more information).
When it comes to logging in your .NET application there are numerous options from which to choose; however, I have settled on two recommendations:1. The basic exception logging scenario
- Utilizes custom code found in the new project templates
2. The advanced scenario
- Utilizes log4net as the logger of choice and log4net for custom extensions
* Note that these are recommendations, other alternatives may be viable.
* A tutorial is provided here to assist with the setup of log4net.
Which one of these options you choose depends largely on your specific requirements. Below is a list of general guidelines that may help you decide.Use the basic solution if:
- You require logging of exceptions only
- You want/need to own the logging codeUse the advanced solution if:
- You want to log more than exceptions
- You want configuration based flexibility for dictating what/how items are logged
- You want to instrument your application (more common with shared assemblies)Basic solution:
The basic solution is implemented as custom code that will be found in the "Logging" directory of the library project within the solution, the solution layout is as below. Note - As an alternative to the basic logging solution you can consider using the ELMAH framework.Library project breakdown
Logger - This is the main entry point for logging in the solution. This class is exposed as a singleton instance through the static Instance property of this class. This was setup as a singleton instead of static methods so you more easily extend it if you need to. There is only a single public method in this class, LogException, that accepts different parameters depending on which overload you call. If you desire to log more information than what the current design allows you could easily add additional overloads to accommodate this.
LoggingData - This class is used for a couple of purposes, first it is used as a wrapper around all the data we plan to use when writing our log entry and second it holds a log formatter. If you need additional data included in your log then you would want to expand this class to hold that data as well. The formatter is any implementation of ILoggingDataFormatter which is used to format the output of the current instance.
ILoggingDataFormatter - This in an interface which has a LoggingData property and a method to get the formatted data.
TextLoggingDataFormatter - This is the default implementation of ILoggingDataFormatter, it outputs log data in a simple text based format. You can easily modify what this looks like or if you want a different output style entirely (i.e. HTML output for richer emails) you are free to create your own implementations.Advanced solution:
This solution is implemented by adding an additional logging project. In that project there is an abstraction over the actual underlying logging mechanism (log4net in this case). The purpose of this abstraction is so that your consuming code does not interact directly with log4net and could then be changed to some other logging solution on the backend without imposing major changes to your code.
Logging project breakdown
ILogger - This is an interface that is at the heart of the abstraction, it is basically a slimmed down version of log4net's ILog interface. This interface is what defines the available methods you have for logging.
log4netLogger - This is the lone implementation of ILogger in this project, it is what handles the interaction between the abstraction and the implementation (log4net).
LogManager - This is a class that helps us follow the log4net best practice of having a "logger per class" which is what fully enables the power of the hierarchical logging mechanisms in log4net. This class houses a few static methods that return instances of the ILogger interface based on the parameters passed. The most common one will be GetLoggerForCurrentClass which will return an ILogger instance for the class that the calling method is defined in.
*Note - In this sample solution you will notice a private static log field that is of type ILogger in all classes, this is the variable that you will be using to do all of your logging for that particular class. Following this pattern is how you can fully leverage the hierarchical logging capabilities of log4net. If you plan on following this pattern you may want to add this field to a base class so you don't have to repeat this code throughout.
Caveat:
Logging in either of the above methods does not necessarily meet auditing requirements! If your application has specific audit requirements the use of logging (as implemented above) may not meet your needs. The reason is that neither of the above two options are "reliable" meaning that they are best-effort, fail stop logging systems (neither solution should throw unexpected exceptions at runtime). If your logging target is unavailable (i.e. the event log is full) your calls to "log" will continue on as if everything worked. This is by-design, as it is potentially dangerous to throw exceptions in the code that is intended to log exceptions. With that being said if your audit requirements are such that you have to ensure that your audit records get successfully written you should include those messages in the same transaction for which the audit log message is for. This is typically the difference in needing to provide non-repudiation vs. exception/debug info.