JSON Logging

Use this page to output structured JSON logs to files or the console, optionally alongside standard pattern-formatted output.

The library supports outputting structured JSON logs to either the console or a file. To utilize this feature, use named placeholders in the format string (e.g., {name} instead of {}) when invoking the LOG_ macros.

For convenience, the LOGJ_ macros offer an alternative method for logging l-values, automatically including the argument names in the placeholders. These macros support up to 26 arguments.

For details on how named arguments work in Quill, including important differences from fmtlib’s fmt::arg(), see Named Placeholders in the Logging Macros guide.

It is also possible to combine JSON output with standard log pattern display by passing multiple Sink objects to a Logger. This allows you to see the same log message in different formats simultaneously.

Logging Json to Console

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

#include <string>

/**
 * This example shows structured JSON logging to the console using both the
 * `LOGJ_*` helpers and named placeholders in regular log statements.
 */

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

  // Frontend

  // Create a json sink
  auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonConsoleSink>("json_sink_1");

  // PatternFormatter is only used for non-structured logs formatting
  // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "json_logger", std::move(json_sink),
    quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});

  int var_a = 123;
  std::string var_b = "test";

  // Log via the convenient LOGJ_ macros
  LOGJ_INFO(logger, "A json message", var_a, var_b);

  // Or manually specify the desired names of each variable
  LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);

  for (uint32_t i = 0; i < 40; ++i)
  {
    // Will only log the message once per second
    LOG_INFO_LIMIT(std::chrono::seconds{1}, logger, "A json message with {var_1} and {var_2}", var_a, var_b);
    LOGJ_INFO_LIMIT(std::chrono::seconds{1}, logger, "A json message", var_a, var_b);

    if (i % 10 == 0)
    {
      std::this_thread::sleep_for(std::chrono::milliseconds{(i / 10) * 500});
    }
  }

  for (uint32_t i = 0; i < 20; ++i)
  {
    // Will only log the message once per N occurrences second
    LOG_INFO_LIMIT_EVERY_N(10, logger, "A json message with {var_1} and {occurrence}", var_a, i);
    LOGJ_INFO_LIMIT_EVERY_N(10, logger, "A json message", var_a, i);
  }
}

Logging Json to File

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

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

  // Create a json sink
  auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>("example_json.log",
                                                                            []()
                                                                            {
                                                                              quill::FileSinkConfig config;
                                                                              return config;
                                                                            }());

  // PatternFormatter is only used for non-structured logs formatting
  // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "json_logger", std::move(json_sink),
    quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});

  int var_a = 123;
  std::string var_b = "test";

  // Log via the convenient LOGJ_ macros
  LOGJ_INFO(logger, "A json message", var_a, var_b);

  // Or manually specify the desired names of each variable
  LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);
}

Customising Json Format

To customize the JSON format, define a custom sink that derives from one of the following classes:

 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/sinks/JsonSink.h"

#include <string>

/**
 * This example shows how to customize the JSON payload emitted by
 * `JsonConsoleSink`.
 */
class MyJsonConsoleSink : public quill::JsonConsoleSink
{
  void generate_json_message(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,
                             std::string_view /** log_message **/,
                             std::string_view /** log_statement **/, char const* message_format) override
  {
    // format json as desired
    _json_message.append(fmtquill::format(R"({{"timestamp":"{}","log_level":"{}","message":"{}")",
                                          std::to_string(log_timestamp), log_level_description, message_format));

    // add log statement arguments as key-values to the json
    if (named_args)
    {
      for (auto const& [key, value] : *named_args)
      {
        _json_message.append(std::string_view{",\""});
        _json_message.append(key);
        _json_message.append(std::string_view{"\":\""});
        _json_message.append(value);
        _json_message.append(std::string_view{"\""});
      }
    }
  }
};

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

  // Frontend

  // Create a json sink
  auto json_sink = quill::Frontend::create_or_get_sink<MyJsonConsoleSink>("json_sink_1");

  // PatternFormatter is only used for non-structured logs formatting
  // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
  quill::Logger* logger = quill::Frontend::create_or_get_logger(
    "json_logger", std::move(json_sink),
    quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});

  int var_a = 123;
  std::string var_b = "test";

  // Log via the convenient LOGJ_ macros
  LOGJ_INFO(logger, "A json message", var_a, var_b);

  // Or manually specify the desired names of each variable
  LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);
}

Combining JSON and Standard Log Patterns

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

#include <utility>

/**
 * This example shows how to write structured JSON logs to a file with `JsonFileSink`.
 * It also demonstrates a hybrid logger that writes JSON to a file and a traditional
 * text format to the console at the same time.
 *
 * For JSON logging, use named placeholders in the format string such as
 * `{method}` and `{endpoint}`.
 */

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

  // Frontend

  // Create a json file for output
  auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>(
    "example_json.log",
    []()
    {
      quill::FileSinkConfig cfg;
      cfg.set_open_mode('w');
      cfg.set_filename_append_option(quill::FilenameAppendOption::None);
      return cfg;
    }(),
    quill::FileEventNotifier{});

  // `JsonFileSink` produces its own structured output, so the normal text pattern is left empty
  // to avoid unnecessary formatting work.
  quill::Logger* json_logger = quill::Frontend::create_or_get_logger(
    "json_logger", std::move(json_sink),
    quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});

  for (int i = 0; i < 2; ++i)
  {
    LOG_INFO(json_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
  }

  // You can also create a logger that writes to both the JSON file and stdout, with
  // each sink using the format that makes sense for it.
  auto json_sink_2 = quill::Frontend::get_sink("example_json.log");
  auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink_id_1");

  // Define a custom format pattern for console logging, which includes named arguments in the output.
  // If you prefer to omit named arguments from the log messages, you can remove the "[%(named_args)]" part.
  quill::PatternFormatterOptions console_log_pattern = quill::PatternFormatterOptions{
    "%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<20) "
    "%(message) [%(named_args)]"};

  // Create a logger named "hybrid_logger" that writes to both a JSON sink and a console sink.
  // Note: The JSON sink uses its own internal format, so the custom format defined here
  // will only apply to the console output (via console_sink).
  quill::Logger* hybrid_logger = quill::Frontend::create_or_get_logger(
    "hybrid_logger", {std::move(json_sink_2), std::move(console_sink)}, console_log_pattern);

  for (int i = 2; i < 4; ++i)
  {
    LOG_INFO(hybrid_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
  }

  LOG_INFO(hybrid_logger, "Operation {name} completed with code {code}", "Update", 123,
           "Data synced successfully");
}