Skip to content

Commit f54ab70

Browse files
Merge pull request ClickHouse#86493 from ClickHouse/backport/25.8/86331
Backport ClickHouse#86331 to 25.8: Add ability to enable JSON logging only for specific channel (i.e. console)
2 parents 20676d6 + 0b4fba7 commit f54ab70

File tree

9 files changed

+107
-59
lines changed

9 files changed

+107
-59
lines changed

docs/en/operations/server-configuration-parameters/_server_settings_outside_source.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,8 @@ To enable JSON logging support, use the following snippet:
969969
<logger>
970970
<formatting>
971971
<type>json</type>
972+
<!-- Can be configured on a per-channel basis (log, errorlog, console, syslog), or globally for all channels (then just omit it). -->
973+
<!-- <channel></channel> -->
972974
<names>
973975
<date_time>date_time</date_time>
974976
<thread_name>thread_name</thread_name>

programs/server/config.xml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@
7272
</logger>
7373
</levels>
7474
-->
75+
7576
<!-- Structured log formatting:
76-
You can specify log format(for now, JSON only). In that case, the console log will be printed
77-
in specified format like JSON.
78-
For example, as below:
77+
78+
You can specify log format(for now, JSON only).
79+
It can be done either on a per-channel (log, errorlog, console, syslog) level (set `channel`, i.e. `<channel>console</channel>') or for all channels (omit `channel`).
80+
The log will be printed in specified format like JSON, example:
7981
8082
{"date_time":"1650918987.180175","thread_name":"#1","thread_id":"254545","level":"Trace","query_id":"","logger_name":"BaseDaemon","message":"Received signal 2","source_file":"../base/daemon/BaseDaemon.cpp; virtual void SignalListener::run()","source_line":"192"}
8183
{"date_time_utc":"2024-11-06T09:06:09Z","thread_name":"#1","thread_id":"254545","level":"Trace","query_id":"","logger_name":"BaseDaemon","message":"Received signal 2","source_file":"../base/daemon/BaseDaemon.cpp; virtual void SignalListener::run()","source_line":"192"}
@@ -91,8 +93,10 @@
9193
However, if you comment out all the tags under <names>, the program will print default values for as
9294
below.
9395
-->
94-
<!-- <formatting>
96+
<!--
97+
<formatting>
9598
<type>json</type>
99+
<channel></channel>
96100
<names>
97101
<date_time>date_time</date_time>
98102
<date_time_utc>date_time_utc</date_time_utc>
@@ -105,7 +109,8 @@
105109
<source_file>source_file</source_file>
106110
<source_line>source_line</source_line>
107111
</names>
108-
</formatting> -->
112+
</formatting>
113+
-->
109114
</logger>
110115

111116
<url_scheme_mappers>

src/Daemon/BaseDaemon.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -621,11 +621,7 @@ void BaseDaemon::setupWatchdog()
621621
/// If streaming compression of logs is used then we write watchdog logs to cerr
622622
if (config().getRawString("logger.stream_compress", "false") == "true")
623623
{
624-
Poco::AutoPtr<OwnPatternFormatter> pf;
625-
if (config().getString("logger.formatting.type", "") == "json")
626-
pf = new OwnJSONPatternFormatter(config());
627-
else
628-
pf = new OwnPatternFormatter;
624+
Poco::AutoPtr<OwnPatternFormatter> pf = getFormatForChannel(config(), "console");
629625
Poco::AutoPtr<OwnFormattingChannel> log = new OwnFormattingChannel(pf, new Poco::ConsoleChannel(std::cerr));
630626
logger().setChannel(log);
631627
}

src/Loggers/Loggers.cpp

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,37 @@ static std::string renderFileNameTemplate(time_t now, const std::string & file_p
6767
return path.replace_filename(ss.str());
6868
}
6969

70+
Poco::AutoPtr<OwnPatternFormatter> getFormatForChannel(Poco::Util::AbstractConfiguration & config, const std::string & channel, bool color)
71+
{
72+
Poco::Util::AbstractConfiguration::Keys keys;
73+
config.keys("logger", keys);
74+
75+
std::string config_prefix_for_channel;
76+
std::string config_prefix_global;
77+
for (const auto & key : keys)
78+
{
79+
if (key != "formatting" && !key.starts_with("formatting["))
80+
continue;
81+
82+
if (config.getString(fmt::format("logger.{}.channel", key), "") == channel)
83+
{
84+
config_prefix_for_channel = "logger." + key;
85+
break;
86+
}
87+
if (config.getString(fmt::format("logger.{}.channel", key), "").empty())
88+
{
89+
config_prefix_global = "logger." + key;
90+
break;
91+
}
92+
}
93+
94+
const auto & config_prefix = config_prefix_for_channel.empty() ? config_prefix_global : config_prefix_for_channel;
95+
if (config.getString(config_prefix + ".type", "") == "json")
96+
return new OwnJSONPatternFormatter(config, config_prefix);
97+
else
98+
return new OwnPatternFormatter(color);
99+
}
100+
70101
/// NOLINTBEGIN(readability-static-accessed-through-instance)
71102

72103
void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Logger & logger /*_root*/, const std::string & cmd_name)
@@ -124,13 +155,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log
124155
log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false"));
125156
log_file->open();
126157

127-
Poco::AutoPtr<OwnPatternFormatter> pf;
128-
129-
if (config.getString("logger.formatting.type", "") == "json")
130-
pf = new OwnJSONPatternFormatter(config);
131-
else
132-
pf = new OwnPatternFormatter;
133-
158+
Poco::AutoPtr<OwnPatternFormatter> pf = getFormatForChannel(config, "log");
134159
auto log = std::make_shared<DB::OwnFormattingChannel>(pf, log_file);
135160
split->addChannel(
136161
log, "FileLog", log_level, ProfileEvents::AsyncLoggingFileLogTotalMessages, ProfileEvents::AsyncLoggingFileLogDroppedMessages);
@@ -163,13 +188,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log
163188
error_log_file->setProperty(Poco::FileChannel::PROP_FLUSH, config.getRawString("logger.flush", "true"));
164189
error_log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false"));
165190

166-
Poco::AutoPtr<OwnPatternFormatter> pf;
167-
168-
if (config.getString("logger.formatting.type", "") == "json")
169-
pf = new OwnJSONPatternFormatter(config);
170-
else
171-
pf = new OwnPatternFormatter;
172-
191+
Poco::AutoPtr<OwnPatternFormatter> pf = getFormatForChannel(config, "errorlog");
173192
auto errorlog = std::make_shared<DB::OwnFormattingChannel>(pf, error_log_file);
174193
errorlog->open();
175194
split->addChannel(
@@ -207,13 +226,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log
207226
}
208227
syslog_channel->open();
209228

210-
Poco::AutoPtr<OwnPatternFormatter> pf;
211-
212-
if (config.getString("logger.formatting.type", "") == "json")
213-
pf = new OwnJSONPatternFormatter(config);
214-
else
215-
pf = new OwnPatternFormatter;
216-
229+
Poco::AutoPtr<OwnPatternFormatter> pf = getFormatForChannel(config, "syslog");
217230
auto log = std::make_shared<DB::OwnFormattingChannel>(pf, syslog_channel);
218231
split->addChannel(
219232
log, "Syslog", syslog_level, ProfileEvents::AsyncLoggingSyslogTotalMessages, ProfileEvents::AsyncLoggingSyslogDroppedMessages);
@@ -231,11 +244,7 @@ void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Log
231244
auto console_log_level = Poco::Logger::parseLevel(console_log_level_string);
232245
max_log_level = std::max(console_log_level, max_log_level);
233246

234-
Poco::AutoPtr<OwnPatternFormatter> pf;
235-
if (config.getString("logger.formatting.type", "") == "json")
236-
pf = new OwnJSONPatternFormatter(config);
237-
else
238-
pf = new OwnPatternFormatter(color_enabled);
247+
Poco::AutoPtr<OwnPatternFormatter> pf = getFormatForChannel(config, "console", color_enabled);
239248
auto log = std::make_shared<DB::OwnFormattingChannel>(pf, new Poco::ConsoleChannel);
240249
split->addChannel(
241250
log,

src/Loggers/Loggers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,6 @@ class Loggers
5050

5151
Poco::AutoPtr<DB::OwnSplitChannelBase> split;
5252
};
53+
54+
class OwnPatternFormatter;
55+
Poco::AutoPtr<OwnPatternFormatter> getFormatForChannel(Poco::Util::AbstractConfiguration & config, const std::string & channel, bool color = false);

src/Loggers/OwnJSONPatternFormatter.cpp

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include <Loggers/OwnJSONPatternFormatter.h>
22

3-
#include <functional>
43
#include <IO/WriteBufferFromString.h>
54
#include <IO/WriteHelpers.h>
65
#include <Interpreters/InternalTextLogsQueue.h>
@@ -11,37 +10,37 @@
1110
#include <Common/DateLUTImpl.h>
1211

1312

14-
OwnJSONPatternFormatter::OwnJSONPatternFormatter(Poco::Util::AbstractConfiguration & config)
13+
OwnJSONPatternFormatter::OwnJSONPatternFormatter(Poco::Util::AbstractConfiguration & config, const std::string & config_prefix)
1514
{
16-
if (config.has("logger.formatting.names.date_time"))
17-
date_time = config.getString("logger.formatting.names.date_time", "");
15+
if (config.has(config_prefix + ".names.date_time"))
16+
date_time = config.getString(config_prefix + ".names.date_time", "");
1817

19-
if (config.has("logger.formatting.names.date_time_utc"))
20-
date_time_utc= config.getString("logger.formatting.names.date_time_utc", "");
18+
if (config.has(config_prefix + ".names.date_time_utc"))
19+
date_time_utc= config.getString(config_prefix + ".names.date_time_utc", "");
2120

22-
if (config.has("logger.formatting.names.thread_name"))
23-
thread_name = config.getString("logger.formatting.names.thread_name", "");
21+
if (config.has(config_prefix + ".names.thread_name"))
22+
thread_name = config.getString(config_prefix + ".names.thread_name", "");
2423

25-
if (config.has("logger.formatting.names.thread_id"))
26-
thread_id = config.getString("logger.formatting.names.thread_id", "");
24+
if (config.has(config_prefix + ".names.thread_id"))
25+
thread_id = config.getString(config_prefix + ".names.thread_id", "");
2726

28-
if (config.has("logger.formatting.names.level"))
29-
level = config.getString("logger.formatting.names.level", "");
27+
if (config.has(config_prefix + ".names.level"))
28+
level = config.getString(config_prefix + ".names.level", "");
3029

31-
if (config.has("logger.formatting.names.query_id"))
32-
query_id = config.getString("logger.formatting.names.query_id", "");
30+
if (config.has(config_prefix + ".names.query_id"))
31+
query_id = config.getString(config_prefix + ".names.query_id", "");
3332

34-
if (config.has("logger.formatting.names.logger_name"))
35-
logger_name = config.getString("logger.formatting.names.logger_name", "");
33+
if (config.has(config_prefix + ".names.logger_name"))
34+
logger_name = config.getString(config_prefix + ".names.logger_name", "");
3635

37-
if (config.has("logger.formatting.names.message"))
38-
message = config.getString("logger.formatting.names.message", "");
36+
if (config.has(config_prefix + ".names.message"))
37+
message = config.getString(config_prefix + ".names.message", "");
3938

40-
if (config.has("logger.formatting.names.source_file"))
41-
source_file = config.getString("logger.formatting.names.source_file", "");
39+
if (config.has(config_prefix + ".names.source_file"))
40+
source_file = config.getString(config_prefix + ".names.source_file", "");
4241

43-
if (config.has("logger.formatting.names.source_line"))
44-
source_line = config.getString("logger.formatting.names.source_line", "");
42+
if (config.has(config_prefix + ".names.source_line"))
43+
source_line = config.getString(config_prefix + ".names.source_line", "");
4544

4645
if (date_time.empty() && thread_name.empty() && thread_id.empty() && level.empty() && query_id.empty()
4746
&& logger_name.empty() && message.empty() && source_file.empty() && source_line.empty())

src/Loggers/OwnJSONPatternFormatter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Loggers;
2626
class OwnJSONPatternFormatter : public OwnPatternFormatter
2727
{
2828
public:
29-
explicit OwnJSONPatternFormatter(Poco::Util::AbstractConfiguration & config);
29+
explicit OwnJSONPatternFormatter(Poco::Util::AbstractConfiguration & config, const std::string & config_prefix);
3030

3131
void format(const Poco::Message & msg, std::string & text) override;
3232
void formatExtended(const DB::ExtendedLogMessage & msg_ext, std::string & text) const override;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0"?>
2+
<clickhouse>
3+
<logger>
4+
<formatting>
5+
<type>json</type>
6+
<channel>errorlog</channel>
7+
<names>
8+
<date_time>DATE_TIME</date_time>
9+
<date_time_utc>DATE_TIME_UTC</date_time_utc>
10+
<thread_name>THREAD_NAME</thread_name>
11+
<thread_id>THREAD_ID</thread_id>
12+
<level>LEVEL</level>
13+
<query_id>QUERY_ID</query_id>
14+
<logger_name>LOGGER_NAME</logger_name>
15+
<message>MESSAGE</message>
16+
<source_file>SOURCE_FILE</source_file>
17+
<source_line>SOURCE_LINE</source_line>
18+
</names>
19+
</formatting>
20+
</logger>
21+
</clickhouse>

tests/integration/test_structured_logging_json/test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
"node_no_keys", main_configs=["configs/config_no_keys_json.xml"]
1818
)
1919

20+
node_json_logging_per_channel = cluster.add_instance(
21+
"node_json_logging_per_channel", main_configs=["configs/config_json_logging_per_channel.xml"]
22+
)
23+
2024

2125
@pytest.fixture(scope="module")
2226
def start_cluster():
@@ -147,3 +151,12 @@ def test_structured_logging_json_format(start_cluster):
147151
== True
148152
)
149153
assert validate_everything(config_no_keys, node_no_keys, "config_no_keys") == True
154+
155+
def test_structured_logging_per_channel(start_cluster):
156+
logs = node_json_logging_per_channel.grep_in_log("").split("\n")
157+
assert len(logs) > 0
158+
assert not validate_logs(logs)
159+
160+
error_logs = node_json_logging_per_channel.grep_in_log("", filename="clickhouse-server.err.log").strip().split("\n")
161+
assert len(error_logs) > 0
162+
assert validate_logs(error_logs)

0 commit comments

Comments
 (0)