top of page

Keep Track of Your Users with a Usage Logger

There may be times when you'd like to keep track of your script's usage. For example, you may want to know the identity of the users running your program. Or whether users are using any of the features you just added. Or whether they're getting any errors in response to some action.


There are several third-party libraries to help you log such information. The most common one is log4net, which is very versatile and has many features. We want something simple and easy to use, so we'll write our own. Later, if you want, you can use the class we will write as a wrapper around log4net calls.


Our class will simply be named UsageLogger. Its constructor takes the path of the log file and stores it in a property (in case it needs to be accessed later):


public UsageLogger(string logPath)
{
    LogPath = logPath;
}

We then define the method Log, which takes in the user name, an event name, and any additional information to log. The Log method simply writes these data to the log file:


public void Log(string userName, string eventName, string info)
{
    try
    {
        File.AppendAllText(LogPath,
            $"{DateTime.Now:G}\t{userName}\t{eventName}\t{info}{Environment.NewLine}");
    }
    catch
    {
        // Ignore all exceptions
    }
}

We also include a timestamp in the log. The userName is obviously the name of the user. The eventName could be any events of interest, like "ScriptStarted," "ScriptEnded," "ReportExported," or "Error." The info is any additional information related to the event. For example, it may be the error message that the user received.


I've chosen to ignore all exceptions related to logging. This is so that any class that uses the logger doesn't have to worry about catching any exceptions.


That's it! Now create a UsageLogger object in your script, and pass it to the classes that need to log. As a convenience, you can provide a default UsageLogger instance (defined in the UsageLogger class):


public static UsageLogger Default
{
    get
    {
        if (_default == null)
        {
            _default = new UsageLogger(GetDefaultLogPath());
        }

        return _default;
    }
}

private static UsageLogger _default;

We used the Singleton pattern to define a single UsageLogger instance that can be accessed by any class by calling UsageLogger.Default. The GetDefaultLogPath() method creates a log path in the directory of the executing assembly, which will often be the location of your script:


private static string GetDefaultLogPath()
{
    return Path.Combine(GetAssemblyDirectory(), DefaultLogFileName);
}

private static string GetAssemblyDirectory()
{
    return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}

private const string DefaultLogFileName = "usage.txt";

This is a very simple logger, and useful in small programs. As I've already mentioned, log4net has many more features and is more "industrial-strength."


Note that using a singleton creates a dependency in your code that's not explicit. This makes classes that use it difficult to test and re-use. Even if you didn't use a singleton, logging is of a different "concern" than the function of the class. Another way to integrate logging in software is to use aspect-oriented programming.

Related Posts

See All

ESAPI Essentials 1.1 and 2.0

A few months ago, I introduced ESAPI Essentials—a toolkit for working with ESAPI (available via NuGet). I've recently added one major feature: asynchronous access to ESAPI for binary plugin scripts. Y

Announcement: ESAPI Subreddit

A few months ago, Matt Schmidt started the ESAPI subreddit. It's another useful resource for finding and discussing ESAPI-related topics. According to the description, This community is to post and di

Dump All Patient Data from ESAPI

If, for whatever reason, you need to dump out all of the data for a patient from ESAPI, there's a quick and dirty way to do it. Well, there are probably several ways to do it, but here's one I've foun

bottom of page