While the default behavior of fcfTest is to output logs to the standard console, real-world testing scenarios often demand more. You might need to simultaneously stream human-readable logs to the terminal while generating machine-readable junit XML files for your CI/CD pipeline. Or perhaps you want to log high-verbosity debug information to a dedicated file without cluttering the console.
Understanding Output Targets
Each individual logger output stream is described by the fcf::NTest::Logger::OutputTarget object.
An fcf::NTest::Logger::OutputTarget is a configuration object that defines a single destination for log messages. It contains four essential properties:
- stream: A pointer to an
std::ostream (e.g., std::cout, std::cerr, or a file stream).
- name: A unique identifier for this target.
- format: The name of the formatter to use for this specific target. If an empty string is specified, the format set globally for the logger is used.
- level: The logging level threshold for this specific output target. If the property value is equal to fcf::NTest::LL_DEF, the logging level set in the logger object (via the fcf::NTest::Logger::level method) is used.
Output Target Names
Each target in the logging system must have a unique name within the logger object. Adding a new target is performed by calling fcf::NTest::Logger::appendTarget. If a target with the same name is already registered, it will be overwritten.
By default, the system always registers at least one target named "default", which directs output to the console.
Additionally, if you configure file output via command-line arguments or by setting the fcf::NTest::Options::files property for fcf::NTest::cmdRun, the system automatically creates targets named "file-[FORMAT]".
Programmatic Multi-Targeting
You can register multiple targets using the appendTarget method. Once registered, the logger will broadcast every log message to every active target. This is how you achieve simultaneous output to different streams.
The following example demonstrates how to configure a logger to output standard messages to the console while simultaneously writing a JUnit XML report to a file.
#define FCF_TEST_IMPLEMENTATION
#include <fcfTest/test.hpp>
#include <iostream>
#include <fstream>
// A simple test to trigger logging
FCF_TEST_DEFINE("TargetDemo", "Logger", "MultiTargetTest") {
fcf::NTest::log() << "This message goes to both streams!" << std::endl;
FCF_TEST(true);
}
int main(int a_argc, char* a_argv[]) {
fcf::NTest::Logger& logger = fcf::NTest::logger();
// 1. Prepare a file stream for the XML report
// We use 'static' to ensure the stream remains valid during the entire test run
static std::ofstream fileStream("test_results.xml");
// 2. Configure a target for the standard console output
fcf::NTest::Logger::OutputTarget consoleTarget;
consoleTarget.name = "default";
consoleTarget.stream = &std::cout;
// Using "default" format for the terminal
consoleTarget.format = "default";
// 3. Configure a target for the file output
fcf::NTest::Logger::OutputTarget fileTarget;
fileTarget.name = "my-junit-file";
fileTarget.stream = &fileStream;
// Specifying "junit" format for the XML file
fileTarget.format = "junit";
// 4. Register both targets
// The logger will now broadcast every log event to both destinations
logger.appendTarget(consoleTarget);
logger.appendTarget(fileTarget);
bool error = false;
fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error);
return error ? 1 : 0;
}
Terminal Output:
Performing the test: "TargetDemo" -> "Logger" -> "MultiTargetTest" ...
> This message goes to both streams!
[SUCCESS] Test completed successfully (0.000`005`123 sec)
[SUCCESS] All tests were completed.
Tests: 1 passed, 0 failed, 0 skipped, 1 total
Duration: 0.000`005`123 sec
File Content (test_results.xml):
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="1" failure="0" skipped="0" time="0.000005123">
<testsuite name="TargetDemo/Logger" tests="1" failure="0" skipped="0" time="0.000005123">
<testcase classname="TargetDemo/Logger" name="MultiTargetTest" time="0.000005123"/>
</testsuite>
</testsuites>
Controlling the Default Format
Every fcf::NTest::Logger::OutputTarget has a format property. This property determines which formatter is used for that specific stream. However, you don't always have to specify it manually.
If you leave the format as an empty string, the target will automatically inherit the global default format currently set in the logger's environment.
The global default format can be established in two ways:
1. Via Command Line
This is the most convenient method for CI/CD pipelines. Use the --test-format flag to change the default for all targets that haven't specified their own format:
# All targets without a specific format will now use JUnit
$ ./my_tests --test-format junit
2. Via Programmatic Configuration
If you want to hardcode a default format within your application, you can configure the fcf::NTest::Options structure before running the tests:
fcf::NTest::Options options;
// Set the global default to junit
options.format = "junit";
bool error = false;
// The runner will apply this format to all targets with an empty format string
fcf::NTest::cmdRun(options, a_argc, a_argv, fcf::NTest::CRM_RUN, &error);
Summary of Format Resolution
When a log message is sent to a target, the logger follows this logic to decide the format:
- Check if the fcf::NTest::Logger::OutputTarget has a non-empty format string. If yes, use that specific format.
- If the target's format is empty, use the format specified in the fcf::NTest::Options passed to cmdRun.
- If no options were provided, use the format currently set in the fcf::NTest::Logger singleton.