FCF 2.0 development in progress...
> > > >
[News] [C++ Libraries API] [C++ Downloads] [Donate to the project] [Contacts]

Logging System: Customizing Logger Prefixes

When monitoring test execution, raw log messages can sometimes be difficult to parse visually, especially in high-volume environments. The fcfTest logger allows you to enhance these messages by attaching Prefixes. A prefix can be a simple static string or a dynamic function that generates content on the fly.

The Prefix Structure

To add a prefix, you must configure a fcf::NTest::Logger::Prefix object. This structure contains several key properties:

  • name: A unique identifier for the prefix configuration.
  • category: A bitmask that determines which messages this prefix applies to.
  • multiLine: A boolean indicating if the prefix should be applied to every line of a multi-line message.
  • prefix: A static string to be prepended to the message.
  • handler: A callback function for dynamic prefix generation.

The Message Category Bitmask

One of the most powerful aspects of the fcfTest logging system is how it handles message categorization. Every log message is associated with a category, which is a 32-bit unsigned integer (unsigned int).

This integer is not just a random number; it is a structured bitmask divided into two distinct parts:

Bit Range Purpose
High 16 bits (0xFFFF0000) Message Group/Type. This defines the broad category (e.g., User Group, Test Group, or System Group).
Low 16 bits (0x0000FFFF) Message ID. This allows for specific sub-categorization within a group.

When the logger checks if a prefix should be applied, it performs a bitwise comparison. If the prefix's category is set to a broad group (like fcf::NTest::LMC_ALL), it matches everything. If it is set to a specific ID, it only matches messages with that exact ID.

Example check for prefix output:

const unsigned int hmask = 0xffff0000 & prefix.category; const unsigned int lmask = 0x0000ffff & prefix.category; if ( (messageCategory & hmask) && (lmask == 0 || lmask == (0x0000ffff & messageCategory)) ) { // Code to process the prefix goes here ... }

You can manually send messages to the log with a specified category using functions such as fcf::NTest::log(unsigned int a_messageCategory = fcf::NTest::LMC_USER_GROUP) and fcf::NTest::err(unsigned int a_messageCategory = fcf::NTest::LMC_USER_GROUP), which accept the message category number as their first argument.


Default Pre-configured Prefixes

By default, the logger comes with three pre-configured prefixes. These prefixes are set using the fcf::NTest::Logger::clearPrefixes method.

Prefix name Description
"test-offset" Adds an indentation ("    ") for all messages belonging to the fcf::NTest::LMC_TEST_GROUP group.
"user-offset" Adds an indentation ("  > ") for all messages belonging to the fcf::NTest::LMC_USER_GROUP group.
"case-offset" Adds an indentation (" == ") for all messages with the fcf::NTest::LMC_LAUNCH_CASE_START_MESSAGE category (start of processing a new test parameter).

Example 1: Static Prefix

The simplest way to use a prefix is to provide a static string. This is useful for adding indentation or simple labels like [LOG].

#define FCF_TEST_IMPLEMENTATION #include <fcfTest/test.hpp> #include <iostream> FCF_TEST_DEFINE("PrefixDemo", "Static", "SimplePrefixTest") { // Trigger a log message fcf::NTest::log() << "This is a simple message." << std::endl; FCF_TEST(true); } int main(int a_argc, char* a_argv[]) { // 1. Create a prefix object fcf::NTest::Logger::Prefix myPrefix; myPrefix.name = "indent"; myPrefix.prefix = "____"; // Apply to all messages myPrefix.category = fcf::NTest::LMC_ALL; myPrefix.multiLine = false; // 2. Register the prefix with the global logger fcf::NTest::logger().appendPrefix(myPrefix, true); bool error = false; fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error); return error ? 1 : 0; }

Terminal output:

____Performing the test: "PrefixDemo" -> "Static" -> "SimplePrefixTest" ... ____ > This is a simple message. ____ [SUCCESS] Test completed successfully (0.000`008`331 sec) ____ ____[SUCCESS] All tests were completed. ____Tests: 1 passed, 0 failed, 0 skipped, 1 total ____Duration: 0.000`008`331 sec

Example 2: Dynamic Prefix (Timestamp & Level)

For more advanced needs, you can use the handler property. This allows you to access the fcf::NTest::Logger::MessageContext and generate a prefix dynamically, such as including a timestamp or the current log level.

#define FCF_TEST_IMPLEMENTATION #include <fcfTest/test.hpp> #include <iostream> #include <ctime> #include <iomanip> #include <sstream> FCF_TEST_DEFINE("PrefixDemo", "Dynamic", "TimestampTest") { fcf::NTest::log() << "Dynamic content test." << std::endl; FCF_TEST(true); } int main(int a_argc, char* a_argv[]) { // 1. Create a prefix object fcf::NTest::Logger::Prefix dynamicPrefix; dynamicPrefix.name = "timestamp_level"; dynamicPrefix.category = fcf::NTest::LMC_ALL; dynamicPrefix.multiLine = true; // 2. Implement the handler callback // The handler receives the logger and the message context dynamicPrefix.handler = [](fcf::NTest::Logger&, fcf::NTest::Logger::MessageContext& a_context) -> std::string { // Get current time auto now = std::time(nullptr); auto localTime = std::localtime(&now); // Convert log level to string using the built-in utility std::string levelStr = fcf::NTest::Logger::toLevelStr(a_context.level); // Format the prefix: [HH:MM:SS | level] std::stringstream ss; ss << "[" << std::put_time(localTime, "%H:%M:%S") << " | " << levelStr << "] "; return ss.str(); }; // 3. Register the prefix fcf::NTest::logger().appendPrefix(dynamicPrefix, true); bool error = false; fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error); return error ? 1 : 0; }

Terminal output:

[04:16:20 | log] Performing the test: "PrefixDemo" -> "Dynamic" -> "TimestampTest" ... [04:16:20 | log] > Dynamic content test. [04:16:20 | log] [SUCCESS] Test completed successfully (0.000`033`497 sec) [04:16:20 | log] [04:16:20 | log] [SUCCESS] All tests were completed. [04:16:20 | log] Tests: 1 passed, 0 failed, 0 skipped, 1 total [04:16:20 | log] Duration: 0.000`033`497 sec

Example 3: Category-Based Filtering

You can use the bitmask system to ensure a prefix only appears for specific types of messages. In this example, we create a custom category by combining a group with a specific ID and apply a prefix only to that category.

#define FCF_TEST_IMPLEMENTATION #include <fcfTest/test.hpp> #include <iostream> // Define a custom category: User Group + ID 0x000A const unsigned int MY_CUSTOM_CMD = fcf::NTest::LMC_USER_GROUP | 0x000A; FCF_TEST_DEFINE("PrefixDemo", "Filter", "CategoryTest") { // 1. This message belongs to the default User Group (no specific ID) // It will NOT trigger the custom prefix fcf::NTest::log() << "Standard user message." << std::endl; // 2. This message uses our custom category // It WILL trigger the custom prefix fcf::NTest::log(MY_CUSTOM_CMD) << "Special command executed!" << std::endl; FCF_TEST(true); } int main(int a_argc, char* a_argv[]) { // 1. Create a prefix that only targets our custom category fcf::NTest::Logger::Prefix cmdPrefix; cmdPrefix.name = "cmd"; cmdPrefix.category = MY_CUSTOM_CMD; cmdPrefix.prefix = "[CMD] "; cmdPrefix.multiLine = false; // 2. Register the prefix fcf::NTest::logger().appendPrefix(cmdPrefix); bool error = false; fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error); return error ? 1 : 0; }

Terminal output:

Performing the test: "PrefixDemo" -> "Filter" -> "CategoryTest" ... > Standard user message. > [CMD] Special command executed! [SUCCESS] Test completed successfully (0.000`035`770 sec) [SUCCESS] All tests were completed. Tests: 1 passed, 0 failed, 0 skipped, 1 total Duration: 0.000`035`770 sec