Skip to content

2. Handlers

Odysseas Georgoudis edited this page Oct 28, 2022 · 15 revisions

Handlers

Handlers are the objects that actually write the log to their target.

A Handler object is never instantiated directly, this class acts as a base class of different handler derived classes e.g. StreamHandler, FileHandler

Each handler is responsible for only single target (e.g file, console, db), and each one owns a formatter object.

Each handler have its own instance of a formatter object which formats the messages to its destination.

Upon the handler creation, the handler object is registered and owned by a central manager object the HandlerCollection

For files, one handler is created per filename. For stdout and stderr a default handler for each one is always created during initialisation. It is possible for the user to create multiple stdout or stderr handles by providing a unique id per handle.

When creating a custom logger one or more handlers for this logger can be specified. This can only be done only the logger creation.

Creating a handler object to a file

#include "quill/Quill.h"
quill::Handler* fhandler = quill::file_handler("a_filename.log", "w");

The above code creates a new FileHandler capable of delivery log records to the file "a_filename.log"

Reusing an existing handler object

It is possible to reuse the same handle object in multiple loggers for example all logger objects will be writing to the same file.
The following code is also thread-safe.

  // The first time this function is called a file handler is created for this filename.
  // Calling the function with the same filename will return the existing handler
  quill::Handler* file_handler = quill::file_handler(filename, "w");

  // Create a logger using this handler
  quill::Logger* logger_foo = quill::create_logger("logger_foo", file_handler);

  // Because a handler already created for this filename a pointer to the existing handler is returned
  quill::Handler* file_handler_2 = quill::file_handler(filename, "w");

  // Create a new logger using this handler
  quill::Logger* logger_bar = quill::create_logger("logger_bar", file_handler_2);

Creating multiple stdout or stderr handler objects

While when operating to files, only one handle object can be created per file name, this is not the case for stdout or stderr. It is possible to create multiple handlers to stdout or stderr by providing a unique name.

This is useful for when you want to have different loggers writing to stdout using a different format. (also see example_stdout_multiple_formatters)

  // Get the stdout file handler, with a unique name
  quill::Handler* stdout_handler_1 = quill::stdout_handler("stdout_1");

  stdout_handler_1->set_pattern(
    "%(ascii_time) [%(process)] [%(thread)] LOG_%(level_name) %(logger_name) - %(message)", // message format
    "%D %H:%M:%S.%Qms %z",     // timestamp format
    quill::Timezone::GmtTime); // timestamp's timezone

  quill::Logger* logger_foo = quill::create_logger("logger_foo", stdout_handler_1);

  // Get the stdout file handler, with another unique name
  quill::Handler* stdout_handler_2 = quill::stdout_handler("stdout_2");

  stdout_handler_2->set_pattern("%(ascii_time) LOG_%(level_name) %(logger_name) - %(message)", // message format
                                "%D %H:%M:%S.%Qms %z",     // timestamp format
                                quill::Timezone::GmtTime); // timestamp's timezone

  quill::Logger* logger_bar = quill::create_logger("logger_bar", stdout_handler_2);

Handler Types

The following useful handlers are provided in the library.

A different pattern can be set to the handlers by calling set_pattern(...). See Formatters

ConsoleHandler

The ConsoleHandler class sends logging output to streams stdout or stderr. Printing colour codes to terminal or windows console is also supported.

  Handler* stdout_handler(std::string const& stdout_handler_name = std::string{"stdout"}, 
                          ConsoleColours const& console_colours = ConsoleColours{});

Obtains a handler to `stdout.

  Handler* stderr_handler(std::string const& stderr_handler_name = std::string{"stderr"});

Obtains a handler to stderr.

set_pattern(...) can be used to set the formatting pattern on each pattern. stdout_handler_name and stderr_handler_name can be used in case multiple formatting to stdout or stderr is required. See example

By default no colour codes are printed to the terminal unless it is enabled. For more advanced example See example

  // Enable console colours on the default constructed handler to stdout before calling quill:start()
  quill::Config cfg;
  cfg.enable_console_colours = true;
  quill::configure(cfg);

  // Start the logging backend thread
  quill::start();

  // The default logger and any created logger that prints to the console will also print colour codes.

FileHandler

  Handler* file_handler(filename_t const& filename, 
                        std::string const& mode = std::string{"a"}, 
                        FilenameAppend append_to_filename = FilenameAppend::None);

Creates a new instance of the FileHandler class or looks up an existing instance. If the file is already opened the existing handler for this file is returned instead.
append_to_filename can be used when the file handler is created to append additional text to the filename. e.g. FilenameAppend::Date will append the date to the file name.

  // Start the backend logging thread
  quill::start();

  // Create a new file handler
  quill::Handler* file_handler = quill::file_handler(filename, "w");

  // Create a logger using this handler
  quill::Logger* logger_foo = quill::create_logger("logger_foo", file_handler);

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

RotatingFileHandler

  Handler* rotating_file_handler(filename_t const& base_filename, 
                                 std::string const& mode = std::string{"a"}, 
                                 size_t max_bytes = 0, 
                                 uint32_t backup_count = 0);

Creates a new instance of the RotatingFileHandler class or looks up an existing instance. If a rotating file handler with base_filename exists then the existing instance is returned. If a rotating file handler does not exist then the specified file is opened and used as the stream for logging.

By default, the file grows indefinitely. You can use the max_bytes and backup_count values to allow the file to rollover at a predetermined size. When the size is about to be exceeded, the file is closed and a new file is silently opened for output. Rollover occurs whenever the current log file is nearly max_bytes in length; but if either of max_bytes or backup_count is zero, rollover never occurs, so you generally want to set backup_count to at least 1, and have a non-zero maxBytes.

When backup_count is non-zero, the system will save old log files by appending the extensions ‘.1’, ‘.2’ etc., to the filename.

For example, with a backup_count of 5 and a base_filename of app.log, you would get app.log, app.1.log, app.2.log, up to app.5.log. The file being written to is always app.log. When this file is filled, it is closed and renamed to app.1.log, and if files app.1.log, app.2.log, etc. exist, then they are renamed to app.2.log, app.3.log etc. respectively.

  // Start the backend logging thread
  quill::start();

  // Create a rotating file handler with a max file size per log file and maximum rotation up to 5 times
  quill::Handler* file_handler = quill::rotating_file_handler(base_filename, "w", 1024, 5);

  // Create a logger using this handler
  quill::Logger* logger_bar = quill::create_logger("rotating", file_handler);

  for (uint32_t i = 0; i < 15; ++i)
  {
    LOG_INFO(logger_bar, "Hello from {} {}", "rotating logger", i);
  }

  // Get an instance to the existing rotating file handler
  quill::Handler* file_handler = quill::rotating_file_handler(base_filename);

TimeRotatingFileHandler

  Handler* time_rotating_file_handler(filename_t const& base_filename, 
                                      std::string const& mode = std::string{"a"},
                                      std::string const& when = std::string{"H"}, 
                                      uint32_t interval = 1, 
                                      uint32_t backup_count = 0,
                                      Timezone timezone = Timezone::LocalTime, 
                                      std::string const& at_time = std::string{"00:00"});

Creates a new instance of the TimeRotatingFileHandler class or looks up an existing instance.

The specified file is opened and used as the stream for logging. Rotating happens based on the product of when and interval. You can use the when to specify the type of interval. when can have the values of "H", "M" or "daily".
If "daily" is passed, then interval is ignored.

The system will save old log files by appending extensions to the filename. The extensions are date-and-time based in the format of %Y-%m-%d_%H-%M-%S. If the timezone argument is GmtTime, times in UTC will be used; otherwise local time is used

At most backup_count files are be kept, and if more would be created when rollover occurs, the oldest one is deleted.

at_time argument specifies the time of day when rollover occurs. This is only used when 'daily' is passed. It must be in the format HH:MM.

For example to create a log file that rotates daily at 02:00

  // Start the backend logging thread
  quill::start();

  // Create a rotating file handler which rotates daily at 02:00
  quill::Handler* file_handler =
    quill::time_rotating_file_handler(filename, "w", "daily", 1, 10, Timezone::LocalTime, "02:00");

  // Create a logger using this handler
  quill::Logger* logger_bar = quill::create_logger("daily_logger", file_handler);

  LOG_INFO(logger_bar, "Hello from {}", "daily logger");

Or to create a log file that rotates every 1 hour

  // Start the backend logging thread
  quill::start();

  // Create a rotating file handler which rotates every one hour and keep maximum 24 files
  quill::Handler* file_handler =
    quill::time_rotating_file_handler(filename, "w", "H", 24, 10);

  // Create a logger using this handler
  quill::Logger* logger_bar = quill::create_logger("daily_logger", file_handler);

  LOG_INFO(logger_bar, "Hello from {}", "daily logger");