Loggers

Use this page to understand how to create, retrieve, configure, and remove loggers.

Logger instances are created by users with a specified name, along with designated Sinks and a Formatter. Direct instantiation of logger objects is not possible; instead, they must first be created with the desired configurations.

Upon creation, users specify the Sinks and Formatter for the logger. The logger name serves as an identifier, allowing the same logger configuration to be reused without re-specifying Sinks and Formatter settings.

The Logger class is thread-safe.

Note

Due to the asynchronous design of the library, logger parameters are immutable after creation. To modify a logger (such as adding or removing sinks), you must remove it, wait for its removal, and then recreate it with the same name. Additionally, you should update any stored references to the logger*, as the newly created logger will have a different memory address.

Logger Creation

auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");

quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));

LOG_INFO(logger, "Hello from {}", "library foo");

Logger Retrieval

Loggers can be obtained using FrontendImpl::get_logger(), FrontendImpl::create_or_get_logger(), or FrontendImpl::create_logger().

  • create_logger creates a new logger and throws QuillError if the name is already taken.

  • create_or_get_logger creates a new logger if one does not already exist; otherwise, it returns the existing logger with the specified name. Note that when a logger with the given name already exists, the provided sinks, pattern, clock source, and other configuration parameters are ignored — the existing logger is returned as-is.

  • get_logger retrieves an existing logger by name.

quill::Logger* logger = quill::Frontend::get_logger("root");

Logger Removal

Loggers can be removed using FrontendImpl::remove_logger() or FrontendImpl::remove_logger_blocking(). These functions remove the logger asynchronously. Once a logger is removed, it becomes invalid and must no longer be used by any other thread. The backend ensures that all pending log statements from the logger are processed before final removal. After removal, a logger with the same logger_name can be recreated with different parameters.

FrontendImpl::remove_logger_blocking should only be used while the backend worker is running after Backend::start(), and must not be called from backend-thread callbacks because it waits for the backend worker to process the removal.

If you plan to use logger removal functions, ensure that no other threads continue using the logger after calling remove_logger() or FrontendImpl::remove_logger_blocking(). To avoid potential issues, it is recommended to create a separate logger for each application thread, giving each logger a unique logger_name. This ensures that when one thread removes its logger, it does not affect other threads.

When all loggers associated with a particular Sink are removed, the corresponding Sink instances are destroyed, and any associated files are closed automatically.

In many cases, it is sufficient to create a new logger rather than removing an old one. However, logger removal is particularly useful when the underlying sinks need to be destructed and files closed.

For example, if your server creates a logger for each connected TCP session and writes logs to a separate file for each session, removing the logger upon session disconnection ensures that the underlying file is closed — provided no other logger is sharing the same Sink.

Note

While the logger removal functions (FrontendImpl::remove_logger() and FrontendImpl::remove_logger_blocking()) are thread-safe, removing the same logger (`Logger*`) from multiple threads is not allowed. Ensure that a single thread is responsible for removing a particular logger to avoid undefined behavior.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/ConsoleSink.h"
#include "quill/sinks/FileSink.h"

#include <string>

// Stored logger pointer
quill::Logger* g_logger{nullptr};

int main()
{
  quill::BackendOptions backend_options;
  quill::Backend::start(backend_options);

  // Store created logger
  std::string const logger_name{"root"};
  g_logger = quill::Frontend::create_or_get_logger(
    logger_name, quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));

  LOG_INFO(g_logger, "A {} message with number {}", "log", 123);

  // Remove the logger
  // Note: remove_logger(g_logger) is not enough for this example. We must wait until the logger
  // is removed in order to re-create one with the same name (logger_name). The blocking removal
  // API should only be used while the backend worker is still running.
  quill::Frontend::remove_logger_blocking(g_logger);

  // Recreate a logger with the same name (logger_name)
  // Make sure you also update all references to the previous logger* (e.g. if stored as class
  // member) as the previous is now invalid after removal
  g_logger = quill::Frontend::create_or_get_logger(
    logger_name, quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"),
    quill::PatternFormatterOptions{"%(time) LOG_%(log_level:<9) %(message)"});

  LOG_INFO(g_logger, "A second {} message with number {}", 456, "log");
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/sinks/FileSink.h"

#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <utility>

/**
 * This example demonstrates the creation and removal of a logger object for each instance of the Session class.
 * Each session instance logs to a new log file using a unique logger.
 * When the session ends, the logger is removed, and the associated file is closed.
 * Additionally, it showcases the usage of the FileEventNotifier, which provides notifications for file changes.
 */

class Session
{
public:
  explicit Session(std::string const& unique_name)
  {
    // Set up a FileEventNotifier so we are notified on file changes
    quill::FileEventNotifier file_notifier;

    file_notifier.before_open = [](quill::fs::path const& filename)
    { std::cout << "file_notifier - preparing to open file " << filename << std::endl; };

    file_notifier.after_open = [](quill::fs::path const& filename, quill::FileEventNotifierHandle)
    { std::cout << "file_notifier - opened file " << filename << std::endl; };

    file_notifier.before_close = [](quill::fs::path const& filename, quill::FileEventNotifierHandle)
    { std::cout << "file_notifier - preparing to close file " << filename << std::endl; };

    file_notifier.after_close = [](quill::fs::path const& filename)
    { std::cout << "file_notifier - closed file " << filename << std::endl; };

    // Create a new log file for this session
    auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
      std::string{"session_"} + unique_name + ".log",
      []()
      {
        quill::FileSinkConfig cfg;
        cfg.set_open_mode('w');
        cfg.set_filename_append_option(quill::FilenameAppendOption::None);
        return cfg;
      }(),
      file_notifier);

    // Create a session specific logger for the current session
    _logger = quill::Frontend::create_or_get_logger(unique_name, std::move(file_sink));

    LOG_INFO(_logger, "Hello from session {}", unique_name);
  }

  ~Session()
  {
    // Remove the logger when the session is done. That will also remove the associated file_handler
    // and close the file as long as no other logger is using that file_handler
    quill::Frontend::remove_logger(_logger);
  }

private:
  quill::Logger* _logger{nullptr};
};

int main()
{
  // Start the backend thread
  quill::BackendOptions backend_options;
  quill::Backend::start(backend_options);

  {
    Session session_1 = Session{"SessionA"};
    std::this_thread::sleep_for(std::chrono::seconds{3});

    Session session_2 = Session{"SessionB"};
    std::this_thread::sleep_for(std::chrono::seconds{3});
  }
  std::this_thread::sleep_for(std::chrono::seconds{3});

  {
    Session session_3 = Session{"SessionC"};
    std::this_thread::sleep_for(std::chrono::seconds{3});
  }
  std::this_thread::sleep_for(std::chrono::seconds{3});

  Session session_4 = Session{"SessionD"};
  std::this_thread::sleep_for(std::chrono::seconds{3});
}

Note

In FileEventNotifier callbacks, use quill::FileEventNotifierHandle for the file handle parameter. On Windows this is a native HANDLE. On other platforms it is a FILE*.

before_write runs as part of normal log writing. before_open, after_open, before_close, and after_close run on whichever thread performs the file open/close operation. Different callbacks, and different invocations of the same callback, may therefore run on different threads over the sink lifetime. Callbacks must be thread-safe and must not assume a single calling thread.

Simplifying Logger Usage for Single Root Logger Applications

For some applications the use of the single root logger might be enough. In that case, passing the logger every time to the macro becomes inconvenient. The solution is to store the created Logger as a static variable and create your own macros. See example.