Function Profiling with the logme Library

The logme library includes powerful built-in support for function profiling. One of its strongest advantages is how little effort it takes to start tracing function execution in detail.

With logme, you can add a single macro to automatically log:

  • function entry (including its name),
  • function exit,
  • optionally, input parameters,
  • and the return value.

This makes it easy to understand how a function behaves in real execution: when it is called, what data it processes, what it returns, and how long it takes to run.


Basic Profiling Macros

Function profiling in logme is implemented through a family of macros called LogmeP.

The naming convention is simple:

  • V — indicates that the function has no return value (void logging)
  • Log level suffix:
    • D — Debug
    • I — Info
    • W — Warning
    • E — Error
    • C — Critical

If no level is specified, Info is used by default.

So, to log function entry and exit without parameters or return value, you can simply write:

LogmePV(); // or LogmePVI()

Example: Basic Function Tracing

The following example demonstrates minimal setup for tracing a function:

#include <thread>
#include <chrono>

#include <Logme/Logme.h>

int MyProc(int i, const char* name)
{
  LogmePV();

  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  return i + 1;
}

int main()
{
  MyProc(123, "abc");
  return 0;
}

This will produce log entries showing when the function is entered and exited.

logging function enter/leave

Measuring Execution Time

To include execution time in the logs, enable the Duration flag on the logging channel:

int main()
{
  auto ch = Logme::Instance->GetDefaultChannelPtr();
  auto flags = ch->GetFlags();
  flags.Duration = true;
  ch->SetFlags(flags);

  MyProc(123, "abc");
  return 0;
}

Once enabled, the log output will also display how long each function call takes.

This feature becomes particularly valuable when analyzing performance bottlenecks or comparing different implementations.

logging enter/leave and execution time


Logging Return Values

To log the return value, use a macro without the V suffix and pass a variable that will store the result:

int MyProc(int i, const char* name)
{
  int ret{};
  LogmePW(ret);

  ret = i + 1;

  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  return ret;
}

This allows logme to capture and print the final value returned by the function.

logging return value


Logging Function Arguments

To include function arguments in the log, pass them via helper macros.

Values only: 

int MyProc(int i, const char* name)
{
  LogmePV(ARGS(i, name));

  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  return i + 1;
}

logging arument values

Names and values: 

int MyProc(int i, const char* name)
{
  LogmePV(ARGS2(i, name));

  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  return i + 1;
}

Macros like ARGS1, ARGS2, etc., indicate how many parameters are passed and allow logme to include both names and values in the output.

logging argument values with their names

Custom Printers for Complex Types

For standard types, logme already provides built-in formatting (called Printer methods). However, for user-defined types, you need to define your own formatter.

Here is an example:

struct Point
{
  int X;
  int Y;
};

template<> std::string FormatValue<Point>(const Point& value)
{
  return "Point(" + std::to_string(value.X) + ", " + std::to_string(value.Y) + ")";
}

static Point MakePoint(int x, int y)
{
  Point r{ x, y };
  LogmeP(r);

  return r;
}

By defining FormatValue, you enable logme to print meaningful representations of your custom types.


Selecting a Logging Channel

All previous examples use the default logging channel. If needed, you can explicitly specify a custom channel:

Logme::ID CHT{ "test" };

int MyProc(int i, const char* name)
{
  int ret{};
  LogmePI(ret, CHT, ARGS2(i, name));

  ret = i + 1;
  std::this_thread::sleep_for(std::chrono::milliseconds(100));

  return ret;
}

int main()
{
  auto ch = Logme::Instance->CreateChannel(CHT);
  ch->AddLink(::CH);

  MyProc(123, "abc");
  return 0;
}

Using a separate channel for function tracing is useful when this information is needed only occasionally.

A typical setup is to route tracing into a dedicated channel that is disabled by default, or is not created at all in the normal configuration. When deeper diagnostics become necessary, this channel can be created, enabled or unblocked, and function tracing starts producing output without changing the traced code itself.

This makes function tracing suitable for temporary investigation of control flow, unexpected latency, or difficult-to-reproduce bugs, while keeping normal logs smaller and quieter.

logging to a custom channel

Final Thoughts

The LogmeP macros provide a remarkably efficient way to introduce detailed function-level logging with minimal intrusion into your codebase.

Instead of manually writing logging statements at every entry and exit point, you gain:

  • automatic tracing,
  • optional parameter and return value logging,
  • execution time measurement,
  • flexible log levels,
  • and channel-based routing.

This approach scales particularly well in complex or multithreaded systems, where understanding execution flow is otherwise difficult and time-consuming.

By keeping the code concise and readable while significantly improving observability, logme helps reduce debugging time and simplifies long-term maintenance.


For more details, refer to the logme wiki documentation.

You may also find it useful to read the companion article comparing performance of leading logging libraries.

Leave a Reply