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 either FrontendImpl::get_logger() or FrontendImpl::create_or_get_logger(). The latter will create a new logger if one does not already exist; otherwise, it returns the existing logger with the specified 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.

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#include "quill/Backend.h"
 2#include "quill/Frontend.h"
 3#include "quill/LogMacros.h"
 4#include "quill/Logger.h"
 5#include "quill/sinks/ConsoleSink.h"
 6#include "quill/sinks/FileSink.h"
 7
 8#include <string>
 9
10// Stored logger pointer
11quill::Logger* g_logger{nullptr};
12
13int main()
14{
15  quill::BackendOptions backend_options;
16  quill::Backend::start(backend_options);
17
18  // Store created logger
19  std::string const logger_name{"root"};
20  g_logger = quill::Frontend::create_or_get_logger(
21    logger_name, quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"));
22
23  LOG_INFO(g_logger, "A {} message with number {}", "log", 123);
24
25  // Remove the logger
26  // Note:: remove_logger(g_logger) is not enough for this example. We must wait until the logger
27  // is removed in order to re-create one with the same name (logger_name)
28  quill::Frontend::remove_logger_blocking(g_logger);
29
30  // Recreate a logger with the same name (logger_name)
31  // Make sure you also update all references to the previous logger* (e.g. if stored as class
32  // member) as the previous is now invalid after removal
33  g_logger = quill::Frontend::create_or_get_logger(
34    logger_name, quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1"),
35    quill::PatternFormatterOptions{"%(time) LOG_%(log_level:<9) %(message)"});
36
37  LOG_INFO(g_logger, "A second {} message with number {}", 456, "log");
38}
 1#include "quill/Backend.h"
 2#include "quill/Frontend.h"
 3#include "quill/LogMacros.h"
 4#include "quill/Logger.h"
 5#include "quill/sinks/FileSink.h"
 6
 7#include <chrono>
 8#include <iostream>
 9#include <string>
10#include <thread>
11#include <utility>
12
13/**
14 * This example demonstrates the creation and removal of a logger object for each instance of the Session class.
15 * Each session instance logs to a new log file using a unique logger.
16 * When the session ends, the logger is removed, and the associated file is closed.
17 * Additionally, it showcases the usage of the FileEventNotifier, which provides notifications for file changes.
18 */
19
20class Session
21{
22public:
23  explicit Session(std::string const& unique_name)
24  {
25    // Set up a FileEventNotifier so we are notified on file changes
26    quill::FileEventNotifier file_notifier;
27
28    file_notifier.before_open = [](quill::fs::path const& filename)
29    { std::cout << "file_notifier - preparing to open file " << filename << std::endl; };
30
31    file_notifier.after_open = [](quill::fs::path const& filename, FILE*)
32    { std::cout << "file_notifier - opened file " << filename << std::endl; };
33
34    file_notifier.before_close = [](quill::fs::path const& filename, FILE*)
35    { std::cout << "file_notifier - preparing to close file " << filename << std::endl; };
36
37    file_notifier.after_close = [](quill::fs::path const& filename)
38    { std::cout << "file_notifier - closed file " << filename << std::endl; };
39
40    // Create a new log file for this session
41    auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
42      std::string{"session_"} + unique_name + ".log",
43      []()
44      {
45        quill::FileSinkConfig cfg;
46        cfg.set_open_mode('w');
47        cfg.set_filename_append_option(quill::FilenameAppendOption::None);
48        return cfg;
49      }(),
50      file_notifier);
51
52    // Create a session specific logger for the current session
53    _logger = quill::Frontend::create_or_get_logger(unique_name, std::move(file_sink));
54
55    LOG_INFO(_logger, "Hello from session {}", unique_name);
56  }
57
58  ~Session()
59  {
60    // Remove the logger when the session is done. That will also remove the associated file_handler
61    // and close the file as long as no other logger is using that file_handler
62    quill::Frontend::remove_logger(_logger);
63  }
64
65private:
66  quill::Logger* _logger{nullptr};
67};
68
69int main()
70{
71  // Start the backend thread
72  quill::BackendOptions backend_options;
73  quill::Backend::start(backend_options);
74
75  {
76    Session session_1 = Session{"SessionA"};
77    std::this_thread::sleep_for(std::chrono::seconds{3});
78
79    Session session_2 = Session{"SessionB"};
80    std::this_thread::sleep_for(std::chrono::seconds{3});
81  }
82  std::this_thread::sleep_for(std::chrono::seconds{3});
83
84  {
85    Session session_3 = Session{"SessionC"};
86    std::this_thread::sleep_for(std::chrono::seconds{3});
87  }
88  std::this_thread::sleep_for(std::chrono::seconds{3});
89
90  Session session_4 = Session{"SessionD"};
91  std::this_thread::sleep_for(std::chrono::seconds{3});
92}

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 everytime to the macro becomes inconvenient. The solution is to store the created Logger as a static variable and create your own macros. See example