Simple function-call tracing in C++

There are many ways to do function-call tracing in C++, where the simplest method is to just print "Entering function X" when entering the function, and right before returning print "Leaving function X". It’s a lot of work though, especially if the function have multiple return statements.

One solution to the problem above is to use a trace object. You create a class whose constructor prints the entering message, stores the function name, and whose destructor prints the leaving message. Then create an instance of this class as a local variable. When the object is instantiated, which happens when the function is called, the enter message will be printed. And when the function leave, no matter when or where or how it happens, the object will be destructed and the leave message will be printed.

Now how should such a class look like? It could be something simple like this:

struct tracer
{
    std::string name_;  // Name of the function

    tracer(std::string const& name)
        : name_(name)
    {
        std::clog << "Entering function " << name_ << std::endl;  // Flushing is important
    }

    ~tracer()
    {
        std::clog << "Leaving function " << name_ << std::endl;  // Flushing is still important
    }
};

That’s the basic of what is needed.

Now to use it is very simple:

void some_class::some_member_function()
{
    tracer _unique_name_{"some_class::some_member_function");

    // Lots of code and multiple `return` statements...

}

That is the basic usage. It has some drawbacks, in that tracing will always be enabled. It’s seldom needed for release builds for example, so only using it when a _DEBUG macro is defined is a good start. It might be even better to have a special TRACING_ENABLED macro so it can be enabled even in release builds which can be useful some times. Extra logic could also be added to check for flags set at run-time.


Here is an example full solution that uses preprocessor macros to enable and disable tracing at the time of compilation.

#pragma once
#ifndef TRACING_H
#define TRACING_H

#include <string>
#include <iostream>
#include <iomanip>

// Simple structure to handle function-call tracing.

// On debug builds, always build with tracing enabled unless explicitly disabled
#if defined(_DEBUG) && !defined(TRACING_DISABLED)
# define TRACING_ENABLED
#endif

// Define a preprocessor macro to help with the tracing
#ifdef TRACING_ENABLED
# define TRACE() tracing::tracer _tracer_object__ ## __COUNTER__ {__func__, __FILE__, __LINE__}
#else
# define TRACE() // Nothing
#endif

#ifdef TRACING_ENABLED
namespace tracing
{
    class tracer
    {
    public:
        tracer() = delete;  // Disallow default construction
        tracer(tracer const&) = delete;  // Disallow copy construction
        tracer(tracer&&) = delete;  // Disallow move construction
        tracer& operator=(tracer const&) = delete;  // Disallow copy assignment
        tracer& operator=(tracer&&) = delete;  // Disallow move assignment

        tracer(std::string const& fun, std::string const& file, int const line)
            : function_name{fun}, file_name{file}, line_number{line}
        {
            std::clog << "TRACE: Entering function " << function_name << " (" << file_name << ':' << line_number << ')' << std::endl;
        }

        ~tracer()
        {
            std::clog << "TRACE: Leaving function " << function_name << std::endl;
        }

    private:
        std::string function_name;
        std::string file_name;
        int         line_number;
    };
}
#endif // TRACING_ENABLED

#endif // TRACING_H

Sample program using the above header file:

#include "tracing.h"

struct foo
{
    int bar(int value)
    {
        TRACE();

        if (value < 10)
            return value * 2;
        else
            return value * 3;
    }
};

int main()
{
    TRACE();

    foo my_foo;
    my_foo.bar(20);
    my_foo.bar(5);
}

The output from the program as shown above could be something like

TRACE: Entering function main (/home/X/Y/main.cpp:18)
TRACE: Entering function bar (/home/X/Y/main.cpp:7)
TRACE: Leaving function bar
TRACE: Entering function bar (/home/X/Y/Testing/main.cpp:7)
TRACE: Leaving function bar
TRACE: Leaving function main

Output may vary depending on the compiler being used.

Leave a comment