Click here to Skip to main content
Click here to Skip to main content
Go to top

Simple Log

, 30 Apr 2013
Rate this:
Please Sign up or sign in to vote.
Essential logging in one simple, robust, static, thread-safe class. No initialization, no configuration, no dependencies. Don't think, just log.

Simple Log usage demo

Introduction

A simple but robust logging class that fulfills the following requirements:

  • One simple, static class, easy to understand.
  • Works "out of the box", just include it and start logging. No initialization, no configuration necessary. No dependencies.
  • Simple, static methods to write a log entry.
  • Logs XML fragments to a file, one tag per log entry.
  • Easy but flexible way to change file and folder where logs are written to.
  • Possibility to pass log severity (info, warning, error, exception).
  • Possibility of simple filtering by log severity.
  • Log exceptions recursively with all inner exceptions, data, stack trace and specific properties of some exception types.
  • Has very little impact on performance of the main program due to queue-and-background task approach.
  • Is thread-safe, i.e. several threads can log into the same file concurrently.
  • Automatically logs the class and method name where the log method was called from.
  • Robust, compact and well-documented.

Background

Some time ago, I was looking for a really simple logging class.

Having a small project with a handful of files and classes, I didn't want to blow it up with a complex framework like log4net with dozens of classes and files. I didn't want to deal with hundreds of options and configuration settings. I didn't want to add a configuration file, just for logging. I didn't want to spend hours for learning how logging works with a particular framework.

I just wanted to include one class as source code and start logging. A class I can understand in minutes, not days, and I can eventually extend to my needs. Simple as that. I thought "why re-invent the wheel?" there are millions of logging approaches out there!

I stumbled over Marco Manso's Really Simple Log Writer. Yeah, that is really simple! A static class with some logging methods, working "out of the box". Logs XML fragments to a file, one tag per entry. Just what I was looking for!

Well ... logging inner exceptions should be done recursively. No big deal, changed in a minute. Oh, there is some code-redundancy in writing the logs ... no problem, duplicate code extracted in a separate method. Hmm, configuring the log file name and its location could be done in a more consistent, flexible way ... and comments and regions would be a good idea, too. When I'm logging, I'd like to distinguish between info, warning and error. That's really not asking for too much, is it? Ok, logging severity built in, along with some shortcut methods, of course. For compactness, IMHO, severity, date and time, should not be logged as separate tags, but as attributes - changed. One thing that I ever wanted and often missed, is that the location in code where a logging method was called is logged automatically. This way, you do not have to write "exception in method xy ..." each time; logging does that for you! Built in quickly. What do people suggest for Marco's class? A method to get the log file completed to a regular XML document? Right, that will be needed for sure. And when we're already about it, it could be shown in the browser with just one call. Great, isn't it? Log filtering is something that is always available in every logging system. Shall this class provide it, too? Yes, but it must be very basic and intuitive. Should not have an impact on the learning curve - built in a simple filtering by severity that everyone will understand instantly. Whew, we're through now, aren't we? What about performance and thread safety? True. We're neither - nor. Good logging systems have a queue and a background task to actually write the entries to disk. That way, they achieve both, performance and thread safety. Hmm, wasn't I looking for a simple class initially? That feature would make the class much (?) more complex. But it's the correct way how to do it! It's the last thing I'm adding.

Now, the class can be used in any project without worrying - and it is still comparably simple and easy to understand. From my point of view, it should be a good compromise between simplicity and functionality.

Using the Code

As Marco nicely expressed it: That's the beauty of it. Just include the class as source code anywhere in your project and start logging:

// Write info message to log
SimpleLog.Info("Test logging started.");

// Write warning message to log
SimpleLog.Warning("This is a warning.");

// Write error message to log
SimpleLog.Error("This is an error.");

That will produce the following output in a log file e.g. "2013_04_29.log" in the current working directory:

<LogEntry Date="2013-04-29 18:56:43" Severity="Info" 
              Source="SimpleLogDemo.Program.Main" ThreadId="9">
  <Message>Test logging started.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Warning" 
               Source="SimpleLogDemo.Program.Main" ThreadId="9">
  <Message>This is a warning.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Warning" 
               Source="SimpleLogDemo.Program.Main" ThreadId="9">
  <Message>This is an error.</Message>
</LogEntry>

Of course, exceptions can be logged, too, including all inner exceptions:

try
{
    // For demonstration, do logging in sub-method, 
    // throw an exception, catch it, wrap it in 
    // another exception and throw it.
    DoSomething();
}
catch(Exception ex)
{
    // Write exception with all inner exceptions to log
    SimpleLog.Log(ex);
}

...

/// <summary>
/// Do something for demonstration
/// </summary>
private static void DoSomething()
{
    SimpleLog.Info("Entering method. See Source which method is meant.");

    try
    {
        DoSomethingElse(null);
    }
    catch(Exception ex)
    {
        throw new InvalidOperationException("Something went wrong.", ex);
    }
}

/// <summary>
/// Do something else for demonstration
/// </summary>
/// <remarks>
/// Purposely provoke an exception
/// </remarks>
/// <param name="fred">Should not be null</param>
private static void DoSomethingElse(string fred)
{
    SimpleLog.Info("Entering method. See Source which method is meant.");

    try
    {
        // Purposely provoking an exception.
        int a = fred.IndexOf("Hello");
    }
    catch(Exception ex)
    {
        throw new Exception("Something went wrong.", ex);
    }
}

That will produce the following output:

<LogEntry Date="2013-04-29 18:56:43" Severity="Info" 
                 Source="SimpleLogDemo.Program.DoSomething" ThreadId="9">
  <Message>Entering method. See Source which method is meant.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Info" 
           Source="SimpleLogDemo.Program.DoSomethingElse" ThreadId="9">
  <Message>Entering method. See Source which method is meant.</Message>
</LogEntry>
<LogEntry Date="2013-04-29 18:56:43" Severity="Exception" 
             Source="SimpleLogDemo.Program.Main" ThreadId="9">
  <Exception Type="System.InvalidOperationException" Source="SimpleLogDemo.Program.DoSomething">
    <Message>Something went wrong.</Message>
    <Exception Type="System.Exception" Source="SimpleLogDemo.Program.DoSomethingElse">
      <Message>Something went wrong.</Message>
      <Exception Type="System.NullReferenceException" Source="SimpleLogDemo.Program.DoSomethingElse">
        <Message>Object reference not set to an instance of an object.</Message>
        <StackTrace>   at SimpleLogDemo.Program.DoSomethingElse(String fred) 
           in D:\Projekt\VisualStudio\SimpleLogDemo\SimpleLogDemo\Program.cs:line 91</StackTrace>
      </Exception>
    </Exception>
  </Exception>
</LogEntry>

The log file that is written to is assembled as follows:

/// <summary>
/// File to log in
/// </summary>
/// <remarks>
/// Is assembled from <see cref="LogDir"/>, <see cref="Prefix"/>,
///   the current date and time formatted in <see cref="DateFormat"/>, 
/// <see cref="Suffix"/>, "." and <see cref="Extension"/>.
///   So, by default, the file is named e.g. "2013_04_21.log"
///   and is written to the current working directory.
/// It is assembled in <see cref="GetFileName"/> using
///   string.Format("{0}\\{1}{2}{3}.{4}", LogDir, Prefix, 
///                 dateTime.ToString(DateFormat), Suffix, Extension)
/// </remarks>
public static string FileName
{
    get
    {
        return GetFileName(DateTime.Now);
    }
}

/// <summary>
/// Gets the log filename for the passed date
/// </summary>
/// <param name="dateTime">The date to get the log file name for</param>
/// <returns>The log filename for the passed date</returns>
public static string GetFileName(DateTime dateTime)
{
    return string.Format("{0}\\{1}{2}{3}.{4}", LogDir, Prefix, 
                  dateTime.ToString(DateFormat), Suffix, Extension);
}

Want to use another log file at another location? Apart from setting several properties like prefix, suffix, extension, directory, log level, etc. separately, you can use convenience method SetLogFile to set them all at once:

/// <summary>
/// Set all log properties at once
/// </summary>
/// <remarks>
/// Set all log customizing properties at once. This is a pure convenience function. 
/// All parameters are optional.
/// When <see cref="logDir"/> is set and it cannot be created or writing a first entry fails, 
/// no exception is thrown, but the previous directory, respectively the default directory 
/// (the current working directory), is used instead.
/// </remarks>
/// <param name="logDir">
/// <see cref="LogDir"/> for details. When null is passed here, <see cref="LogDir"/> is not set. 
/// Here, <see cref="LogDir"/> is created, when it does not exist.</param>
/// <param name="prefix">
/// <see cref="Prefix"/> for details. When null is passed here, <see cref="Prefix"/> is not set.
/// </param>
/// <param name="suffix">
/// <see cref="Suffix"/> for details. When null is passed here, <see cref="Suffix"/> is not set.
/// </param>
/// <param name="extension">
/// <see cref="Extension"/> for details. When null is passed here, <see cref="Extension"/> 
/// is not set.
/// </param>
/// <param name="dateFormat">
/// <see cref="DateFormat"/> for details. When null is passed here, <see cref="DateFormat"/> 
/// is not set.
/// </param>
/// <param name="logLevel">
/// <see cref="LogLevel"/> for details. When null is passed here, <see cref="LogLevel"/> 
/// is not set./// </param>
/// <param name="startExplicitly">
/// <see cref="StartExplicitly"/> for details. When null is passed here, 
/// <see cref="StartExplicitly"/> is not set.
/// </param>
/// <param name="check">
/// Whether to call <see cref="Check"/>, i.e. whether to write a test entry after setting 
/// the new log file. If true, the result of <see cref="Check"/> is returned.
/// </param>
/// <returns>Null on success, otherwise an exception with what went wrong.</returns>
public static Exception SetLogFile(
    string logDir = null, 
    string prefix = null, 
    string suffix = null, 
    string extension = null, 
    string dateFormat = null, 
    Severity? logLevel = null, 
    bool? startExplicitly = null, 
    bool check = true)

For example, you can say:

// Log to a sub-directory 'Log' of the current working directory. Prefix log file with 'MyLog_'. 
// This is an optional call and has only
// to be done once, preferably before the first log entry is written. 
SimpleLog.SetLogFile(".\\Log", "MyLog_");

Note: In contrast to logging itself, changing log file settings, i.e. changing the file it is written to, is NOT thread-safe. It won't crash, though, but unwanted results may occur. It is not intended to change log file settings during regular logging, but once at application startup (before the first log entry is written) and then left alone.

To get a log file as complete XML, you can use:

/// <summary>
/// Get the current log file as XML document
/// </summary>
/// <remarks>
/// Does not throw an exception when the log file does not exist.
/// </remarks>
/// <returns>The log file as XML document or null when it does not exist.</returns>
public static XDocument GetLogFile()
{
    return GetLogFile(DateTime.Now);
}

You can also show the current log file as XML in a browser with just one line:

/// <summary>
/// Shows the current log file 
/// </summary>
/// <remarks>
/// Opens the default program to show XML files and displays
/// the requested file, if it exists. Does nothing otherwise.
/// A temporary XML file is created and saved in the users'
/// temporary path each time this method is called. So don't 
/// use it excessively. 
/// </remarks>
public static void ShowLogFile()

That's it! You don't have to care about anything else. Enjoy!

History

  • 29 April 2013 - First published, Version 1.0
  • 19 June 2014 - Added possibility to log directly to disk, without background task
  • 19 June 2014 - Added possibility to start background task explicitly

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Jochen Scharr
Software Developer (Senior)
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionMy vote of 5 Pinmemberskambo24-Jul-14 13:12 
GeneralThoughts PinmemberPIEBALDconsult19-Jun-14 4:30 
GeneralRe: Thoughts PinmemberJochen Scharr20-Jun-14 1:42 
GeneralRe: Thoughts PinmemberPIEBALDconsult20-Jun-14 12:11 
GeneralRe: Thoughts PinmemberJochen Scharr20-Jun-14 14:39 
GeneralMy vote of 5 Pinmemberjohannesnestler30-Apr-13 4:49 
Looks very promising! I like your queue implementation and the automatic caller detection (I think without async handling this could be a performance bottleneck. So thank you for sharing - and btw. VERY good use of XML comments.
GeneralRe: My vote of 5 PinmemberSharath C V8-Jul-14 2:17 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140922.1 | Last Updated 30 Apr 2013
Article Copyright 2013 by Jochen Scharr
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid