Project

General

Profile

How to: Let loggers from different threads log to different files

Since commit f92e29 (06.02.2026) it is possible to let different threads in DCMTK log to different output files. This is accomplished using a feature called MDC (Mapped Diagnostic Context). It works by adding MDC key/value pairs to the (logging) context of a thread and then use an MDC-based filter (that performs matching on these values) to determine where a specific log event should go or not. Depending on how the loggers are configured, the filtering is not only applied to log events that come from the new code, but also log events that are in called code. For example, if you write a network server "MyServer" based on (i.e. usually derived from or calling) the DcmSCP class which internally logs many messages to the dcmtk.dcmnet logger, one can also filter the existing messages sent to dcmtk.dcmnet logger in the preferred way, i.e. sending them together with the log events from MyServer to the same log file.

Below is a code snippet that demonstrates the basic MDC usage.

In each thread that should be enabled for MDC filtering, add the desired key/value pair(s) that are required. This should be done before the first log event is created by that thread (or code it uses/calls), i.e. most of the time in the constructor. Note that when the thread ends, you also must clean up the MDC info on the class instance.

// ------------------ Setup thread -------------------------
// --------------------------------------------------------
#include "dcmtk/oflog/mdc.h" 
...
// Before first log event (e.g. Constructor)
dcmtk::log4cplus::getMDC().put("service", "worklist");
....
// After last log event (e.g. Destructor)
dcmtk::log4cplus::getMDC().clear();
...

If your goal is to send events to different log files based on MDC info, you can create two different log appenders (here: RollingFileAppender) and configure a related MDC filter on them.
The method shown takes the desired log file name and a service name.

// ------------------ Setup logging framework -------------------------
// --------------------------------------------------------------------
#include "dcmtk/oflog/mdc.h" 
...
dcmtk::log4cplus::SharedAppenderPtr
Configuration::create_service_appender(const std::string& filename,
                        const std::string& service)
{
    using namespace dcmtk::log4cplus;
    using namespace dcmtk::log4cplus::spi;

    // RollingFileAppender
    SharedAppenderPtr appender(
        new RollingFileAppender(
            filename.c_str(),
            10 * 1024 * 1024, // 10 MB
            5                // backups
        )
    );
    std::string appender_name = service + std::string("_APPENDER");
    appender->setName(appender_name.c_str());

    // MDCMatchFilter(service == <service>)
    dcmtk::log4cplus::helpers::Properties props;
    props.setProperty(DCMTK_LOG4CPLUS_TEXT("MDCKeyToMatch"), DCMTK_LOG4CPLUS_TEXT("service"));
    props.setProperty(DCMTK_LOG4CPLUS_TEXT("MDCValueToMatch"), DCMTK_LOG4CPLUS_TEXT(service.c_str()));
    props.setProperty(DCMTK_LOG4CPLUS_TEXT("AcceptOnMatch"), DCMTK_LOG4CPLUS_TEXT("true"));
    FilterPtr mdcFilter(new MDCMatchFilter(props));

    appender->setLayout(
    OFunique_ptr<Layout>(new PatternLayout(
        DCMTK_LOG4CPLUS_TEXT("%D [%t] %X{service} %-5p %c - %m%n")
    )));

    // Deny everything else, i.e. all other log events will not go through this filter
    FilterPtr denyAll(new DenyAllFilter());

    mdcFilter->next = denyAll.get();
    appender->setFilter(OFmove(mdcFilter));

    return appender;
}
...
// To use this method to configure two appenders, one for storage and one for worklist,
// you can use something like the following:

    using namespace dcmtk::log4cplus;
    using namespace dcmtk::log4cplus::spi;
    Logger root = Logger::getRoot();

    if (m_enable_debug_logging)
    {
        root.setLogLevel(DEBUG_LOG_LEVEL);
    }
    else
    {
        root.setLogLevel(INFO_LOG_LEVEL);
    }

    // Worklist appender
    SharedAppenderPtr wlm_appender = create_service_appender(m_wlm_log_file,    "worklist");
    if (!wlm_appender)
        return false;
    root.addAppender(wlm_appender);

    // Storage appender
    SharedAppenderPtr storage_appender = create_service_appender(m_storage_log_file, "storage");
    if (!storage_appender)
        return false;
    root.addAppender(storage_appender);

The logging configuration code is usually located to be executed on program startup. The code that attaches MDC information to the threads should be executed on thread creation (usually).

Note that all code snippets might need extra headers or variables to work and are only put together for demonstration purposes.