Log Tagging

Use this page to embed compile-time tags in log messages for categorization, filtering, and monitoring.

In addition to creating multiple Logger instances, each with a unique name, you can add static compile-time tags to your log messages. These tags are embedded directly into the log message format at compile time, providing zero runtime overhead for categorization. This enhances your ability to search, monitor, categorize, and understand events in your software.

These static tags are included as hashtag-style keywords within your log messages, making it easier to filter and categorize logs based on these predefined tags.

To include tags in your log statements, use the _TAGS macros. You will also need to include the %(tags) placeholder in PatternFormatterOptions for proper display of these tags.

 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

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");
}