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