Log Tagging

Use this page to add static or dynamic tags to log messages for categorization, filtering, and monitoring.

Compile-Time Tags

The _TAGS macros store their tags in the call site’s static metadata. This avoids copying tag text with every record and is the preferred path when the tags are known at compile time.

To include these tags in the output, add the %(tags) placeholder to PatternFormatterOptions.

 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
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/Utility.h"
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
#include <string>
#include <string_view>
#include <utility>

#define TAG_1 "foo"
#define TAG_2 "bar"
#define TAG_3 "baz"

/**
 * This example shows how to attach tags to log statements and include them in
 * the logger pattern with the `%(tags)` placeholder.
 */

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

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

  // Ensure the logging pattern includes the %(tags) placeholder to display custom tags.
  // It is crucial to place %(tags) immediately before the next attribute without any intervening spaces.
  // This ensures correct formatting when no tags are present, preventing unwanted gaps in the output.

  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "root", std::move(console_sink),
    quill::PatternFormatterOptions{
      "%(time) [%(thread_id)] %(short_source_location:<28) %(log_level:<9) "
      "%(tags)%(message)",
      "%Y-%m-%d %H:%M:%S.%Qms", quill::Timezone::GmtTime});

  LOG_INFO_TAGS(logger, TAGS("random"), "Debug with tags");
  LOG_INFO_TAGS(logger, TAGS(TAG_2), "Info with tags");
  LOG_WARNING_TAGS(logger, TAGS(TAG_1, TAG_2), "Warning with tags");
  LOG_ERROR_TAGS(logger, TAGS(TAG_1, TAG_2, TAG_3), "Info with tags");

  LOG_INFO(logger, "Without tags");
}

Output:

2024-08-11 01:23:44.463 [46228] tags_logging.cpp:40          INFO      #random Debug with tags
2024-08-11 01:23:44.463 [46228] tags_logging.cpp:41          INFO      #bar Info with tags
2024-08-11 01:23:44.463 [46228] tags_logging.cpp:42          WARNING   #foo #bar Warning with tags
2024-08-11 01:23:44.463 [46228] tags_logging.cpp:43          ERROR     #foo #bar #baz Info with tags
2024-08-11 01:23:44.463 [46228] tags_logging.cpp:45          INFO      Without tags

Dynamic Tags

For tags selected at runtime, the macro-free API accepts a Tags object built from char const*, std::string, or std::string_view values. The values are copied into the Tags object, so a string view does not need to be null-terminated and its source only needs to remain valid while the Tags constructor runs.

std::string execution_tag = current_execution_tag();
quill::info(logger, quill::Tags{execution_tag}, "Task resumed");

Macro-free logging uses the hybrid runtime-metadata path, which copies the combined tags into the frontend queue before the call returns. The QUILL_LOG_RUNTIME_METADATA_DEEP and QUILL_LOG_RUNTIME_METADATA_HYBRID macros can likewise carry dynamic null-terminated tag strings. QUILL_LOG_RUNTIME_METADATA_SHALLOW only stores the tag pointer, so that string must remain valid and immutable until the backend consumes the record.

The regular LOG_<LEVEL>_TAGS macros remain the lowest-latency choice for static tags; runtime std::string and std::string_view values cannot be stored in their compile-time metadata.

Backend Tag Processing

Set PatternFormatterOptions::process_tags to transform the combined tag text on the backend before it is inserted into %(tags). The callback receives a valid null-terminated string and returns the text to display. See Formatters for an example.

Tag Processing in Sinks

Tags can be accessed at the Sink level and used for additional log processing or filtering. This enables more log handling based on tag content. For example:

 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
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/Utility.h"
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
#include <iostream>
#include <string>
#include <string_view>
#include <utility>

#define TAG_1 "foo"
#define TAG_2 "bar"
#define TAG_3 "baz"

class CustomSink final : public quill::Sink
{
public:
  CustomSink() = default;

  /***/
  void write_log(quill::MacroMetadata const* log_metadata, uint64_t /** log_timestamp **/,
                 std::string_view /** thread_id **/, std::string_view /** thread_name **/,
                 std::string const& /** process_id **/, std::string_view /** logger_name **/,
                 quill::LogLevel /** log_level **/, std::string_view /** log_level_description **/,
                 std::string_view /** log_level_short_code **/,
                 std::vector<std::pair<std::string, std::string>> const* /** named_args - only populated when named args in the format placeholder are used **/,
                 std::string_view /** log_message **/, std::string_view log_statement) override
  {
    if (log_metadata->tags())
    {
      std::cout << "Message tags: " << log_metadata->tags() << std::endl;
    }

    std::cout << log_statement << std::endl;
  }

  /***/
  void flush_sink() noexcept override {}

  /***/
  void run_periodic_tasks() noexcept override {}
};

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

  auto sink = quill::Frontend::create_or_get_sink<CustomSink>("sink_id_1");

  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "root", std::move(sink),
    quill::PatternFormatterOptions{
      "%(time) [%(thread_id)] %(short_source_location:<28) %(log_level:<9) "
      "%(message)",
      "%Y-%m-%d %H:%M:%S.%Qms", quill::Timezone::GmtTime});

  LOG_INFO_TAGS(logger, TAGS("random"), "Debug with tags");
  LOG_INFO_TAGS(logger, TAGS(TAG_2), "Info with tags");
  LOG_WARNING_TAGS(logger, TAGS(TAG_1, TAG_2), "Warning with tags");
  LOG_ERROR_TAGS(logger, TAGS(TAG_1, TAG_2, TAG_3), "Info with tags");

  LOG_INFO(logger, "Without tags");
}