Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I want to add logging to an application I'm currently working on. I've added logging before, that's not an issue here.

But from a design perspective in an object-oriented language, what are the best practices for logging that follow OOP and patterns?

Note: I'm currently doing this in C#, so examples in C# are obviously welcome. I would also like to see examples in Java and Ruby.


Edit: I'm using log4net. I just don't know what's the best way to plug it in.

share|improve this question
add comment

closed as too broad by gnat, GlenH7, Bart van Ingen Schenau, Kilian Foth, MichaelT Dec 3 '13 at 15:23

There are either too many possible answers, or good answers would be too long for this format. Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs.If this question can be reworded to fit the rules in the help center, please edit the question.

7 Answers

The best practice that I would recommend is to use log4j rather than rolling your own. (Which has been ported from Java to both C# and Ruby, so it applies to all 3 languages that you are interested in.)

If you read through that manual page you will discover several other best practices. Such as being lightweight, configurable outside of your application, being able to turn up and down logging for different parts of your application independently, and so on.

share|improve this answer
add comment

Where I work we write a lot of .NET desktop apps. We normally implement 2 events in our components, one for logging information and the other for logging exceptions (although we often let the exceptions bubble up instead of raising the separate event. It depends upon the situation). Using this architecture, none of our libraries need to know how logging is implemented or how the information is used, stored or treated. We then have the application handle the logging events in a manner that is appropriate to that app. A number of years ago this architecture made our switch from using MS Enterprise Library logging to BitFactory's logging component a very simple transition.

share|improve this answer
    
+1 for using an Event/Observer pattern: change the observer, you've changed the logging –  Matthieu M. Jun 8 '11 at 19:59
add comment

Since you are doing this in C#, I would recommend you looking at NLog and ElMAH. They can be VERY easily installed using NUGET. I've put some links to those below so you can get more info.

share|improve this answer
add comment

Personally, I take the logging framework of choice (in my case, Entlib because I work with .NET) and write an AOP aspect for logging.

You can then attribute any methods/properties/classes/namespaces and add logging to them without cluttering up the source.

share|improve this answer
    
It sounds very interesting, but I have reservations about what you would be able to log and how informational the log would be (ie more than "just" instrumentation of methods). Would love to see a working example of this approach to see what can and cannot be done. Especially as I am just starting out on a new app and would like to see where / how far I could carry this. –  Marjan Venema Jun 8 '11 at 16:28
    
@marjan Venema: The post sharp documentation has an example of an aspect that logs entering/exiting a method. doc.sharpcrafters.com/postsharp/2.0/##PostSharp.chm/html/… In Post sharp's case, it weaves the code from the attribute into the source at build time, so it doesn't affect performance like some others do. –  Steve Evers Jun 8 '11 at 16:49
    
thanks! –  Marjan Venema Jun 8 '11 at 17:10
add comment

The system I'm currently working on uses an event driven architecture and messaging, so that most actions in our system are the result of a command and they result in events (as DTO classes that are dispatched, rather than a standard delegate event). We attach event handlers whose sole purpose is to handle logging. This design helps us not repeat ourselves, and also not have to modify existing code to add/change functionality.

Here's an example of one such logging class, that handles all events to be logged from a narrow section of our application (those regarding one particular content source we import from).

I won't necessarily say this is hands down best practice, as I seem to change my mind on what and how to log often - and every time I need to use a log to diagnose an issue I inevitably find ways to make improvements to the information I record.

I will say, though, that recording the pertinent information (especially in a Ctrl-F/find searchable way) is the most important part.

The second most important part is getting the logging code away from your main logic - it can make a method ugly and long and convoluted very quickly.

public class MctLogger :
    IEventHandler<StoryImported>,
    IEventHandler<StoryScanned>,
    IEventHandler<SourceDirectoryMissing>,
    IEventHandler<SourceDirectoryAccessError>,
    IEventHandler<CannotCreateScannedStoryDirectory>,
    IEventHandler<CannotReadStoryDocument>,
    IEventHandler<StorySkippedPastCutoff>,
    IEventHandler<StorySkippedDuplicateUniqueId>,
    IEventHandler<StorySkippedByFilter>
{

    public void Observe(StoryImported e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryImported");
        log.Info("Story Unique ID: {Story.UniqueId}, Content ID: {ContentId}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StoryScanned e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StoryScanned");
        log.Info("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(SourceDirectoryMissing e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryMissing");
        log.Error("Directory: " + e.Directory);
    }

    public void Observe(SourceDirectoryAccessError e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.SourceDirectoryAccessError");
        log.Error(e.Exception, "Exception: " + e.Exception.Message);
    }

    public void Observe(CannotCreateScannedStoryDirectory e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotCreateScannedStoryDirectory");
        log.Error(e.Exception, "Directory: {Directory}, Exception: {Exception.Message}".SmartFormat(e));
    }

    public void Observe(CannotReadStoryDocument e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.CannotReadStoryDocument");
        if (e.Exception == null) {
            log.Warn("File: {FilePath}".SmartFormat(e));
        }
        else {
            log.Warn(e.Exception, "File: {FilePath}, Exception: {Exception.Message}".SmartFormat(e));
        }
    }

    public void Observe(StorySkippedPastCutoff e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedPastCutoff");
        log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StorySkippedDuplicateUniqueId e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedDuplicateUniqueId");
        log.Warn("Story Unique ID: {Story.UniqueId}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }

    public void Observe(StorySkippedByFilter e)
    {
        var log = Slf.LoggerService.GetLogger("RoboChef.Content.Mct.StorySkippedByFilter");
        log.Warn("Story Unique ID: {Story.UniqueId}, Reason: {Reason}, File: {FilePath}, Title: {Story.Headline}".SmartFormat(e));
    }
}
share|improve this answer
add comment

As others have said, use log4j or log4net or some other well-built logging framework.

I tend to really dislike logging code getting in the way of the business logic. That's why I use Log4PostSharp. That means I can use Aspect Oriented Programming to annotate methods like this:

[Log(LogLevel.Info, "Counting characters.")]
int CountCharacters(string arg) 
{
    return arg.Length;
}

Or every method in an assembly like this:

[assembly: Log(AttributeTargetTypes = "*", 
 EntryLevel = LogLevel.Debug, ExitLevel = LogLevel.Debug, 
 ExceptionLevel = LogLevel.Error)]
share|improve this answer
add comment

I am not sure if any framework does this, but from a design perspective, I would model the information need to be logged into mainly three categories:

  1. method level tracing
  2. exception logging
  3. extra run-time information developers believe that is vital for investigating in case of a run-time failure (or any behavior related to run-time only situation).

For the first two categories, my ideal logging framework should handle them as a post build process and transparent to developers. It would be nice to declaratively add logging to assemblies, something like the following:

Trace YourNamespace.* [public methods|constructors]
{  # options
   ignore trivial methods,
   format: "{time stamp}: {method name}({parameter list})",
   condition: "{Context.Instance.UserID in (12432,23432)}",
}

Exception YourNamespace.Program.Main [unhandled exceptions]
{
  format: "{time stamp}: {Context.Instance.UserId} {exception}",
  action: die,  # options are throw, swallow,
}

For the 3rd category, programmers can just create one or more dedicated "logging" methods and leverage the tracing for the first category. The logging methods do nothing more than serving a stub point to which tracing rules can be applied.

share|improve this answer
add comment

Not the answer you're looking for? Browse other questions tagged or ask your own question.