JSON Logging¶
The library supports outputting JSON-structured logs to either the console or a file. To utilize this feature, you need to use named arguments within the format string, specifically inside the {}
placeholders, 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 20 arguments.
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#include "quill/Backend.h"
2#include "quill/Frontend.h"
3#include "quill/LogMacros.h"
4#include "quill/Logger.h"
5#include "quill/sinks/JsonSink.h"
6
7#include <string>
8
9int main()
10{
11 // Start the backend thread
12 quill::BackendOptions backend_options;
13 quill::Backend::start(backend_options);
14
15 // Frontend
16
17 // Create a json sink
18 auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonConsoleSink>("json_sink_1");
19
20 // PatternFormatter is only used for non-structured logs formatting
21 // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
22 quill::Logger* logger = quill::Frontend::create_or_get_logger(
23 "json_logger", std::move(json_sink),
24 quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});
25
26 int var_a = 123;
27 std::string var_b = "test";
28
29 // Log via the convenient LOGJ_ macros
30 LOGJ_INFO(logger, "A json message", var_a, var_b);
31
32 // Or manually specify the desired names of each variable
33 LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);
34
35 for (uint32_t i = 0; i < 40; ++i)
36 {
37 // Will only log the message once per second
38 LOG_INFO_LIMIT(std::chrono::seconds{1}, logger, "A json message with {var_1} and {var_2}", var_a, var_b);
39 LOGJ_INFO_LIMIT(std::chrono::seconds{1}, logger, "A json message", var_a, var_b);
40
41 if (i % 10 == 0)
42 {
43 std::this_thread::sleep_for(std::chrono::milliseconds{(i / 10) * 500});
44 }
45 }
46
47 for (uint32_t i = 0; i < 20; ++i)
48 {
49 // Will only log the message once per N occurrences second
50 LOG_INFO_LIMIT_EVERY_N(10, logger, "A json message with {var_1} and {occurrence}", var_a, i);
51 LOGJ_INFO_LIMIT_EVERY_N(10, logger, "A json message", var_a, i);
52 }
53}
Logging Json to File¶
1#include "quill/Backend.h"
2#include "quill/Frontend.h"
3#include "quill/LogMacros.h"
4#include "quill/Logger.h"
5#include "quill/sinks/JsonSink.h"
6#include <string>
7
8int main()
9{
10 // Start the backend thread
11 quill::BackendOptions backend_options;
12 quill::Backend::start(backend_options);
13
14 // Create a json sink
15 auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>("example_json.log",
16 []()
17 {
18 quill::FileSinkConfig config;
19 return config;
20 }());
21
22 // PatternFormatter is only used for non-structured logs formatting
23 // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
24 quill::Logger* logger = quill::Frontend::create_or_get_logger(
25 "json_logger", std::move(json_sink),
26 quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});
27
28 int var_a = 123;
29 std::string var_b = "test";
30
31 // Log via the convenient LOGJ_ macros
32 LOGJ_INFO(logger, "A json message", var_a, var_b);
33
34 // Or manually specify the desired names of each variable
35 LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);
36}
Customising Json Format¶
To customize the JSON format, define a custom sink that derives from one of the following classes:
RotatingJsonFileSink
1#include "quill/Backend.h"
2#include "quill/Frontend.h"
3#include "quill/LogMacros.h"
4#include "quill/Logger.h"
5#include "quill/sinks/JsonSink.h"
6
7#include <string>
8
9/**
10 * Overrides generate_json_message to use a custom json format
11 */
12class MyJsonConsoleSink : public quill::JsonConsoleSink
13{
14 void generate_json_message(quill::MacroMetadata const* /** log_metadata **/, uint64_t log_timestamp,
15 std::string_view /** thread_id **/, std::string_view /** thread_name **/,
16 std::string const& /** process_id **/, std::string_view /** logger_name **/,
17 quill::LogLevel /** log_level **/, std::string_view log_level_description,
18 std::string_view /** log_level_short_code **/,
19 std::vector<std::pair<std::string, std::string>> const* named_args,
20 std::string_view /** log_message **/,
21 std::string_view /** log_statement **/, char const* message_format) override
22 {
23 // format json as desired
24 _json_message.append(fmtquill::format(R"({{"timestamp":"{}","log_level":"{}","message":"{}")",
25 std::to_string(log_timestamp), log_level_description, message_format));
26
27 // add log statement arguments as key-values to the json
28 if (named_args)
29 {
30 for (auto const& [key, value] : *named_args)
31 {
32 _json_message.append(std::string_view{",\""});
33 _json_message.append(key);
34 _json_message.append(std::string_view{"\":\""});
35 _json_message.append(value);
36 _json_message.append(std::string_view{"\""});
37 }
38 }
39 }
40};
41
42int main()
43{
44 // Start the backend thread
45 quill::BackendOptions backend_options;
46 quill::Backend::start(backend_options);
47
48 // Frontend
49
50 // Create a json sink
51 auto json_sink = quill::Frontend::create_or_get_sink<MyJsonConsoleSink>("json_sink_1");
52
53 // PatternFormatter is only used for non-structured logs formatting
54 // When logging only json, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
55 quill::Logger* logger = quill::Frontend::create_or_get_logger(
56 "json_logger", std::move(json_sink),
57 quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});
58
59 int var_a = 123;
60 std::string var_b = "test";
61
62 // Log via the convenient LOGJ_ macros
63 LOGJ_INFO(logger, "A json message", var_a, var_b);
64
65 // Or manually specify the desired names of each variable
66 LOG_INFO(logger, "A json message with {var_1} and {var_2}", var_a, var_b);
67}
Combining JSON and Standard Log Patterns¶
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/JsonSink.h"
7
8#include <utility>
9
10/**
11 * This example showcases the usage of the JsonFileSink to generate JSON-formatted logs.
12 * Additionally, it demonstrates how to simultaneously log in both the standard logger output
13 * format, e.g., to console and the corresponding JSON format to a JSON output sink.
14 *
15 * For successful JSON logging, it's essential to use named placeholders within the provided
16 * format string, such as "{method}" and "{endpoint}".
17 */
18
19int main()
20{
21 // Start the backend thread
22 quill::BackendOptions backend_options;
23 quill::Backend::start(backend_options);
24
25 // Frontend
26
27 // Create a json file for output
28 auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>(
29 "example_json.log",
30 []()
31 {
32 quill::FileSinkConfig cfg;
33 cfg.set_open_mode('w');
34 cfg.set_filename_append_option(quill::FilenameAppendOption::None);
35 return cfg;
36 }(),
37 quill::FileEventNotifier{});
38
39 // When using the JsonFileSink, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
40 quill::Logger* json_logger = quill::Frontend::create_or_get_logger(
41 "json_logger", std::move(json_sink),
42 quill::PatternFormatterOptions{"", "%H:%M:%S.%Qns", quill::Timezone::GmtTime});
43
44 for (int i = 0; i < 2; ++i)
45 {
46 LOG_INFO(json_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
47 }
48
49 // It is also possible to create a logger than logs to both the json file and stdout
50 // with the appropriate format
51 auto json_sink_2 = quill::Frontend::get_sink("example_json.log");
52 auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink_id_1");
53
54 // Define a custom format pattern for console logging, which includes named arguments in the output.
55 // If you prefer to omit named arguments from the log messages, you can remove the "[%(named_args)]" part.
56 quill::PatternFormatterOptions console_log_pattern = quill::PatternFormatterOptions{
57 "%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<20) "
58 "%(message) [%(named_args)]"};
59
60 // Create a logger named "hybrid_logger" that writes to both a JSON sink and a console sink.
61 // Note: The JSON sink uses its own internal format, so the custom format defined here
62 // will only apply to the console output (via console_sink).
63 quill::Logger* hybrid_logger = quill::Frontend::create_or_get_logger(
64 "hybrid_logger", {std::move(json_sink_2), std::move(console_sink)}, console_log_pattern);
65
66 for (int i = 2; i < 4; ++i)
67 {
68 LOG_INFO(hybrid_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
69 }
70
71 LOG_INFO(hybrid_logger, "Operation {name} completed with code {code}", "Update", 123,
72 "Data synced successfully");
73}