ClickHouse
410 строк · 15.9 Кб
1#include "Loggers.h"
2
3#include "OwnFormattingChannel.h"
4#include "OwnPatternFormatter.h"
5#include "OwnSplitChannel.h"
6
7#include <iostream>
8#include <sstream>
9
10#include <Poco/ConsoleChannel.h>
11#include <Poco/Logger.h>
12#include <Poco/Net/RemoteSyslogChannel.h>
13#include <Poco/SyslogChannel.h>
14#include <Poco/Util/AbstractConfiguration.h>
15
16#ifndef WITHOUT_TEXT_LOG
17#include <Interpreters/TextLog.h>
18#endif
19
20#include <filesystem>
21
22namespace fs = std::filesystem;
23
24namespace DB
25{
26class SensitiveDataMasker;
27
28namespace ErrorCodes
29{
30extern const int BAD_ARGUMENTS;
31}
32
33}
34
35
36// TODO: move to libcommon
37static std::string createDirectory(const std::string & file)
38{
39auto path = fs::path(file).parent_path();
40if (path.empty())
41return "";
42fs::create_directories(path);
43return path;
44}
45
46static std::string renderFileNameTemplate(time_t now, const std::string & file_path)
47{
48fs::path path{file_path};
49std::tm buf;
50localtime_r(&now, &buf);
51std::ostringstream ss; // STYLE_CHECK_ALLOW_STD_STRING_STREAM
52ss << std::put_time(&buf, path.filename().c_str());
53return path.replace_filename(ss.str());
54}
55
56/// NOLINTBEGIN(readability-static-accessed-through-instance)
57
58void Loggers::buildLoggers(Poco::Util::AbstractConfiguration & config, Poco::Logger & logger /*_root*/, const std::string & cmd_name)
59{
60auto current_logger = config.getString("logger", "");
61if (config_logger.has_value() && *config_logger == current_logger)
62return;
63
64config_logger = current_logger;
65
66bool is_daemon = config.getBool("application.runAsDaemon", false);
67
68/// Split logs to ordinary log, error log, syslog and console.
69/// Use extended interface of Channel for more comprehensive logging.
70split = new DB::OwnSplitChannel();
71
72auto log_level_string = config.getString("logger.level", "trace");
73
74/// different channels (log, console, syslog) may have different loglevels configured
75/// The maximum (the most verbose) of those will be used as default for Poco loggers
76int max_log_level = 0;
77
78time_t now = std::time({});
79
80const auto log_path_prop = config.getString("logger.log", "");
81if (!log_path_prop.empty())
82{
83const auto log_path = renderFileNameTemplate(now, log_path_prop);
84createDirectory(log_path);
85
86std::string ext;
87if (config.getRawString("logger.stream_compress", "false") == "true")
88ext = ".lz4";
89
90std::cerr << "Logging " << log_level_string << " to " << log_path << ext << std::endl;
91
92auto log_level = Poco::Logger::parseLevel(log_level_string);
93if (log_level > max_log_level)
94{
95max_log_level = log_level;
96}
97
98// Set up two channel chains.
99log_file = new Poco::FileChannel;
100log_file->setProperty(Poco::FileChannel::PROP_PATH, fs::weakly_canonical(log_path));
101log_file->setProperty(Poco::FileChannel::PROP_ROTATION, config.getRawString("logger.size", "100M"));
102log_file->setProperty(Poco::FileChannel::PROP_ARCHIVE, "number");
103log_file->setProperty(Poco::FileChannel::PROP_COMPRESS, config.getRawString("logger.compress", "true"));
104log_file->setProperty(Poco::FileChannel::PROP_STREAMCOMPRESS, config.getRawString("logger.stream_compress", "false"));
105log_file->setProperty(Poco::FileChannel::PROP_PURGECOUNT, config.getRawString("logger.count", "1"));
106log_file->setProperty(Poco::FileChannel::PROP_FLUSH, config.getRawString("logger.flush", "true"));
107log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false"));
108log_file->open();
109
110Poco::AutoPtr<OwnPatternFormatter> pf;
111
112if (config.getString("logger.formatting.type", "") == "json")
113pf = new OwnJSONPatternFormatter(config);
114else
115pf = new OwnPatternFormatter;
116
117Poco::AutoPtr<DB::OwnFormattingChannel> log = new DB::OwnFormattingChannel(pf, log_file);
118log->setLevel(log_level);
119split->addChannel(log, "log");
120}
121
122const auto errorlog_path_prop = config.getString("logger.errorlog", "");
123if (!errorlog_path_prop.empty())
124{
125const auto errorlog_path = renderFileNameTemplate(now, errorlog_path_prop);
126createDirectory(errorlog_path);
127
128// NOTE: we don't use notice & critical in the code, so in practice error log collects fatal & error & warning.
129// (!) Warnings are important, they require attention and should never be silenced / ignored.
130auto errorlog_level = Poco::Logger::parseLevel(config.getString("logger.errorlog_level", "notice"));
131if (errorlog_level > max_log_level)
132{
133max_log_level = errorlog_level;
134}
135
136std::string ext;
137if (config.getRawString("logger.stream_compress", "false") == "true")
138ext = ".lz4";
139
140std::cerr << "Logging errors to " << errorlog_path << ext << std::endl;
141
142error_log_file = new Poco::FileChannel;
143error_log_file->setProperty(Poco::FileChannel::PROP_PATH, fs::weakly_canonical(errorlog_path));
144error_log_file->setProperty(Poco::FileChannel::PROP_ROTATION, config.getRawString("logger.size", "100M"));
145error_log_file->setProperty(Poco::FileChannel::PROP_ARCHIVE, "number");
146error_log_file->setProperty(Poco::FileChannel::PROP_COMPRESS, config.getRawString("logger.compress", "true"));
147error_log_file->setProperty(Poco::FileChannel::PROP_STREAMCOMPRESS, config.getRawString("logger.stream_compress", "false"));
148error_log_file->setProperty(Poco::FileChannel::PROP_PURGECOUNT, config.getRawString("logger.count", "1"));
149error_log_file->setProperty(Poco::FileChannel::PROP_FLUSH, config.getRawString("logger.flush", "true"));
150error_log_file->setProperty(Poco::FileChannel::PROP_ROTATEONOPEN, config.getRawString("logger.rotateOnOpen", "false"));
151
152Poco::AutoPtr<OwnPatternFormatter> pf;
153
154if (config.getString("logger.formatting.type", "") == "json")
155pf = new OwnJSONPatternFormatter(config);
156else
157pf = new OwnPatternFormatter;
158
159Poco::AutoPtr<DB::OwnFormattingChannel> errorlog = new DB::OwnFormattingChannel(pf, error_log_file);
160errorlog->setLevel(errorlog_level);
161errorlog->open();
162split->addChannel(errorlog, "errorlog");
163}
164
165if (config.getBool("logger.use_syslog", false))
166{
167auto syslog_level = Poco::Logger::parseLevel(config.getString("logger.syslog_level", log_level_string));
168if (syslog_level > max_log_level)
169{
170max_log_level = syslog_level;
171}
172
173if (config.has("logger.syslog.address"))
174{
175syslog_channel = new Poco::Net::RemoteSyslogChannel();
176// syslog address
177syslog_channel->setProperty(Poco::Net::RemoteSyslogChannel::PROP_LOGHOST, config.getString("logger.syslog.address"));
178if (config.has("logger.syslog.hostname"))
179{
180syslog_channel->setProperty(Poco::Net::RemoteSyslogChannel::PROP_HOST, config.getString("logger.syslog.hostname"));
181}
182syslog_channel->setProperty(Poco::Net::RemoteSyslogChannel::PROP_FORMAT, config.getString("logger.syslog.format", "syslog"));
183syslog_channel->setProperty(
184Poco::Net::RemoteSyslogChannel::PROP_FACILITY, config.getString("logger.syslog.facility", "LOG_USER"));
185}
186else
187{
188syslog_channel = new Poco::SyslogChannel();
189syslog_channel->setProperty(Poco::SyslogChannel::PROP_NAME, cmd_name);
190syslog_channel->setProperty(Poco::SyslogChannel::PROP_OPTIONS, config.getString("logger.syslog.options", "LOG_CONS|LOG_PID"));
191syslog_channel->setProperty(Poco::SyslogChannel::PROP_FACILITY, config.getString("logger.syslog.facility", "LOG_DAEMON"));
192}
193syslog_channel->open();
194
195Poco::AutoPtr<OwnPatternFormatter> pf;
196
197if (config.getString("logger.formatting.type", "") == "json")
198pf = new OwnJSONPatternFormatter(config);
199else
200pf = new OwnPatternFormatter;
201
202Poco::AutoPtr<DB::OwnFormattingChannel> log = new DB::OwnFormattingChannel(pf, syslog_channel);
203log->setLevel(syslog_level);
204
205split->addChannel(log, "syslog");
206}
207
208bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO);
209bool color_logs_by_default = isatty(STDERR_FILENO);
210
211if (config.getBool("logger.console", false)
212|| (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console))
213{
214bool color_enabled = config.getBool("logger.color_terminal", color_logs_by_default);
215
216auto console_log_level_string = config.getString("logger.console_log_level", log_level_string);
217auto console_log_level = Poco::Logger::parseLevel(console_log_level_string);
218if (console_log_level > max_log_level)
219{
220max_log_level = console_log_level;
221}
222
223Poco::AutoPtr<OwnPatternFormatter> pf;
224if (config.getString("logger.formatting.type", "") == "json")
225pf = new OwnJSONPatternFormatter(config);
226else
227pf = new OwnPatternFormatter(color_enabled);
228Poco::AutoPtr<DB::OwnFormattingChannel> log = new DB::OwnFormattingChannel(pf, new Poco::ConsoleChannel);
229log->setLevel(console_log_level);
230split->addChannel(log, "console");
231}
232
233split->open();
234logger.close();
235
236logger.setChannel(split);
237logger.setLevel(max_log_level);
238
239// Global logging level and channel (it can be overridden for specific loggers).
240logger.root().setLevel(max_log_level);
241logger.root().setChannel(logger.getChannel());
242
243// Set level and channel to all already created loggers
244std::vector<std::string> names;
245logger.names(names);
246
247for (const auto & name : names)
248{
249logger.get(name).setLevel(max_log_level);
250logger.get(name).setChannel(split);
251}
252
253// Explicitly specified log levels for specific loggers.
254{
255Poco::Util::AbstractConfiguration::Keys loggers_level;
256config.keys("logger.levels", loggers_level);
257
258if (!loggers_level.empty())
259{
260for (const auto & key : loggers_level)
261{
262if (key == "logger" || key.starts_with("logger["))
263{
264const std::string name(config.getString("logger.levels." + key + ".name"));
265const std::string level(config.getString("logger.levels." + key + ".level"));
266logger.root().get(name).setLevel(level);
267}
268else
269{
270// Legacy syntax
271const std::string level(config.getString("logger.levels." + key, "trace"));
272logger.root().get(key).setLevel(level);
273}
274}
275}
276}
277#ifndef WITHOUT_TEXT_LOG
278if (config.has("text_log"))
279{
280String text_log_level_str = config.getString("text_log.level", "trace");
281int text_log_level = Poco::Logger::parseLevel(text_log_level_str);
282
283DB::SystemLogQueueSettings log_settings;
284log_settings.flush_interval_milliseconds = config.getUInt64("text_log.flush_interval_milliseconds",
285DB::TextLog::getDefaultFlushIntervalMilliseconds());
286
287log_settings.max_size_rows = config.getUInt64("text_log.max_size_rows",
288DB::TextLog::getDefaultMaxSize());
289
290if (log_settings.max_size_rows< 1)
291throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS, "text_log.max_size_rows {} should be 1 at least",
292log_settings.max_size_rows);
293
294log_settings.reserved_size_rows = config.getUInt64("text_log.reserved_size_rows", DB::TextLog::getDefaultReservedSize());
295
296if (log_settings.max_size_rows < log_settings.reserved_size_rows)
297{
298throw DB::Exception(DB::ErrorCodes::BAD_ARGUMENTS,
299"text_log.max_size {0} should be greater or equal to text_log.reserved_size_rows {1}",
300log_settings.max_size_rows,
301log_settings.reserved_size_rows);
302}
303
304log_settings.buffer_size_rows_flush_threshold = config.getUInt64("text_log.buffer_size_rows_flush_threshold",
305log_settings.max_size_rows / 2);
306
307log_settings.notify_flush_on_crash = config.getBool("text_log.flush_on_crash",
308DB::TextLog::shouldNotifyFlushOnCrash());
309
310log_settings.turn_off_logger = DB::TextLog::shouldTurnOffLogger();
311
312log_settings.database = config.getString("text_log.database", "system");
313log_settings.table = config.getString("text_log.table", "text_log");
314
315split->addTextLog(DB::TextLog::getLogQueue(log_settings), text_log_level);
316}
317#endif
318}
319
320void Loggers::updateLevels(Poco::Util::AbstractConfiguration & config, Poco::Logger & logger)
321{
322int max_log_level = 0;
323
324const auto log_level_string = config.getString("logger.level", "trace");
325int log_level = Poco::Logger::parseLevel(log_level_string);
326if (log_level > max_log_level)
327max_log_level = log_level;
328
329if (log_file)
330split->setLevel("log", log_level);
331
332// Set level to console
333bool is_daemon = config.getBool("application.runAsDaemon", false);
334bool should_log_to_console = isatty(STDIN_FILENO) || isatty(STDERR_FILENO);
335if (config.getBool("logger.console", false)
336|| (!config.hasProperty("logger.console") && !is_daemon && should_log_to_console))
337split->setLevel("console", log_level);
338else
339split->setLevel("console", 0);
340
341// Set level to errorlog
342if (error_log_file)
343{
344int errorlog_level = Poco::Logger::parseLevel(config.getString("logger.errorlog_level", "notice"));
345if (errorlog_level > max_log_level)
346max_log_level = errorlog_level;
347split->setLevel("errorlog", errorlog_level);
348}
349
350// Set level to syslog
351int syslog_level = 0;
352if (config.getBool("logger.use_syslog", false))
353{
354syslog_level = Poco::Logger::parseLevel(config.getString("logger.syslog_level", log_level_string));
355if (syslog_level > max_log_level)
356max_log_level = syslog_level;
357}
358split->setLevel("syslog", syslog_level);
359
360// Global logging level (it can be overridden for specific loggers).
361logger.setLevel(max_log_level);
362
363// Set level to all already created loggers
364std::vector<std::string> names;
365
366logger.root().names(names);
367for (const auto & name : names)
368logger.root().get(name).setLevel(max_log_level);
369
370logger.root().setLevel(max_log_level);
371
372// Explicitly specified log levels for specific loggers.
373{
374Poco::Util::AbstractConfiguration::Keys loggers_level;
375config.keys("logger.levels", loggers_level);
376
377if (!loggers_level.empty())
378{
379for (const auto & key : loggers_level)
380{
381if (key == "logger" || key.starts_with("logger["))
382{
383const std::string name(config.getString("logger.levels." + key + ".name"));
384const std::string level(config.getString("logger.levels." + key + ".level"));
385logger.root().get(name).setLevel(level);
386}
387else
388{
389// Legacy syntax
390const std::string level(config.getString("logger.levels." + key, "trace"));
391logger.root().get(key).setLevel(level);
392}
393}
394}
395}
396}
397
398/// NOLINTEND(readability-static-accessed-through-instance)
399
400void Loggers::closeLogs(Poco::Logger & logger)
401{
402if (log_file)
403log_file->close();
404if (error_log_file)
405error_log_file->close();
406// Shouldn't syslog_channel be closed here too?
407
408if (!log_file)
409logger.warning("Logging to console but received signal to close log file (ignoring).");
410}
411