Smart tracing in C++
Tracing is probably the simplest way to debug some piece of code you (or someone else) wrote. For large projects, it’s also often faster than trying to load every debug symbol in your favorite debugger.
The principle of tracing is printing out messages at key points in your code. This way you can follow the execution of your program as it runs normally, instead of dealing with breakpoints and debugger controls. While outputting text is quite straightforward in most programming languages, we can still make use of a mini framework. Such framework could provide useful things such as:
- redirecting output to different streams or files
- turning output on/off
- automatically indenting tracing output to enhance the readability
The approach I’m presenting here deals with items 2 and 3 of this list and is designed for C++ development.
The goal
C++ is a functional language and the behavior of your program depends on the interaction of those functions. Therefore, we would like the trace output to easily display this interaction of functions (or methods). An indented tree view fits the bill perfectly:
+- main() | +- intialize() | | initializing... | / | +- functionA() | | doing something.. | +- functionB() | | +- functionA() | | | doing something.. | | / | / | +- finalize() | | cleaning up... | / /
Making use of the scope
An object or variable always lives in a well-defined scope. Our goal is to show the entry and exit points of our program’s functions. Every function body forms a scope. Therefore, we can generalize the problem and begin to think of a way to trace scope entry and exit points.
Constructors and destructors
Every time an object becomes in scope, its constructor is called . Similarly, the destructor is called when the object goes out of scope.
using std::cout;
using std::endl;
class MyClass
{
public:
{ cout << "MyClass::MyClass()" << endl; }
{ cout << "MyClass::~MyClass()" << endl; }
};
int
{
MyClass a;
cout << "function body" << endl;
return 0;
}
Running the above code gives us the following output:
MyClass::MyClass() function body MyClass::~MyClass()
Generating tracing objects on demand
In every scope we would like to trace, we simply have to keep a tracer instance that will output certain messages in its constructor and destructor. To customize the text displayed, we’ll add a string argument to our tracer’s constructor.
We hide the tracing objects behind macros. This way we can use #define values to turn tracing on or off. In debug mode, the preprocessor inserts the tracer object. In release mode, the macro is ignored and no code is generated.
Scope indentation
The final piece in our little framework is the smart indentation. Scope can be viewed as a stack. When your program executes, the current scope is the top of the stack. All we need to indent our tracing messages is a shared (static) integer variable that will tell the tracers how many indentations they should use. The constructor will increment this value and the destructor will decrease it.
struct ScopeTrace
{
static int traceLevel;
{
++traceLevel;
// print indented line (msg)
}
{
// print indented line
--traceLevel;
}
};
Adding visual cues
Now that everything is in place, we can improve the readability by outputting vertical lines and other symbols. Too much symbols will make our output hard to decipher.
Putting it all together
I’ve put together the ideas described here in
smartTrace.h. All you need to do is to #include it and make use of the following macros in your code:
- TRACE_ENTER( “some string” )
- Insert this on the very first line of any function or method you wish to trace. It will increase the indentation level.
- TRACE( “some string” )
- This can be used anywhere and will output the string using the current indentation level without modifying it. Use it inside functions.
Example
Compiling this file with DEBUG defined ( g++ -DDEBUG example.cpp ) gives us the following output:
+- main | +- ClassA::ClassA | / | Calling function... | +- function1 | | +- ClassA::ClassA | | / | | Calling method on a... | | +- ClassA::method1 | | | Processing data... | | / | | +- ClassA::~ClassA | | | Releasing memory... | | / | / | +- ClassA::~ClassA | | Releasing memory... | / /
See also
A relatively recent article over at the The Code Project features a similar tracer that lets you save the output to disk. It can also report the time usage of your function, combining the tracer with a simple profiler.
It is a very interesting story. Thanks!
Hey!
Recently found this article. I’ve actually been working on a C++ code tracing library myself, and thought you might be interested. I liked your trace output style, so I borrowed it and updated my own to match it – hope you don’t mind.
One of the things I wanted to tackle was tracing in multiple threads. Found this quite a challenge actually.
Anyway, you can find my trace library here:
http://www.bigangrydog.com/tracedog/code_tracing.xhtml
I also wrote a blog articule discussing the reasons for it here:
http://bigangrydog.blogspot.com/2010/05/new-open-source-puppies-from-big-angry.html
Good stuff & thanks for the post.
Andy