SS_Log, SS_Log_Window, SS_Log_AddIn

Introduction
Projects Descriptions
Installation / Integration
Usage
The Filters

 

Introduction

Often times we want to create a log of what steps a program
takes during run-time. Maybe our program works fine during a
debug build but crashes during a release build. Or maybe we
are developing on WinNT, but testing on Win98 without a development
environment on the Win98 machine. Since we can’t debug in these cases,
it would be nice to have a log of all the steps a program took before
it crashed.

This is a set of small projects (all working together) that can ease
creating a log of your program’s code path at run-time. Some features
include:

  • Easy installation / integration into any project (MFC, Non-MFC, Console
    apps… all app types on Windows platforms.)
  • No need to create message strings ahead of time… log functions accept
    sprintf-style messages with variable length parameter lists.
  • Automatically
    include date/time, and code filename/line number in every message.
  • Log to any file, or log straight to an MFC-based Log Window which opens
    automagically
    on demand during run-time (see picture below).
  • When using the Log Window, double-click on any log entry to jump
    straight to that location in your code in MSDEV
    .
  • Log Window will remain open when your app crashes/closes.
  • Send different types of log messages (Debug, Release, Critical, Warning,
    Normal, Trace, or add your own fairly easily) and then filter out any types of
    messages you are not currently interested in without re-compiling.
  • For each message, individually specify if the message is to be sent in either
    debug, release, or both builds.
  • For each message, individually specify if the message is to be sent to a file,
    the Log Window, or both.
  • Have multiple simultaneous logs, specifying output location for
    each log individually (unique or shared file, and unique or shared Log Window,
    or both).
  • Append logs over multiple runs, or erase log before starting each new run.
  • Completely remove all logging code from your project by simply defining
    “_SS_LOG_INACTIVE” in the pre-compiler (NOTE: this removes only the global log
    code, but not local logs if any are defined).

 

 

Projects Descriptions

  • SS_Log — This project includes the main logging classes, and can
    be used without the other projects if you desire only to output your logs
    to a file.
  • SS_Log_Window — This project is the Log Window mentioned above,
    which will automatically open during your program’s run-time and start
    accepting logs from the SS_Log class.
  • SS_Log_AddIn — This project is an MSDEV add-in that allows you to
    double-click in the Log Window and have MSDEV jump to the associated line
    of code in your project.
  • SS_Log_Test — This project is simply an example of how to use the
    SS_Log logging class. It uses a global log and 2 local logs, and
    demonstrates a few of the features available.
  • Utils — This project is a helper library. It contains only a
    registry key class. Nothing too interesting here…

 

Installation / Integration

To install the projects, simply place the SS_Log_Window.exe and SS_Log_AddIn.dll
files in your %SYSTEMROOT% folder (ex. c:\winnt or c:\windows.). Don’t worry
about installing the add-in, as the first time you double-click on a log entry
in the SS_Log_Window.exe application, it will ask you if you want to install the
add-in.

To integrate the log into your projects, follow 3 these steps:

  1. Place the SS_Log_Include.h and SS_Log.h files in your project’s include
    directory.
  2. Add the SS_LogD.lib and UtilsD.lib to your debug project, and add SS_Log.lib
    and Utils.lib to your release project. (Project->Settings->”Link” tab->
    Object/Library Modules).
  3. #include “SS_Log_Include.h” in any file you would like to produce logs from
    (do not include “SS_Log.h”).
  • If you would like, you can add the SS_Log_Include.cpp, SS_Log.cpp, and
    SS_RegistryKey.cpp files to your project instead of adding SS_Log.lib
    and SS_LogD.lib as in step 2.

 

Usage

We will talk about 2 different log types: a) the global log, and b) local logs.
By #including “SS_Log_Include.h” in your project, a global log is created
for you (assuming you don’t have _SS_LOG_INACTIVE #defined). To use the global
log, all you have to do is enter a line as follows:

        Log(“some message here, %d, %s”, nValue, szText, … );

This line will use the global log’s defaults, sending the message to the global
log with a “CRITICAL” level. The message goes to a Log Window in debug builds
and to the file “\SS_Log.log” during release builds by default. To override the
global log’s defaults, use these calls:

        LogFilter( DWORD dwFilter );
        LogRemoveFilters( DWORD dwFilter );
        LogAddFilters( DWORD dwFilter );
        LogFilename( TCHAR* szFilename );
        LogWindowName( TCHAR* szWindowName );
        LogEraseLog()

        Log( TCHAR* pMessage, … );
        Log( DWORD dwFilter, TCHAR* pMessage, … );

When the above global log functions are used, all messages in all threads will
go to the same place (specified file, specified log window, or both). If you
want to send some messages to a different location, you may create multiple
simultaneous logs by defining “local logs” with the following code:

        SS_Log myLog;

The following calls can be used for local logs:

        myLog.Filter( DWORD dwFilter );
        myLog.RemoveFilters( DWORD dwFilter );
        myLog.AddFilters( DWORD dwFilter );
        myLog.Filename( TCHAR* szFilename );
        myLog.WindowName( TCHAR* szWindowName );
        myLog.EraseLog()

        Log( SS_Log* pLog, TCHAR* pMessage, … );
        Log( SS_Log* pLog, DWORD dwFilter, TCHAR* pMessage, … );

Note that if you don’t call the myLog.Filename(…) and
myLog.WindowName(…) functions, the local log will default
to the same window and file that the global log defaults to, and hence,
all messages from both logs will go to the same file/window. An example
for a complete local log would be:

        SS_Log myLog;
        myLog.Filename(“\\MyLog.log”);
        myLog.WindowName(“My Local Log”);

        Log( &myLog, “message to local log here, %d, %s”, nValue1, szText1 );
        Log( “message to global log here, %s, %s”, szText2, szText3 );

The first Log(…) call above will send it’s message to the local log, while
the second one will send it’s message to the global log.

 

The Filters

Filters are assigned to each log individually by default, but each log’s
default values can be overridden. In addition, every message sent to a log
can contain filters that overriding the log’s default filters. You may
then turn each filter type “on” and “off” by setting registry values (created
automatically by the SS_Log class). Any message with a filter type that is
turned off in the registry will not get sent to the log. This allows you to
turn on and off the filters without re-compiling.

(Registry key: “HKEY_CURRENT_USER\Software\SS_Log”. For all values, 1=on
and 0=off. You can also set the default log file here.)

You set log filters with the LogAddFilters(…),
LogRemoveFilters(…), and
LogFilter(…) global calls. The following
filters are pre-defined (the code includes instructions for adding your
own filter types):

  • Builds — These are handled automatically. You typically won’t need
    to change the Builds filters unless you want messages to be sent *only* in
    debug (or release) builds.
    • LOGTYPE_DEBUG
    • LOGTYPE_RELEASE

  • Outputs — These determine where the messages go during run-time.
    By default, debug builds send messages to Log Windows, and release builds
    send messages to a file (\SS_Log.log). You can have messages go to both
    a window and a file simultaneously, or you can specify that messages always
    go to the Log Window (or a file) as opposed being dependant on the build type.
    • LOGTYPE_LOGTOWINDOW
    • LOGTYPE_LOGTOFILE

  • Levels — You may specify one or more of these types (though I can’t
    really think of why you’d specify more than one for any given message).
    By default, all messages get the LOGTYPE_CRITICAL level. Note that unlike
    the Builds and Outputs types, the Levels types combine. That is, if a log
    or individual message has the LOGTYPE_WARNING and the LOGTYPE_TRACE types
    specified, then BOTH types must be turned on in the registry for the message
    to be sent.
    • LOGTYPE_CRITICAL
    • LOGTYPE_WARNING
    • LOGTYPE_NORMAL
    • LOGTYPE_TRACE

While the LogAddFilters(…) and
LogRemoveFilters(…) calls work as you’d expect, the
LogFilter(…) call does not. The
LogFilter(…) does not “set” a log’s filter exactly
as specified in the parameter list. Instead, the function is “smart” and tries to figure
out what type of filter (Builds, Outputs, Levels, or some combination thereof) the user
is trying to set. For example, if the user calls
LogFilter( LOGTYPE_TRACE ), the function
will set only the Levels type, ignoring the Builds and Outputs types currently set in the
filter. Specifically, the function would remove the LOGTYPE_CRITICAL filter (which was
there by default), add the LOGTYPE_TRACE filter, and leave the Builds and Outputs types
alone. Note the difference if the user had called
LogAddFilters( LOGTYPE_TRACE ) instead,
where the function would have added the LOGTYPE_TRACE filter and kept the LOGTYPE_CRITICAL
filter.

For more examples and detail, see the example project “SS_Log_Test”.

 

Notes

A few problems that should be mentioned:

  • Log Windows and log files are both appended by default. This means that over
    time, if a program is writing to some log file every time it runs, the file
    could grow quite large. You should call LogEraseLog() at the beginning of every
    program in order to ensure that your files remain small. I will consider adding
    a set-able max file size if enough people request it.
  • #define-ing _SS_LOG_INACTIVE will remove any code that accesses the global log
    from your project. However, it does not remove local logs. Maybe there are
    situations where this behavior would actually be desirable, but the real reason
    it behaves like that is because I couldn’t figure out how to do it otherwise.
  • Typically you will not need to call the three global log functions that have no
    parameters (i.e. LogFilter(), LogFilename(), and LogWindowName()). These
    functions return the associated value that the global log currently holds. Just
    be aware that if you use these three functions in your code, they will cause a
    compile error if _SS_LOG_INACTIVE is ever #defined.
  • Many file-writing functions (streams) will accept information to be written to a
    file but wait to write out to the file until a “convenient” time, which ensures
    fluid program flow. However, if the program crashes before the stream “flushes”
    its contents to the file, some information may not get written. In the case of our
    log messages, some final messages could get lost. This is a problem, so in order
    to be completely sure that all log messages get written out to their destinations
    before a program crash occurs, we tell the stream to flush after every message call.
    This, however, is inefficient use of system resources, and can slow your program’s
    execution substantially. I have no way around this problem. Furthermore, currently
    the registry is called to determine which filters are on and off for every message
    sent. This is also inefficient (and easily fix-able), but I’ve chosen to leave it
    this way so that the filters can be turned on and off during run-time (no need to
    restart the program). Just be aware of this fact: The SS_Log logging class can
    significantly reduce your program’s performance (speed). The up-side is, assuming
    that you use only the global log (no local logs) in your code, you should regain 100%
    of your program’s efficiency by #declare-ing “_SS_LOG_INACTIVE” for the pre-compiler.

Downloads

Download “SS_Log Binaries and Includes”
Download “SS_Log MSDEV Projects”

More by Author

Must Read