1 /*
2 Copyright (c) 2017, 2020, Oracle and/or its affiliates.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License, version 2.0,
6 as published by the Free Software Foundation.
7
8 This program is also distributed with certain software (including
9 but not limited to OpenSSL) that is licensed under separate terms,
10 as designated in a particular file or component or in included license
11 documentation. The authors of MySQL hereby grant you an additional
12 permission to link the program and your derivative works with the
13 separately licensed software that they have included with MySQL.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 #include <condition_variable>
26 #include <csignal>
27 #include <fstream>
28 #include <functional>
29 #include <mutex>
30 #include <string>
31 #include <thread>
32
33 #include "dim.h"
34 #include "mock_server_rest_client.h"
35 #include "mock_server_testutils.h"
36 #include "mysql/harness/logging/logging.h"
37 #include "mysql_session.h"
38 #include "mysqlrouter/utils.h"
39 #include "random_generator.h"
40 #include "router_component_test.h"
41 #include "tcp_port_pool.h"
42
43 /**
44 * @file
45 * @brief Component Tests for loggers
46 */
47
48 using mysql_harness::logging::LogLevel;
49 using mysql_harness::logging::LogTimestampPrecision;
50 using testing::HasSubstr;
51 using testing::Not;
52 using testing::StartsWith;
53 using namespace std::chrono_literals;
54
55 class RouterLoggingTest : public RouterComponentTest {
56 protected:
create_config_file(const std::string & directory,const std::string & sections,const std::map<std::string,std::string> * default_section) const57 std::string create_config_file(
58 const std::string &directory, const std::string §ions,
59 const std::map<std::string, std::string> *default_section) const {
60 return ProcessManager::create_config_file(
61 directory, sections, default_section, "mysqlrouter.conf", "", false);
62 }
63
64 TcpPortPool port_pool_;
65 };
66
67 /** @test This test verifies that fatal error messages thrown before switching
68 * to logger specified in config file (before Loader::run() runs
69 * logger_plugin.cc:init()) are properly logged to STDERR
70 */
TEST_F(RouterLoggingTest,log_startup_failure_to_console)71 TEST_F(RouterLoggingTest, log_startup_failure_to_console) {
72 auto conf_params = get_DEFAULT_defaults();
73 // we want to log to the console
74 conf_params["logging_folder"] = "";
75 TempDirectory conf_dir("conf");
76 const std::string conf_file =
77 create_config_file(conf_dir.name(), "[invalid]", &conf_params);
78
79 // run the router and wait for it to exit
80 auto &router = launch_router({"-c", conf_file}, 1);
81 check_exit_code(router, EXIT_FAILURE);
82
83 // expect something like this to appear on STDERR
84 // plugin 'invalid' failed to
85 // load: ./plugin_output_directory/invalid.so: cannot open shared object
86 // file: No such file or directory
87 const std::string out = router.get_full_output();
88 EXPECT_THAT(
89 out, HasSubstr("Loading plugin for config-section '[invalid]' failed"));
90 }
91
92 /** @test This test is similar to log_startup_failure_to_console(), but the
93 * failure message is expected to be logged into a logfile
94 */
TEST_F(RouterLoggingTest,log_startup_failure_to_logfile)95 TEST_F(RouterLoggingTest, log_startup_failure_to_logfile) {
96 // create tmp dir where we will log
97 TempDirectory logging_folder;
98
99 // create config with logging_folder set to that directory
100 std::map<std::string, std::string> params = get_DEFAULT_defaults();
101 params.at("logging_folder") = logging_folder.name();
102 TempDirectory conf_dir("conf");
103 const std::string conf_file =
104 create_config_file(conf_dir.name(), "[routing]", ¶ms);
105
106 // run the router and wait for it to exit
107 auto &router = launch_router({"-c", conf_file}, 1);
108 check_exit_code(router, EXIT_FAILURE);
109
110 // expect something like this to appear in log:
111 // 2018-12-19 03:54:04 main ERROR [7f539f628780] Configuration error: option
112 // destinations in [routing] is required
113 auto matcher = [](const std::string &line) -> bool {
114 return line.find(
115 "Configuration error: option destinations in [routing] is "
116 "required") != line.npos;
117 };
118
119 EXPECT_TRUE(find_in_file(logging_folder.name() + "/mysqlrouter.log", matcher))
120 << "log:"
121 << router.get_full_logfile("mysqlrouter.log", logging_folder.name());
122 }
123
124 /** @test This test verifies that invalid logging_folder is properly handled and
125 * appropriate message is printed on STDERR. Router tries to
126 * mkdir(logging_folder) if it doesn't exist, then write its log inside of it.
127 */
TEST_F(RouterLoggingTest,bad_logging_folder)128 TEST_F(RouterLoggingTest, bad_logging_folder) {
129 // create tmp dir to contain our tests
130 TempDirectory tmp_dir;
131
132 // unfortunately it's not (reasonably) possible to make folders read-only on
133 // Windows, therefore we can run the following 2 tests only on Unix
134 // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
135 #ifndef _WIN32
136
137 // make tmp dir read-only
138 chmod(tmp_dir.name().c_str(),
139 S_IRUSR | S_IXUSR); // r-x for the user (aka 500)
140
141 // logging_folder doesn't exist and can't be created
142 {
143 const std::string logging_dir = tmp_dir.name() + "/some_dir";
144
145 // create Router config
146 std::map<std::string, std::string> params = get_DEFAULT_defaults();
147 params.at("logging_folder") = logging_dir;
148 TempDirectory conf_dir("conf");
149 const std::string conf_file =
150 create_config_file(conf_dir.name(), "[keepalive]\n", ¶ms);
151
152 // run the router and wait for it to exit
153 auto &router = launch_router({"-c", conf_file}, 1);
154 check_exit_code(router, EXIT_FAILURE);
155
156 // expect something like this to appear on STDERR
157 // Error: Error when creating dir '/bla': 13
158 const std::string out = router.get_full_output();
159 EXPECT_THAT(
160 out.c_str(),
161 HasSubstr("plugin 'logger' init failed: Error when creating dir '" +
162 logging_dir + "': 13"));
163 }
164
165 // logging_folder exists but is not writeable
166 {
167 const std::string logging_dir = tmp_dir.name();
168
169 // create Router config
170 std::map<std::string, std::string> params = get_DEFAULT_defaults();
171 params.at("logging_folder") = logging_dir;
172 TempDirectory conf_dir("conf");
173 const std::string conf_file =
174 create_config_file(conf_dir.name(), "[keepalive]\n", ¶ms);
175
176 // run the router and wait for it to exit
177 auto &router = launch_router({"-c", conf_file}, 1);
178 check_exit_code(router, EXIT_FAILURE);
179
180 // expect something like this to appear on STDERR
181 // Error: Cannot create file in directory //mysqlrouter.log: Permission
182 // denied
183 const std::string out = router.get_full_output();
184 #ifndef _WIN32
185 EXPECT_THAT(
186 out.c_str(),
187 HasSubstr(
188 "plugin 'logger' init failed: Cannot create file in directory " +
189 logging_dir + ": Permission denied\n"));
190 #endif
191 }
192
193 // restore writability to tmp dir
194 chmod(tmp_dir.name().c_str(),
195 S_IRUSR | S_IWUSR | S_IXUSR); // rwx for the user (aka 700)
196
197 #endif // #ifndef _WIN32
198
199 // logging_folder is really a file
200 {
201 const std::string logging_dir = tmp_dir.name() + "/some_file";
202
203 // create that file
204 {
205 std::ofstream some_file(logging_dir);
206 EXPECT_TRUE(some_file.good());
207 }
208
209 // create Router config
210 std::map<std::string, std::string> params = get_DEFAULT_defaults();
211 params.at("logging_folder") = logging_dir;
212 TempDirectory conf_dir("conf");
213 const std::string conf_file =
214 create_config_file(conf_dir.name(), "[keepalive]\n", ¶ms);
215
216 // run the router and wait for it to exit
217 auto &router = launch_router({"-c", conf_file}, 1);
218 check_exit_code(router, EXIT_FAILURE);
219
220 // expect something like this to appear on STDERR
221 // Error: Cannot create file in directory /etc/passwd/mysqlrouter.log: Not a
222 // directory
223 const std::string out = router.get_full_output();
224 const std::string prefix("Cannot create file in directory " + logging_dir +
225 ": ");
226 #ifndef _WIN32
227 EXPECT_THAT(out.c_str(), HasSubstr(prefix + "Not a directory\n"));
228 #else
229 // on Windows emulate (wine) we get ENOTDIR
230 // with native windows we get ENOENT
231
232 EXPECT_THAT(
233 out.c_str(),
234 ::testing::AnyOf(
235 ::testing::HasSubstr(prefix + "Directory name invalid.\n"),
236 ::testing::HasSubstr(
237 prefix + "The system cannot find the path specified.\n")));
238 #endif
239 }
240 }
241
TEST_F(RouterLoggingTest,multiple_logger_sections)242 TEST_F(RouterLoggingTest, multiple_logger_sections) {
243 // This test verifies that multiple [logger] sections are handled properly.
244 // Router should report the error on STDERR and exit
245
246 auto conf_params = get_DEFAULT_defaults();
247 // we want to log to the console
248 conf_params["logging_folder"] = "";
249 TempDirectory conf_dir("conf");
250 const std::string conf_file =
251 create_config_file(conf_dir.name(), "[logger]\n[logger]\n", &conf_params);
252
253 // run the router and wait for it to exit
254 auto &router = launch_router({"-c", conf_file}, 1);
255 check_exit_code(router, EXIT_FAILURE);
256
257 // expect something like this to appear on STDERR
258 // Error: Configuration error: Section 'logger' already exists
259 const std::string out = router.get_full_output();
260 EXPECT_THAT(
261 out.c_str(),
262 ::testing::HasSubstr(
263 "Error: Configuration error: Section 'logger' already exists"));
264 }
265
TEST_F(RouterLoggingTest,logger_section_with_key)266 TEST_F(RouterLoggingTest, logger_section_with_key) {
267 // This test verifies that [logger:with_some_key] section is handled properly
268 // Router should report the error on STDERR and exit
269 auto conf_params = get_DEFAULT_defaults();
270 // we want to log to the console
271 conf_params["logging_folder"] = "";
272 TempDirectory conf_dir("conf");
273 const std::string conf_file =
274 create_config_file(conf_dir.name(), "[logger:some_key]\n", &conf_params);
275
276 // run the router and wait for it to exit
277 auto &router = launch_router({"-c", conf_file}, 1);
278 check_exit_code(router, EXIT_FAILURE);
279
280 // expect something like this to appear on STDERR
281 // Error: Section 'logger' does not support key
282 const std::string out = router.get_full_output();
283 EXPECT_THAT(out.c_str(),
284 HasSubstr("Error: Section 'logger' does not support keys"));
285 }
286
TEST_F(RouterLoggingTest,bad_loglevel)287 TEST_F(RouterLoggingTest, bad_loglevel) {
288 // This test verifies that bad log level in [logger] section is handled
289 // properly. Router should report the error on STDERR and exit
290
291 auto conf_params = get_DEFAULT_defaults();
292 // we want to log to the console
293 conf_params["logging_folder"] = "";
294 TempDirectory conf_dir("conf");
295 const std::string conf_file = create_config_file(
296 conf_dir.name(), "[logger]\nlevel = UNKNOWN\n", &conf_params);
297
298 // run the router and wait for it to exit
299 auto &router = launch_router({"-c", conf_file}, 1);
300 check_exit_code(router, EXIT_FAILURE);
301
302 // expect something like this to appear on STDERR
303 // Configuration error: Log level 'unknown' is not valid. Valid values are:
304 // debug, error, fatal, info, note, system, and warning
305 const std::string out = router.get_full_output();
306 EXPECT_THAT(
307 out.c_str(),
308 HasSubstr(
309 "Configuration error: Log level 'unknown' is not valid. Valid "
310 "values are: debug, error, fatal, info, note, system, and warning"));
311 }
312
313 /**************************************************/
314 /* Tests for valid logger configurations */
315 /**************************************************/
316
317 struct LoggingConfigOkParams {
318 std::string logger_config;
319 bool logging_folder_empty;
320
321 LogLevel consolelog_expected_level;
322 LogLevel filelog_expected_level;
323
324 LogTimestampPrecision consolelog_expected_timestamp_precision;
325 LogTimestampPrecision filelog_expected_timestamp_precision;
326
LoggingConfigOkParamsLoggingConfigOkParams327 LoggingConfigOkParams(const std::string &logger_config_,
328 const bool logging_folder_empty_,
329 const LogLevel consolelog_expected_level_,
330 const LogLevel filelog_expected_level_)
331 : logger_config(logger_config_),
332 logging_folder_empty(logging_folder_empty_),
333 consolelog_expected_level(consolelog_expected_level_),
334 filelog_expected_level(filelog_expected_level_),
335 consolelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet),
336 filelog_expected_timestamp_precision(LogTimestampPrecision::kNotSet) {}
337
LoggingConfigOkParamsLoggingConfigOkParams338 LoggingConfigOkParams(
339 const std::string &logger_config_, const bool logging_folder_empty_,
340 const LogLevel consolelog_expected_level_,
341 const LogLevel filelog_expected_level_,
342 const LogTimestampPrecision consolelog_expected_timestamp_precision_,
343 const LogTimestampPrecision filelog_expected_timestamp_precision_)
344 : logger_config(logger_config_),
345 logging_folder_empty(logging_folder_empty_),
346 consolelog_expected_level(consolelog_expected_level_),
347 filelog_expected_level(filelog_expected_level_),
348 consolelog_expected_timestamp_precision(
349 consolelog_expected_timestamp_precision_),
350 filelog_expected_timestamp_precision(
351 filelog_expected_timestamp_precision_) {}
352 };
353
operator <<(::std::ostream & os,const LoggingConfigOkParams & ltp)354 ::std::ostream &operator<<(::std::ostream &os,
355 const LoggingConfigOkParams <p) {
356 return os << "config=" << ltp.logger_config
357 << ", logging_folder_empty=" << ltp.logging_folder_empty;
358 }
359
360 class RouterLoggingTestConfig
361 : public RouterLoggingTest,
362 public ::testing::WithParamInterface<LoggingConfigOkParams> {};
363
364 /** @test This test verifies that a proper loggs are written to selected sinks
365 * for various sinks/levels combinations.
366 */
TEST_P(RouterLoggingTestConfig,LoggingTestConfig)367 TEST_P(RouterLoggingTestConfig, LoggingTestConfig) {
368 auto test_params = GetParam();
369
370 TempDirectory tmp_dir;
371 TcpPortPool port_pool;
372 const auto router_port = port_pool.get_next_available();
373 const auto server_port = port_pool.get_next_available();
374
375 // These are different level log entries that are expected to get logged after
376 // the logger plugin has been initialized
377 const std::string kDebugLogEntry = "plugin 'logger:' doesn't implement start";
378 const std::string kInfoLogEntry = "[routing] started: listening on 127.0.0.1";
379 const std::string kWarningLogEntry =
380 "Can't connect to remote MySQL server for client";
381 // System/Note does not produce unique output today
382 const std::string kNoteLogEntry = "";
383 const std::string kSystemLogEntry = "";
384
385 // to trigger the warning entry in the log
386 const std::string kRoutingConfig =
387 "[routing]\n"
388 "bind_address=127.0.0.1:" +
389 std::to_string(router_port) +
390 "\n"
391 "destinations=localhost:" +
392 std::to_string(server_port) +
393 "\n"
394 "routing_strategy=round-robin\n";
395
396 auto conf_params = get_DEFAULT_defaults();
397 conf_params["logging_folder"] =
398 test_params.logging_folder_empty ? "" : tmp_dir.name();
399
400 TempDirectory conf_dir("conf");
401 const std::string conf_text =
402 test_params.logger_config + "\n" + kRoutingConfig;
403
404 const std::string conf_file =
405 create_config_file(conf_dir.name(), conf_text, &conf_params);
406
407 auto &router = launch_router({"-c", conf_file});
408
409 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
410
411 // try to make a connection; this will fail but should generate a warning in
412 // the logs
413 mysqlrouter::MySQLSession client;
414 try {
415 client.connect("127.0.0.1", router_port, "username", "password", "", "");
416 } catch (const std::exception &exc) {
417 if (std::string(exc.what()).find("Error connecting to MySQL server") !=
418 std::string::npos) {
419 // that's what we expect
420 } else
421 throw;
422 }
423
424 const std::string console_log_txt = router.get_full_output();
425
426 // check the console log if it contains what's expected
427 if (test_params.consolelog_expected_level >= LogLevel::kDebug &&
428 test_params.consolelog_expected_level != LogLevel::kNotSet) {
429 EXPECT_THAT(console_log_txt, HasSubstr(kDebugLogEntry)) << "console:\n"
430 << console_log_txt;
431 } else {
432 EXPECT_THAT(console_log_txt, Not(HasSubstr(kDebugLogEntry)))
433 << "console:\n"
434 << console_log_txt;
435 }
436
437 if (test_params.consolelog_expected_level >= LogLevel::kNote &&
438 test_params.consolelog_expected_level != LogLevel::kNotSet) {
439 // No NOTE output from Router today, so check that we see info
440 EXPECT_THAT(console_log_txt, HasSubstr(kInfoLogEntry)) << "console:\n"
441 << console_log_txt;
442 } else {
443 // No NOTE output from Router today, so disable until Router does
444 #if 0
445 EXPECT_THAT(console_log_txt, Not(HasSubstr(kInfoLogEntry)))
446 << "console:\n"
447 << console_log_txt;
448 #endif
449 }
450
451 if (test_params.consolelog_expected_level >= LogLevel::kInfo &&
452 test_params.consolelog_expected_level != LogLevel::kNotSet) {
453 EXPECT_THAT(console_log_txt, HasSubstr(kInfoLogEntry)) << "console:\n"
454 << console_log_txt;
455 } else {
456 EXPECT_THAT(console_log_txt, Not(HasSubstr(kInfoLogEntry)))
457 << "console:\n"
458 << console_log_txt;
459 }
460
461 if (test_params.consolelog_expected_level >= LogLevel::kWarning &&
462 test_params.consolelog_expected_level != LogLevel::kNotSet) {
463 EXPECT_THAT(console_log_txt, HasSubstr(kWarningLogEntry))
464 << "console:\n"
465 << console_log_txt;
466 } else {
467 EXPECT_THAT(console_log_txt, Not(HasSubstr(kWarningLogEntry)))
468 << "console:\n"
469 << console_log_txt;
470 }
471
472 if (test_params.consolelog_expected_level >= LogLevel::kSystem &&
473 test_params.consolelog_expected_level != LogLevel::kNotSet) {
474 // No SYSTEM output from Router today, so disable until Router does
475 #if 0
476 EXPECT_THAT(console_log_txt, HasSubstr(kSystemLogEntry)) << "console:\n"
477 << console_log_txt;
478 #endif
479 } else {
480 // No SYSTEM output from Router today, so disable until Router does
481 #if 0
482 EXPECT_THAT(console_log_txt, Not(HasSubstr(kSystemLogEntry)))
483 << "console:\n"
484 << console_log_txt;
485 #endif
486 }
487
488 // check the file log if it contains what's expected
489 const std::string file_log_txt =
490 router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
491
492 if (test_params.filelog_expected_level >= LogLevel::kDebug &&
493 test_params.filelog_expected_level != LogLevel::kNotSet) {
494 EXPECT_THAT(file_log_txt, HasSubstr(kDebugLogEntry))
495 << "file:\n"
496 << file_log_txt << "\nconsole:\n"
497 << console_log_txt;
498 } else {
499 EXPECT_THAT(file_log_txt, Not(HasSubstr(kDebugLogEntry)))
500 << "file:\n"
501 << file_log_txt << "\nconsole:\n"
502 << console_log_txt;
503 }
504
505 if (test_params.filelog_expected_level >= LogLevel::kNote &&
506 test_params.filelog_expected_level != LogLevel::kNotSet) {
507 // No NOTE output from Router today, so check that we see info
508 EXPECT_THAT(file_log_txt, HasSubstr(kInfoLogEntry))
509 << "file:\n"
510 << file_log_txt << "\nconsole:\n"
511 << console_log_txt;
512 } else {
513 // No NOTE output from Router today, so disable until Router does
514 #if 0
515 EXPECT_THAT(file_log_txt, Not(HasSubstr(kNoteLogEntry)))
516 << "file:\n"
517 << file_log_txt << "\nconsole:\n"
518 << console_log_txt;
519 #endif
520 }
521
522 if (test_params.filelog_expected_level >= LogLevel::kInfo &&
523 test_params.filelog_expected_level != LogLevel::kNotSet) {
524 EXPECT_THAT(file_log_txt, HasSubstr(kInfoLogEntry))
525 << "file:\n"
526 << file_log_txt << "\nconsole:\n"
527 << console_log_txt;
528 } else {
529 EXPECT_THAT(file_log_txt, Not(HasSubstr(kInfoLogEntry)))
530 << "file:\n"
531 << file_log_txt << "\nconsole:\n"
532 << console_log_txt;
533 }
534
535 if (test_params.filelog_expected_level >= LogLevel::kWarning &&
536 test_params.filelog_expected_level != LogLevel::kNotSet) {
537 EXPECT_THAT(file_log_txt, HasSubstr(kWarningLogEntry))
538 << "file:\n"
539 << file_log_txt << "\nconsole:\n"
540 << console_log_txt;
541 } else {
542 EXPECT_THAT(file_log_txt, Not(HasSubstr(kWarningLogEntry)))
543 << "file:\n"
544 << file_log_txt << "\nconsole:\n"
545 << console_log_txt;
546 }
547
548 if (test_params.filelog_expected_level >= LogLevel::kSystem &&
549 test_params.filelog_expected_level != LogLevel::kNotSet) {
550 // No SYSTEM output from Router today, so disable until Router does
551 #if 0
552 EXPECT_THAT(file_log_txt, HasSubstr(kSystemLogEntry))
553 << "file:\n"
554 << file_log_txt << "\nconsole:\n"
555 << console_log_txt;
556 #endif
557 } else {
558 // No SYSTEM output from Router today, so disable until Router does
559 #if 0
560 EXPECT_THAT(file_log_txt, Not(HasSubstr(kSystemLogEntry)))
561 << "file:\n"
562 << file_log_txt << "\nconsole:\n"
563 << console_log_txt;
564 #endif
565 }
566 }
567
568 INSTANTIATE_TEST_SUITE_P(
569 LoggingConfigTest, RouterLoggingTestConfig,
570 ::testing::Values(
571 // no logger section, no sinks sections
572 // logging_folder not empty so we are expected to log to the file
573 // with a warning level so info and debug logs will not be there
574 /*0*/ LoggingConfigOkParams(
575 "",
576 /* logging_folder_empty = */ false,
577 /* consolelog_expected_level = */ LogLevel::kNotSet,
578 /* filelog_expected_level = */ LogLevel::kWarning),
579
580 // no logger section, no sinks sections
581 // logging_folder empty so we are expected to log to the console
582 // with a warning level so info and debug logs will not be there
583 /*1*/
584 LoggingConfigOkParams(
585 "",
586 /* logging_folder_empty = */ true,
587 /* consolelog_expected_level = */ LogLevel::kWarning,
588 /* filelog_expected_level = */ LogLevel::kNotSet),
589
590 // logger section, no sinks sections
591 // logging_folder not empty so we are expected to log to the file
592 // with a warning level as level is not redefined in the [logger]
593 // section
594 /*2*/
595 LoggingConfigOkParams(
596 "[logger]",
597 /* logging_folder_empty = */ false,
598 /* consolelog_expected_level = */ LogLevel::kNotSet,
599 /* filelog_expected_level = */ LogLevel::kWarning),
600
601 // logger section, no sinks sections
602 // logging_folder not empty so we are expected to log to the file
603 // with a level defined in the logger section
604 /*3*/
605 LoggingConfigOkParams(
606 "[logger]\n"
607 "level=info\n",
608 /* logging_folder_empty = */ false,
609 /* consolelog_expected_level = */ LogLevel::kNotSet,
610 /* filelog_expected_level = */ LogLevel::kInfo),
611
612 // logger section, no sinks sections; logging_folder is empty so we are
613 // expected to log to the console with a level defined in the logger
614 // section
615 /*4*/
616 LoggingConfigOkParams(
617 "[logger]\n"
618 "level=info\n",
619 /* logging_folder_empty = */ true,
620 /* consolelog_expected_level = */ LogLevel::kInfo,
621 /* filelog_expected_level = */ LogLevel::kNotSet),
622
623 // consolelog configured as a sink; it does not have its section in the
624 // config but that is not an error; even though the logging folder is
625 // not empty, we still don't log to the file as sinks= setting wants use
626 // the console
627 /*5*/
628 LoggingConfigOkParams(
629 "[logger]\n"
630 "level=debug\n"
631 "sinks=consolelog\n",
632 /* logging_folder_empty = */ false,
633 /* consolelog_expected_level = */ LogLevel::kDebug,
634 /* filelog_expected_level = */ LogLevel::kNotSet),
635
636 // 2 sinks have sections but consolelog is not defined as a sink in the
637 // [logger] section so there should be no logging to the console (after
638 // [logger] is initialised; prior to that all is logged to the console
639 // by default)
640 /*6*/
641 LoggingConfigOkParams(
642 "[logger]\n"
643 "sinks=filelog\n"
644 "level=debug\n"
645 "[filelog]\n"
646 "[consolelog]\n"
647 "level=debug\n",
648 /* logging_folder_empty = */ false,
649 /* consolelog_expected_level = */ LogLevel::kNotSet,
650 /* filelog_expected_level = */ LogLevel::kDebug),
651
652 // 2 sinks, both should inherit log level from [logger] section (which
653 // is debug)
654 /*7*/
655 LoggingConfigOkParams(
656 "[logger]\n"
657 "sinks=filelog,consolelog\n"
658 "level=debug\n"
659 "[filelog]\n"
660 "[consolelog]\n",
661 /* logging_folder_empty = */ false,
662 /* consolelog_expected_level = */ LogLevel::kDebug,
663 /* filelog_expected_level = */ LogLevel::kDebug),
664
665 // 2 sinks, both should inherit log level from [logger] section (which
666 // is info); debug logs are not expected for both sinks
667 /*8*/
668 LoggingConfigOkParams(
669 "[logger]\n"
670 "sinks=filelog,consolelog\n"
671 "level=info\n"
672 "[filelog]\n"
673 "[consolelog]\n",
674 /* logging_folder_empty = */ false,
675 /* consolelog_expected_level = */ LogLevel::kInfo,
676 /* filelog_expected_level = */ LogLevel::kInfo),
677
678 // 2 sinks, both should inherit log level from [logger] section (which
679 // is warning); neither debug not info logs are not expected for both
680 // sinks
681 /*9*/
682 LoggingConfigOkParams(
683 "[logger]\n"
684 "sinks=filelog,consolelog\n"
685 "level=warning\n"
686 "[filelog]\n"
687 "[consolelog]\n",
688 /* logging_folder_empty = */ false,
689 /* consolelog_expected_level = */ LogLevel::kWarning,
690 /* filelog_expected_level = */ LogLevel::kWarning),
691
692 // 2 sinks, one overwrites the default log level, the other inherits
693 // default from [logger] section
694 /*10*/
695 LoggingConfigOkParams(
696 "[logger]\n"
697 "sinks=filelog,consolelog\n"
698 "level=info\n"
699 "[filelog]\n"
700 "level=debug\n"
701 "[consolelog]\n",
702 /* logging_folder_empty = */ false,
703 /* consolelog_expected_level = */ LogLevel::kInfo,
704 /* filelog_expected_level = */ LogLevel::kDebug),
705
706 // 2 sinks, each defines its own custom log level that overwrites the
707 // default from [logger] section
708 /*11*/
709 LoggingConfigOkParams(
710 "[logger]\n"
711 "sinks=filelog,consolelog\n"
712 "level=info\n"
713 "[filelog]\n"
714 "level=debug\n"
715 "[consolelog]\n"
716 "level=warning\n",
717 /* logging_folder_empty = */ false,
718 /* consolelog_expected_level = */ LogLevel::kWarning,
719 /* filelog_expected_level = */ LogLevel::kDebug),
720
721 // 2 sinks, each defines its own custom log level that overwrites the
722 // default from [logger] section
723 /*12*/
724 LoggingConfigOkParams(
725 "[logger]\n"
726 "sinks=filelog,consolelog\n"
727 "level=warning\n"
728 "[filelog]\n"
729 "level=info\n"
730 "[consolelog]\n"
731 "level=warning\n",
732 /* logging_folder_empty = */ false,
733 /* consolelog_expected_level = */ LogLevel::kWarning,
734 /* filelog_expected_level = */ LogLevel::kInfo),
735
736 // 2 sinks, each defines its own custom log level (that is more strict)
737 // that overwrites the default from [logger] section
738 /*13*/
739 LoggingConfigOkParams(
740 "[logger]\n"
741 "sinks=filelog,consolelog\n"
742 "level=debug\n"
743 "[filelog]\n"
744 "level=info\n"
745 "[consolelog]\n"
746 "level=warning\n",
747 /* logging_folder_empty = */ false,
748 /* consolelog_expected_level = */ LogLevel::kWarning,
749 /* filelog_expected_level = */ LogLevel::kInfo),
750
751 // 2 sinks,no level in the [logger] section and no level in the sinks
752 // sections; default log level should be used (which is warning)
753 /*14*/
754 LoggingConfigOkParams(
755 "[logger]\n"
756 "sinks=filelog,consolelog\n"
757 "[filelog]\n"
758 "[consolelog]\n",
759 /* logging_folder_empty = */ false,
760 /* consolelog_expected_level = */ LogLevel::kWarning,
761 /* filelog_expected_level = */ LogLevel::kWarning),
762
763 // 2 sinks, level in the [logger] section is warning; it should be
764 // used by the sinks as they don't redefine it in their sections
765 /*15*/
766 LoggingConfigOkParams(
767 "[logger]\n"
768 "level=warning\n"
769 "sinks=filelog,consolelog\n",
770 /* logging_folder_empty = */ false,
771 /* consolelog_expected_level = */ LogLevel::kWarning,
772 /* filelog_expected_level = */ LogLevel::kWarning),
773
774 // 2 sinks, level in the [logger] section is error; it should be used
775 // by the sinks as they don't redefine it in their sections
776 /*16*/
777 LoggingConfigOkParams(
778 "[logger]\n"
779 "level=error\n"
780 "sinks=filelog,consolelog\n",
781 /* logging_folder_empty = */ false,
782 /* consolelog_expected_level = */ LogLevel::kError,
783 /* filelog_expected_level = */ LogLevel::kError),
784
785 // 2 sinks, no level in the [logger] section, each defines it's own
786 // level
787 /*17*/
788 LoggingConfigOkParams(
789 "[logger]\n"
790 "sinks=filelog,consolelog\n"
791 "[filelog]\n"
792 "level=error\n"
793 "[consolelog]\n"
794 "level=debug\n",
795 /* logging_folder_empty = */ false,
796 /* consolelog_expected_level = */ LogLevel::kDebug,
797 /* filelog_expected_level = */ LogLevel::kError),
798
799 // 2 sinks, no level in the [logger] section, one defines it's own
800 // level, the other expected to go with default (warning)
801 /*18*/
802 LoggingConfigOkParams(
803 "[logger]\n"
804 "sinks=filelog,consolelog\n"
805 "[filelog]\n"
806 "level=error\n",
807 /* logging_folder_empty = */ false,
808 /* consolelog_expected_level = */ LogLevel::kWarning,
809 /* filelog_expected_level = */ LogLevel::kError),
810 // level note to filelog sink (TS_FR1_01)
811 // Note: Router does not log at NOTE now
812 /*19*/
813 LoggingConfigOkParams(
814 "[logger]\n"
815 "level=note\n"
816 "sinks=filelog\n",
817 /* logging_folder_empty = */ false,
818 /* consolelog_expected_level = */ LogLevel::kNotSet,
819 /* filelog_expected_level = */ LogLevel::kNote),
820 // note level to filelog sink (TS_FR1_02)
821 // Note: Router does not log at NOTE now
822 /*20*/
823 LoggingConfigOkParams(
824 "[logger]\n"
825 "level=system\n"
826 "sinks=filelog\n",
827 /* logging_folder_empty = */ false,
828 /* consolelog_expected_level = */ LogLevel::kNotSet,
829 /* filelog_expected_level = */ LogLevel::kSystem)));
830
831 #ifndef WIN32
832 INSTANTIATE_TEST_SUITE_P(
833 LoggingConfigTestUnix, RouterLoggingTestConfig,
834 ::testing::Values(
835 // We can't reliably check if the syslog logging is working with a
836 // component test as this is too operating system intrusive and we are
837 // supposed to run on pb2 environment. Let's at least check that this
838 // sink type is supported
839 // Level note to syslog,filelog (TS_FR1_06)
840 /*0*/
841 LoggingConfigOkParams(
842 "[logger]\n"
843 "level=note\n"
844 "sinks=syslog,filelog\n",
845 /* logging_folder_empty = */ false,
846 /* consolelog_expected_level = */ LogLevel::kNotSet,
847 /* filelog_expected_level = */ LogLevel::kNote),
848 // Level system to syslog,filelog (TS_FR1_07)
849 /*1*/
850 LoggingConfigOkParams(
851 "[logger]\n"
852 "level=system\n"
853 "sinks=syslog,filelog\n",
854 /* logging_folder_empty = */ false,
855 /* consolelog_expected_level = */ LogLevel::kNotSet,
856 /* filelog_expected_level = */ LogLevel::kSystem),
857 // All sinks (TS_FR1_08)
858 /*2*/
859 LoggingConfigOkParams(
860 "[logger]\n"
861 "level=debug\n"
862 "sinks=syslog,filelog,consolelog\n"
863 "[consolelog]\n"
864 "level=note\n"
865 "[syslog]\n"
866 "level=system\n",
867 /* logging_folder_empty = */ false,
868 /* consolelog_expected_level = */ LogLevel::kNote,
869 /* filelog_expected_level = */ LogLevel::kDebug),
870 // Verify filename option is disregarded by syslog sink
871 /*3*/
872 LoggingConfigOkParams(
873 "[logger]\n"
874 "level=note\n"
875 "sinks=syslog,filelog\n"
876 "[syslog]\n"
877 "filename=foo.log",
878 /* logging_folder_empty = */ false,
879 /* consolelog_expected_level = */ LogLevel::kNotSet,
880 /* filelog_expected_level = */ LogLevel::kNote)));
881 #else
882 INSTANTIATE_TEST_SUITE_P(
883 LoggingConfigTestWindows, RouterLoggingTestConfig,
884 ::testing::Values(
885 // We can't reliably check if the eventlog logging is working with a
886 // component test as this is too operating system intrusive and also
887 // requires admin priviledges to setup and we are supposed to run on pb2
888 // environment. Let's at least check that this sink type is supported.
889 // Level note to eventlog,filelog (TS_FR1_03)
890 /*0*/
891 LoggingConfigOkParams(
892 "[logger]\n"
893 "level=note\n"
894 "sinks=eventlog,filelog\n",
895 /* logging_folder_empty = */ false,
896 /* consolelog_expected_level = */ LogLevel::kNotSet,
897 /* filelog_expected_level = */ LogLevel::kNote),
898 // Level system to eventlog,filelog (TS_FR1_04)
899 /*1*/
900 LoggingConfigOkParams(
901 "[logger]\n"
902 "level=system\n"
903 "sinks=eventlog,filelog\n",
904 /* logging_folder_empty = */ false,
905 /* consolelog_expected_level = */ LogLevel::kNotSet,
906 /* filelog_expected_level = */ LogLevel::kSystem),
907 // All sinks with note and system included (TS_FR1_05)
908 /*2*/
909 LoggingConfigOkParams(
910 "[logger]\n"
911 "level=debug\n"
912 "sinks=eventlog,filelog,consolelog\n"
913 "[consolelog]\n"
914 "level=note\n"
915 "[eventlog]\n"
916 "level=system\n",
917 /* logging_folder_empty = */ false,
918 /* consolelog_expected_level = */ LogLevel::kNote,
919 /* filelog_expected_level = */ LogLevel::kDebug),
920 // Verify filename option is disregarded by eventlog sink
921 /*3*/
922 LoggingConfigOkParams(
923 "[logger]\n"
924 "level=system\n"
925 "sinks=eventlog,filelog\n"
926 "[eventlog]\n"
927 "filename=foo.log",
928 /* logging_folder_empty = */ false,
929 /* consolelog_expected_level = */ LogLevel::kNotSet,
930 /* filelog_expected_level = */ LogLevel::kSystem)));
931 #endif
932
933 /**************************************************/
934 /* Tests for logger configuration errors */
935 /**************************************************/
936
937 struct LoggingConfigErrorParams {
938 std::string logger_config;
939 bool logging_folder_empty;
940
941 std::string expected_error;
942
LoggingConfigErrorParamsLoggingConfigErrorParams943 LoggingConfigErrorParams(const std::string &logger_config_,
944 const bool logging_folder_empty_,
945 const std::string &expected_error_)
946 : logger_config(logger_config_),
947 logging_folder_empty(logging_folder_empty_),
948 expected_error(expected_error_) {}
949 };
950
operator <<(::std::ostream & os,const LoggingConfigErrorParams & ltp)951 ::std::ostream &operator<<(::std::ostream &os,
952 const LoggingConfigErrorParams <p) {
953 return os << "config=" << ltp.logger_config
954 << ", logging_folder_empty=" << ltp.logging_folder_empty;
955 }
956
957 class RouterLoggingConfigError
958 : public RouterLoggingTest,
959 public ::testing::WithParamInterface<LoggingConfigErrorParams> {};
960
961 /** @test This test verifies that a proper error gets printed on the console for
962 * a particular logging configuration
963 */
TEST_P(RouterLoggingConfigError,LoggingConfigError)964 TEST_P(RouterLoggingConfigError, LoggingConfigError) {
965 auto test_params = GetParam();
966
967 TempDirectory tmp_dir;
968 auto conf_params = get_DEFAULT_defaults();
969 conf_params["logging_folder"] =
970 test_params.logging_folder_empty ? "" : tmp_dir.name();
971
972 TempDirectory conf_dir("conf");
973 const std::string conf_text = "[keepalive]\n" + test_params.logger_config;
974
975 const std::string conf_file =
976 create_config_file(conf_dir.name(), conf_text, &conf_params);
977
978 auto &router = launch_router({"-c", conf_file}, 1);
979
980 check_exit_code(router, EXIT_FAILURE);
981
982 // the error happens during the logger initialization so we expect the message
983 // on the console which is the default sink until we switch to the
984 // configuration from the config file
985 const std::string console_log_txt = router.get_full_output();
986
987 EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
988 << "\nconsole:\n"
989 << console_log_txt;
990 }
991
992 INSTANTIATE_TEST_SUITE_P(
993 LoggingConfigError, RouterLoggingConfigError,
994 ::testing::Values(
995 // Unknown sink name in the [logger] section
996 /*0*/ LoggingConfigErrorParams(
997 "[logger]\n"
998 "sinks=unknown\n"
999 "level=debug\n",
1000 /* logging_folder_empty = */ false,
1001 /* expected_error = */
1002 "Configuration error: Unsupported logger sink type: 'unknown'"),
1003
1004 // Empty sinks option
1005 /*1*/
1006 LoggingConfigErrorParams(
1007 "[logger]\n"
1008 "sinks=\n",
1009 /* logging_folder_empty = */ false,
1010 /* expected_error = */
1011 "plugin 'logger' init failed: sinks option does not contain any "
1012 "valid sink name, was ''"),
1013
1014 // Empty sinks list
1015 /*2*/
1016 LoggingConfigErrorParams(
1017 "[logger]\n"
1018 "sinks=,\n",
1019 /* logging_folder_empty = */ false,
1020 /* expected_error = */
1021 "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1022
1023 // Leading comma on a sinks list
1024 /*3*/
1025 LoggingConfigErrorParams(
1026 "[logger]\n"
1027 "sinks=,consolelog\n",
1028 /* logging_folder_empty = */ false,
1029 /* expected_error = */
1030 "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1031
1032 // Terminating comma on a sinks list
1033 /*4*/
1034 LoggingConfigErrorParams(
1035 "[logger]\n"
1036 "sinks=consolelog,\n",
1037 /* logging_folder_empty = */ false,
1038 /* expected_error = */
1039 "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1040
1041 // Two commas separating sinks
1042 /*5*/
1043 LoggingConfigErrorParams(
1044 "[logger]\n"
1045 "sinks=consolelog,,filelog\n",
1046 /* logging_folder_empty = */ false,
1047 /* expected_error = */
1048 "plugin 'logger' init failed: Unsupported logger sink type: ''"),
1049
1050 // Empty space as a sink name
1051 /*6*/
1052 LoggingConfigErrorParams(
1053 "[logger]\n"
1054 "sinks= \n",
1055 /* logging_folder_empty = */ false,
1056 /* expected_error = */
1057 "plugin 'logger' init failed: sinks option does not contain any "
1058 "valid sink name, was ''"),
1059
1060 // Invalid log level in the [logger] section
1061 /*7*/
1062 LoggingConfigErrorParams(
1063 "[logger]\n"
1064 "sinks=consolelog\n"
1065 "level=invalid\n"
1066 "[consolelog]\n",
1067 /* logging_folder_empty = */ false,
1068 /* expected_error = */
1069 "Configuration error: Log level 'invalid' is not valid. Valid "
1070 "values are: debug, error, fatal, info, note, system, and warning"),
1071
1072 // Invalid log level in the sink section
1073 /*8*/
1074 LoggingConfigErrorParams(
1075 "[logger]\n"
1076 "sinks=consolelog\n"
1077 "[consolelog]\n"
1078 "level=invalid\n",
1079 /* logging_folder_empty = */ false,
1080 /* expected_error = */
1081 "Configuration error: Log level 'invalid' is not valid. Valid "
1082 "values are: debug, error, fatal, info, note, system, and warning"),
1083
1084 // Both level and sinks valuse invalid in the [logger] section
1085 /*9*/
1086 LoggingConfigErrorParams(
1087 "[logger]\n"
1088 "sinks=invalid\n"
1089 "level=invalid\n"
1090 "[consolelog]\n",
1091 /* logging_folder_empty = */ false,
1092 /* expected_error = */
1093 "Configuration error: Log level 'invalid' is not valid. Valid "
1094 "values are: debug, error, fatal, info, note, system, and warning"),
1095
1096 // Logging folder is empty but we request filelog as sink
1097 /*10*/
1098 LoggingConfigErrorParams(
1099 "[logger]\n"
1100 "sinks=filelog\n",
1101 /* logging_folder_empty = */ true,
1102 /* expected_error = */
1103 "plugin 'logger' init failed: filelog sink configured but the "
1104 "logging_folder is empty")));
1105
1106 #ifndef _WIN32
1107 INSTANTIATE_TEST_SUITE_P(
1108 LoggingConfigErrorUnix, RouterLoggingConfigError,
1109 ::testing::Values(
1110 // We can't reliably check if the syslog logging is working with a
1111 // component test as this is too operating system intrusive and we are
1112 // supposed to run on pb2 environment. Let's at least check that this
1113 // sink type is supported
1114 LoggingConfigErrorParams(
1115 "[logger]\n"
1116 "sinks=syslog\n"
1117 "[syslog]\n"
1118 "level=invalid\n",
1119 /* logging_folder_empty = */ false,
1120 /* expected_error = */
1121 "Configuration error: Log level 'invalid' is not valid. Valid "
1122 "values are: debug, error, fatal, info, note, system, and warning"),
1123
1124 // Let's also check that the eventlog is NOT supported
1125 LoggingConfigErrorParams(
1126 "[logger]\n"
1127 "sinks=eventlog\n"
1128 "[eventlog]\n"
1129 "level=invalid\n",
1130 /* logging_folder_empty = */ false,
1131 /* expected_error = */
1132 "Loading plugin for config-section '[eventlog]' failed")));
1133 #else
1134 INSTANTIATE_TEST_SUITE_P(
1135 LoggingConfigErrorWindows, RouterLoggingConfigError,
1136 ::testing::Values(
1137 // We can't reliably check if the eventlog logging is working with a
1138 // component test as this is too operating system intrusive and also
1139 // requires admin priviledges to setup and we are supposed to run on pb2
1140 // environment. Let's at least check that this sink type is supported
1141 LoggingConfigErrorParams(
1142 "[logger]\n"
1143 "sinks=eventlog\n"
1144 "[eventlog]\n"
1145 "level=invalid\n",
1146 /* logging_folder_empty = */ false,
1147 /* expected_error = */
1148 "Configuration error: Log level 'invalid' is not valid. Valid "
1149 "values are: debug, error, fatal, info, note, system, and warning"),
1150
1151 // Let's also check that the syslog is NOT supported
1152 LoggingConfigErrorParams(
1153 "[logger]\n"
1154 "sinks=syslog\n"
1155 "[syslog]\n"
1156 "level=invalid\n",
1157 /* logging_folder_empty = */ false,
1158 /* expected_error = */
1159 "Loading plugin for config-section '[syslog]' failed")));
1160 #endif
1161
1162 class RouterLoggingTestTimestampPrecisionConfig
1163 : public RouterLoggingTest,
1164 public ::testing::WithParamInterface<LoggingConfigOkParams> {};
1165
1166 #define DATE_REGEX "[0-9]{4}-[0-9]{2}-[0-9]{2}"
1167 #define TIME_REGEX "[0-9]{2}:[0-9]{2}:[0-9]{2}"
1168 #define TS_MSEC_REGEX ".[0-9]{3}"
1169 #define TS_USEC_REGEX ".[0-9]{6}"
1170 #define TS_NSEC_REGEX ".[0-9]{9}"
1171 #define TS_REGEX DATE_REGEX " " TIME_REGEX
1172
1173 const std::string kTimestampSecRegex = TS_REGEX " ";
1174 const std::string kTimestampMillisecRegex = TS_REGEX TS_MSEC_REGEX " ";
1175 const std::string kTimestampMicrosecRegex = TS_REGEX TS_USEC_REGEX " ";
1176 const std::string kTimestampNanosecRegex = TS_REGEX TS_NSEC_REGEX " ";
1177
1178 /** @test This test verifies that a proper loggs are written to selected sinks
1179 * for various sinks/levels combinations.
1180 */
TEST_P(RouterLoggingTestTimestampPrecisionConfig,LoggingTestTimestampPrecisionConfig)1181 TEST_P(RouterLoggingTestTimestampPrecisionConfig,
1182 LoggingTestTimestampPrecisionConfig) {
1183 auto test_params = GetParam();
1184
1185 TempDirectory tmp_dir;
1186 TcpPortPool port_pool;
1187 const auto router_port = port_pool.get_next_available();
1188 const auto server_port = port_pool.get_next_available();
1189
1190 // Different log entries that are expected for different levels, but we only
1191 // care that something is logged, not what, when checking timestamps.
1192
1193 // to trigger the warning entry in the log
1194 const std::string kRoutingConfig =
1195 "[routing]\n"
1196 "bind_address=127.0.0.1:" +
1197 std::to_string(router_port) +
1198 "\n"
1199 "destinations=localhost:" +
1200 std::to_string(server_port) +
1201 "\n"
1202 "routing_strategy=round-robin\n";
1203
1204 auto conf_params = get_DEFAULT_defaults();
1205 conf_params["logging_folder"] =
1206 test_params.logging_folder_empty ? "" : tmp_dir.name();
1207
1208 TempDirectory conf_dir("conf");
1209 const std::string conf_text =
1210 test_params.logger_config + "\n" + kRoutingConfig;
1211
1212 const std::string conf_file =
1213 create_config_file(conf_dir.name(), conf_text, &conf_params);
1214
1215 auto &router = launch_router({"-c", conf_file});
1216
1217 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
1218
1219 // try to make a connection; this will fail but should generate a warning in
1220 // the logs
1221 mysqlrouter::MySQLSession client;
1222 try {
1223 client.connect("127.0.0.1", router_port, "username", "password", "", "");
1224 } catch (const std::exception &exc) {
1225 if (std::string(exc.what()).find("Error connecting to MySQL server") !=
1226 std::string::npos) {
1227 // that's what we expect
1228 } else
1229 throw;
1230 }
1231
1232 // check the console log if it contains what's expected
1233 std::string console_log_txt = router.get_full_output();
1234
1235 // strip first line before checking if needed
1236 const std::string prefix = "logging facility initialized";
1237 if (std::mismatch(console_log_txt.begin(), console_log_txt.end(),
1238 prefix.begin(), prefix.end())
1239 .second == prefix.end()) {
1240 console_log_txt.erase(0, console_log_txt.find("\n") + 1);
1241 }
1242
1243 if (test_params.consolelog_expected_level != LogLevel::kNotSet) {
1244 switch (test_params.consolelog_expected_timestamp_precision) {
1245 case LogTimestampPrecision::kNotSet:
1246 case LogTimestampPrecision::kSec:
1247 // EXPECT 12:00:00
1248 EXPECT_TRUE(pattern_found(console_log_txt, kTimestampSecRegex))
1249 << console_log_txt;
1250 break;
1251 case LogTimestampPrecision::kMilliSec:
1252 // EXPECT 12:00:00.000
1253 EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMillisecRegex))
1254 << console_log_txt;
1255 break;
1256 case LogTimestampPrecision::kMicroSec:
1257 // EXPECT 12:00:00.000000
1258 EXPECT_TRUE(pattern_found(console_log_txt, kTimestampMicrosecRegex))
1259 << console_log_txt;
1260 break;
1261 case LogTimestampPrecision::kNanoSec:
1262 // EXPECT 12:00:00.000000000
1263 EXPECT_TRUE(pattern_found(console_log_txt, kTimestampNanosecRegex))
1264 << console_log_txt;
1265 break;
1266 }
1267 }
1268
1269 // check the file log if it contains what's expected
1270 std::string file_log_txt =
1271 router.get_full_logfile("mysqlrouter.log", tmp_dir.name());
1272
1273 // strip first line before checking if needed
1274 if (std::mismatch(file_log_txt.begin(), file_log_txt.end(), prefix.begin(),
1275 prefix.end())
1276 .second == prefix.end()) {
1277 file_log_txt.erase(0, file_log_txt.find("\n") + 1);
1278 }
1279
1280 if (test_params.filelog_expected_level != LogLevel::kNotSet) {
1281 switch (test_params.filelog_expected_timestamp_precision) {
1282 case LogTimestampPrecision::kNotSet:
1283 case LogTimestampPrecision::kSec:
1284 // EXPECT 12:00:00
1285 EXPECT_TRUE(pattern_found(file_log_txt, kTimestampSecRegex))
1286 << file_log_txt;
1287 break;
1288 case LogTimestampPrecision::kMilliSec:
1289 // EXPECT 12:00:00.000
1290 EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMillisecRegex))
1291 << file_log_txt;
1292 break;
1293 case LogTimestampPrecision::kMicroSec:
1294 // EXPECT 12:00:00.000000
1295 EXPECT_TRUE(pattern_found(file_log_txt, kTimestampMicrosecRegex))
1296 << file_log_txt;
1297 break;
1298 case LogTimestampPrecision::kNanoSec:
1299 // EXPECT 12:00:00.000000000
1300 EXPECT_TRUE(pattern_found(file_log_txt, kTimestampNanosecRegex))
1301 << file_log_txt;
1302 break;
1303 }
1304 }
1305 }
1306
1307 #define TS_FR1_1_STR(x) \
1308 "[logger]\n" \
1309 "level=debug\n" \
1310 "sinks=consolelog,filelog\n" \
1311 "timestamp_precision=" x \
1312 "\n" \
1313 "[consolelog]\n\n[filelog]\n\n"
1314
1315 #define TS_FR1_2_STR(x) TS_FR1_1_STR(x)
1316
1317 #define TS_FR1_3_STR(x) TS_FR1_1_STR(x)
1318
1319 INSTANTIATE_TEST_SUITE_P(
1320 LoggingConfigTimestampPrecisionTest,
1321 RouterLoggingTestTimestampPrecisionConfig,
1322 ::testing::Values(
1323 // no logger section, no sinks sections
1324 // logging_folder not empty so we are expected to log to the file
1325 // with a warning level so info and debug logs will not be there
1326 /*0*/ LoggingConfigOkParams(
1327 "",
1328 /* logging_folder_empty = */ false,
1329 /* consolelog_expected_level = */ LogLevel::kNotSet,
1330 /* filelog_expected_level = */ LogLevel::kWarning,
1331 /* consolelog_expected_timestamp_precision = */
1332 LogTimestampPrecision::kNotSet,
1333 /* filelog_expected_timestamp_precision = */
1334 LogTimestampPrecision::kNotSet),
1335 // Two sinks, common timestamp_precision
1336 /*** TS_FR1_1 ***/
1337 /*1*/ /*TS_FR1_1.1*/
1338 LoggingConfigOkParams(
1339 TS_FR1_1_STR("second"),
1340 /* logging_folder_empty = */ false,
1341 /* consolelog_expected_level = */ LogLevel::kDebug,
1342 /* filelog_expected_level = */ LogLevel::kDebug,
1343 /* consolelog_expected_timestamp_precision = */
1344 LogTimestampPrecision::kSec,
1345 /* filelog_expected_timestamp_precision = */
1346 LogTimestampPrecision::kSec),
1347 /*2*/ /*TS_FR1_1.2*/
1348 LoggingConfigOkParams(
1349 TS_FR1_1_STR("Second"),
1350 /* logging_folder_empty = */ false,
1351 /* consolelog_expected_level = */ LogLevel::kDebug,
1352 /* filelog_expected_level = */ LogLevel::kDebug,
1353 /* consolelog_expected_timestamp_precision = */
1354 LogTimestampPrecision::kSec,
1355 /* filelog_expected_timestamp_precision = */
1356 LogTimestampPrecision::kSec),
1357 /*3*/ /*TS_FR1_1.3*/
1358 LoggingConfigOkParams(
1359 TS_FR1_1_STR("sec"),
1360 /* logging_folder_empty = */ false,
1361 /* consolelog_expected_level = */ LogLevel::kDebug,
1362 /* filelog_expected_level = */ LogLevel::kDebug,
1363 /* consolelog_expected_timestamp_precision = */
1364 LogTimestampPrecision::kSec,
1365 /* filelog_expected_timestamp_precision = */
1366 LogTimestampPrecision::kSec),
1367 /*4*/ /*TS_FR1_1.4*/
1368 LoggingConfigOkParams(
1369 TS_FR1_1_STR("SEC"),
1370 /* logging_folder_empty = */ false,
1371 /* consolelog_expected_level = */ LogLevel::kDebug,
1372 /* filelog_expected_level = */ LogLevel::kDebug,
1373 /* consolelog_expected_timestamp_precision = */
1374 LogTimestampPrecision::kSec,
1375 /* filelog_expected_timestamp_precision = */
1376 LogTimestampPrecision::kSec),
1377 /*5*/ /*TS_FR1_1.5*/
1378 LoggingConfigOkParams(
1379 TS_FR1_1_STR("s"),
1380 /* logging_folder_empty = */ false,
1381 /* consolelog_expected_level = */ LogLevel::kDebug,
1382 /* filelog_expected_level = */ LogLevel::kDebug,
1383 /* consolelog_expected_timestamp_precision = */
1384 LogTimestampPrecision::kSec,
1385 /* filelog_expected_timestamp_precision = */
1386 LogTimestampPrecision::kSec),
1387 /*6*/ /*TS_FR1_1.6*/
1388 LoggingConfigOkParams(
1389 TS_FR1_1_STR("S"),
1390 /* logging_folder_empty = */ false,
1391 /* consolelog_expected_level = */ LogLevel::kDebug,
1392 /* filelog_expected_level = */ LogLevel::kDebug,
1393 /* consolelog_expected_timestamp_precision = */
1394 LogTimestampPrecision::kSec,
1395 /* filelog_expected_timestamp_precision = */
1396 LogTimestampPrecision::kSec),
1397 /*** TS_FR1_2 ***/
1398 /*7*/ /*TS_FR1_2.1*/
1399 LoggingConfigOkParams(
1400 TS_FR1_2_STR("millisecond"),
1401 /* logging_folder_empty = */ false,
1402 /* consolelog_expected_level = */ LogLevel::kDebug,
1403 /* filelog_expected_level = */ LogLevel::kDebug,
1404 /* consolelog_expected_timestamp_precision = */
1405 LogTimestampPrecision::kMilliSec,
1406 /* filelog_expected_timestamp_precision = */
1407 LogTimestampPrecision::kMilliSec),
1408 /*8*/ /*TS_FR1_2.2*/
1409 LoggingConfigOkParams(
1410 TS_FR1_2_STR("MILLISECOND"),
1411 /* logging_folder_empty = */ false,
1412 /* consolelog_expected_level = */ LogLevel::kDebug,
1413 /* filelog_expected_level = */ LogLevel::kDebug,
1414 /* consolelog_expected_timestamp_precision = */
1415 LogTimestampPrecision::kMilliSec,
1416 /* filelog_expected_timestamp_precision = */
1417 LogTimestampPrecision::kMilliSec),
1418 /*9*/ /*TS_FR1_2.3*/
1419 LoggingConfigOkParams(
1420 TS_FR1_2_STR("msec"),
1421 /* logging_folder_empty = */ false,
1422 /* consolelog_expected_level = */ LogLevel::kDebug,
1423 /* filelog_expected_level = */ LogLevel::kDebug,
1424 /* consolelog_expected_timestamp_precision = */
1425 LogTimestampPrecision::kMilliSec,
1426 /* filelog_expected_timestamp_precision = */
1427 LogTimestampPrecision::kMilliSec),
1428 /*10*/ /*TS_FR1_2.4*/
1429 LoggingConfigOkParams(
1430 TS_FR1_2_STR("MSEC"),
1431 /* logging_folder_empty = */ false,
1432 /* consolelog_expected_level = */ LogLevel::kDebug,
1433 /* filelog_expected_level = */ LogLevel::kDebug,
1434 /* consolelog_expected_timestamp_precision = */
1435 LogTimestampPrecision::kMilliSec,
1436 /* filelog_expected_timestamp_precision = */
1437 LogTimestampPrecision::kMilliSec),
1438 /*11*/ /*TS_FR1_2.5*/
1439 LoggingConfigOkParams(
1440 TS_FR1_2_STR("ms"),
1441 /* logging_folder_empty = */ false,
1442 /* consolelog_expected_level = */ LogLevel::kDebug,
1443 /* filelog_expected_level = */ LogLevel::kDebug,
1444 /* consolelog_expected_timestamp_precision = */
1445 LogTimestampPrecision::kMilliSec,
1446 /* filelog_expected_timestamp_precision = */
1447 LogTimestampPrecision::kMilliSec),
1448 /*12*/ /*TS_FR1_2.6*/
1449 LoggingConfigOkParams(
1450 TS_FR1_2_STR("MS"),
1451 /* logging_folder_empty = */ false,
1452 /* consolelog_expected_level = */ LogLevel::kDebug,
1453 /* filelog_expected_level = */ LogLevel::kDebug,
1454 /* consolelog_expected_timestamp_precision = */
1455 LogTimestampPrecision::kMilliSec,
1456 /* filelog_expected_timestamp_precision = */
1457 LogTimestampPrecision::kMilliSec),
1458 /*** TS_FR1_3 ***/
1459 /*13*/ /*TS_FR1_3.1*/
1460 LoggingConfigOkParams(
1461 TS_FR1_3_STR("microsecond"),
1462 /* logging_folder_empty = */ false,
1463 /* consolelog_expected_level = */ LogLevel::kDebug,
1464 /* filelog_expected_level = */ LogLevel::kDebug,
1465 /* consolelog_expected_timestamp_precision = */
1466 LogTimestampPrecision::kMicroSec,
1467 /* filelog_expected_timestamp_precision = */
1468 LogTimestampPrecision::kMicroSec),
1469 /*14*/ /*TS_FR1_3.2*/
1470 LoggingConfigOkParams(
1471 TS_FR1_3_STR("Microsecond"),
1472 /* logging_folder_empty = */ false,
1473 /* consolelog_expected_level = */ LogLevel::kDebug,
1474 /* filelog_expected_level = */ LogLevel::kDebug,
1475 /* consolelog_expected_timestamp_precision = */
1476 LogTimestampPrecision::kMicroSec,
1477 /* filelog_expected_timestamp_precision = */
1478 LogTimestampPrecision::kMicroSec),
1479 /*15*/ /*TS_FR1_3.3*/
1480 LoggingConfigOkParams(
1481 TS_FR1_3_STR("usec"),
1482 /* logging_folder_empty = */ false,
1483 /* consolelog_expected_level = */ LogLevel::kDebug,
1484 /* filelog_expected_level = */ LogLevel::kDebug,
1485 /* consolelog_expected_timestamp_precision = */
1486 LogTimestampPrecision::kMicroSec,
1487 /* filelog_expected_timestamp_precision = */
1488 LogTimestampPrecision::kMicroSec),
1489 /*16*/ /*TS_FR1_3.4*/
1490 LoggingConfigOkParams(
1491 TS_FR1_3_STR("UsEC"),
1492 /* logging_folder_empty = */ false,
1493 /* consolelog_expected_level = */ LogLevel::kDebug,
1494 /* filelog_expected_level = */ LogLevel::kDebug,
1495 /* consolelog_expected_timestamp_precision = */
1496 LogTimestampPrecision::kMicroSec,
1497 /* filelog_expected_timestamp_precision = */
1498 LogTimestampPrecision::kMicroSec),
1499 /*17*/ /*TS_FR1_3.5*/
1500 LoggingConfigOkParams(
1501 TS_FR1_3_STR("us"),
1502 /* logging_folder_empty = */ false,
1503 /* consolelog_expected_level = */ LogLevel::kDebug,
1504 /* filelog_expected_level = */ LogLevel::kDebug,
1505 /* consolelog_expected_timestamp_precision = */
1506 LogTimestampPrecision::kMicroSec,
1507 /* filelog_expected_timestamp_precision = */
1508 LogTimestampPrecision::kMicroSec),
1509 /*18*/ /*TS_FR1_3.5*/
1510 LoggingConfigOkParams(
1511 TS_FR1_3_STR("US"),
1512 /* logging_folder_empty = */ false,
1513 /* consolelog_expected_level = */ LogLevel::kDebug,
1514 /* filelog_expected_level = */ LogLevel::kDebug,
1515 /* consolelog_expected_timestamp_precision = */
1516 LogTimestampPrecision::kMicroSec,
1517 /* filelog_expected_timestamp_precision = */
1518 LogTimestampPrecision::kMicroSec),
1519 /*** TS_FR1_4 ***/
1520 /*19*/ /*TS_FR1_4.1*/
1521 LoggingConfigOkParams(
1522 TS_FR1_3_STR("nanosecond"),
1523 /* logging_folder_empty = */ false,
1524 /* consolelog_expected_level = */ LogLevel::kDebug,
1525 /* filelog_expected_level = */ LogLevel::kDebug,
1526 /* consolelog_expected_timestamp_precision = */
1527 LogTimestampPrecision::kNanoSec,
1528 /* filelog_expected_timestamp_precision = */
1529 LogTimestampPrecision::kNanoSec),
1530 /*20*/ /*TS_FR1_4.2*/
1531 LoggingConfigOkParams(
1532 TS_FR1_3_STR("NANOSECOND"),
1533 /* logging_folder_empty = */ false,
1534 /* consolelog_expected_level = */ LogLevel::kDebug,
1535 /* filelog_expected_level = */ LogLevel::kDebug,
1536 /* consolelog_expected_timestamp_precision = */
1537 LogTimestampPrecision::kNanoSec,
1538 /* filelog_expected_timestamp_precision = */
1539 LogTimestampPrecision::kNanoSec),
1540 /*21*/ /*TS_FR1_4.3*/
1541 LoggingConfigOkParams(
1542 TS_FR1_3_STR("nsec"),
1543 /* logging_folder_empty = */ false,
1544 /* consolelog_expected_level = */ LogLevel::kDebug,
1545 /* filelog_expected_level = */ LogLevel::kDebug,
1546 /* consolelog_expected_timestamp_precision = */
1547 LogTimestampPrecision::kNanoSec,
1548 /* filelog_expected_timestamp_precision = */
1549 LogTimestampPrecision::kNanoSec),
1550 /*22*/ /*TS_FR1_4.4*/
1551 LoggingConfigOkParams(
1552 TS_FR1_3_STR("nSEC"),
1553 /* logging_folder_empty = */ false,
1554 /* consolelog_expected_level = */ LogLevel::kDebug,
1555 /* filelog_expected_level = */ LogLevel::kDebug,
1556 /* consolelog_expected_timestamp_precision = */
1557 LogTimestampPrecision::kNanoSec,
1558 /* filelog_expected_timestamp_precision = */
1559 LogTimestampPrecision::kNanoSec),
1560 /*23*/ /*TS_FR1_4.5*/
1561 LoggingConfigOkParams(
1562 TS_FR1_3_STR("ns"),
1563 /* logging_folder_empty = */ false,
1564 /* consolelog_expected_level = */ LogLevel::kDebug,
1565 /* filelog_expected_level = */ LogLevel::kDebug,
1566 /* consolelog_expected_timestamp_precision = */
1567 LogTimestampPrecision::kNanoSec,
1568 /* filelog_expected_timestamp_precision = */
1569 LogTimestampPrecision::kNanoSec),
1570 /*24*/ /*TS_FR1_4.6*/
1571 LoggingConfigOkParams(
1572 TS_FR1_3_STR("NS"),
1573 /* logging_folder_empty = */ false,
1574 /* consolelog_expected_level = */ LogLevel::kDebug,
1575 /* filelog_expected_level = */ LogLevel::kDebug,
1576 /* consolelog_expected_timestamp_precision = */
1577 LogTimestampPrecision::kNanoSec,
1578 /* filelog_expected_timestamp_precision = */
1579 LogTimestampPrecision::kNanoSec),
1580 /*25*/ /*TS_FR4_2*/
1581 LoggingConfigOkParams(
1582 "[logger]\n"
1583 "level=debug\n"
1584 "sinks=filelog\n"
1585 "[filelog]\n"
1586 "timestamp_precision=ms\n",
1587 /* logging_folder_empty = */ false,
1588 /* consolelog_expected_level = */ LogLevel::kNotSet,
1589 /* filelog_expected_level = */ LogLevel::kDebug,
1590 /* consolelog_expected_timestamp_precision = */
1591 LogTimestampPrecision::kNotSet,
1592 /* filelog_expected_timestamp_precision = */
1593 LogTimestampPrecision::kMilliSec),
1594 /*26*/ /*TS_FR4_3*/
1595 LoggingConfigOkParams(
1596 "[logger]\n"
1597 "level=debug\n"
1598 "sinks=filelog,consolelog\n"
1599 "[consolelog]\n"
1600 "timestamp_precision=ns\n",
1601 /* logging_folder_empty = */ false,
1602 /* consolelog_expected_level = */ LogLevel::kDebug,
1603 /* filelog_expected_level = */ LogLevel::kDebug,
1604 /* consolelog_expected_timestamp_precision = */
1605 LogTimestampPrecision::kNanoSec,
1606 /* filelog_expected_timestamp_precision = */
1607 LogTimestampPrecision::kSec)));
1608
1609 INSTANTIATE_TEST_SUITE_P(
1610 LoggingConfigTimestampPrecisionError, RouterLoggingConfigError,
1611 ::testing::Values(
1612 // Unknown timestamp_precision value in a sink
1613 /*0*/ /*TS_FR3_1*/ LoggingConfigErrorParams(
1614 "[logger]\n"
1615 "sinks=consolelog\n"
1616 "[consolelog]\n"
1617 "timestamp_precision=unknown\n",
1618 /* logging_folder_empty = */ false,
1619 /* expected_error = */
1620 "Configuration error: Timestamp precision 'unknown' is not valid. "
1621 "Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
1622 "ns, nsec, s, sec, second, us, and usec"),
1623 // Unknown timestamp_precision value in the [logger] section
1624 /*1*/ /*TS_FR3_1*/
1625 LoggingConfigErrorParams(
1626 "[logger]\n"
1627 "sinks=consolelog,filelog\n"
1628 "timestamp_precision=unknown\n",
1629 /* logging_folder_empty = */ false,
1630 /* expected_error = */
1631 "Configuration error: Timestamp precision 'unknown' is not valid. "
1632 "Valid values are: microsecond, millisecond, ms, msec, nanosecond, "
1633 "ns, nsec, s, sec, second, us, and usec"),
1634 /*2*/ /*TS_FR4_1*/
1635 LoggingConfigErrorParams("[logger]\n"
1636 "sinks=consolelog,filelog\n"
1637 "timestamp_precision=ms\n"
1638 "timestamp_precision=ns\n",
1639 /* logging_folder_empty = */ false,
1640 /* expected_error = */
1641 "Configuration error: Option "
1642 "'timestamp_precision' already defined.")));
1643 #ifndef _WIN32
1644 INSTANTIATE_TEST_SUITE_P(
1645 LoggingConfigTimestampPrecisionErrorUnix, RouterLoggingConfigError,
1646 ::testing::Values(
1647 /*0*/ /* TS_HLD_1 */
1648 LoggingConfigErrorParams("[logger]\n"
1649 "sinks=syslog\n"
1650 "[syslog]\n"
1651 "timestamp_precision=ms\n",
1652 /* logging_folder_empty = */ false,
1653 /* expected_error = */
1654 "Configuration error: timestamp_precision not "
1655 "valid for 'syslog'")));
1656 #else
1657 INSTANTIATE_TEST_SUITE_P(
1658 LoggingConfigTimestampPrecisionErrorWindows, RouterLoggingConfigError,
1659 ::testing::Values(
1660 /*0*/ /* TS_HLD_3 */
1661 LoggingConfigErrorParams("[logger]\n"
1662 "sinks=eventlog\n"
1663 "[eventlog]\n"
1664 "timestamp_precision=ms\n",
1665 /* logging_folder_empty = */ false,
1666 /* expected_error = */
1667 "Configuration error: timestamp_precision not "
1668 "valid for 'eventlog'")));
1669 #endif
1670
TEST_F(RouterLoggingTest,very_long_router_name_gets_properly_logged)1671 TEST_F(RouterLoggingTest, very_long_router_name_gets_properly_logged) {
1672 // This test verifies that a very long router name gets truncated in the
1673 // logged message (this is done because if it doesn't happen, the entire
1674 // message will exceed log message max length, and then the ENTIRE message
1675 // will get truncated instead. It's better to truncate the long name rather
1676 // than the stuff that follows it).
1677 // Router should report the error on STDERR and exit
1678
1679 const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1680 TempDirectory bootstrap_dir;
1681
1682 const auto server_port = port_pool_.get_next_available();
1683
1684 // launch mock server and wait for it to start accepting connections
1685 auto &server_mock = launch_mysql_server_mock(json_stmts, server_port);
1686 ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1687
1688 constexpr char name[] =
1689 "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1690 "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1691 "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1692 "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery"
1693 "verylongname";
1694 static_assert(
1695 sizeof(name) > 255,
1696 "too long"); // log message max length is 256, we want something that
1697 // guarrantees the limit would be exceeded
1698
1699 // launch the router in bootstrap mode
1700 auto &router = launch_router(
1701 {
1702 "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1703 "--name",
1704 name,
1705 "-d",
1706 bootstrap_dir.name(),
1707 },
1708 1);
1709 // add login hook
1710 router.register_response("Please enter MySQL password for root: ",
1711 "fake-pass\n");
1712
1713 // wait for router to exit
1714 check_exit_code(router, EXIT_FAILURE);
1715
1716 // expect something like this to appear on STDERR
1717 // Error: Router name
1718 // 'veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryv...'
1719 // too long (max 255).
1720 const std::string out = router.get_full_output();
1721 EXPECT_THAT(out.c_str(),
1722 HasSubstr("Error: Router name "
1723 "'veryveryveryveryveryveryveryveryveryveryveryveryveryv"
1724 "eryveryveryveryveryveryv...' too long (max 255)."));
1725 }
1726
1727 /**
1728 * @test verify that debug logs are not written to console during boostrap if
1729 * bootstrap configuration file is not provided.
1730 */
TEST_F(RouterLoggingTest,is_debug_logs_disabled_if_no_bootstrap_config_file)1731 TEST_F(RouterLoggingTest, is_debug_logs_disabled_if_no_bootstrap_config_file) {
1732 const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1733
1734 TempDirectory bootstrap_dir;
1735
1736 const auto server_port = port_pool_.get_next_available();
1737
1738 // launch mock server and wait for it to start accepting connections
1739 auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1740 ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1741
1742 // launch the router in bootstrap mode
1743 auto &router = launch_router({
1744 "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1745 "--report-host",
1746 "dont.query.dns",
1747 "-d",
1748 bootstrap_dir.name(),
1749 });
1750
1751 // add login hook
1752 router.register_response("Please enter MySQL password for root: ",
1753 "fake-pass\n");
1754
1755 // check if the bootstraping was successful
1756 check_exit_code(router, EXIT_SUCCESS);
1757 EXPECT_THAT(router.get_full_output(),
1758 testing::Not(testing::HasSubstr("Executing query:")));
1759 }
1760
1761 /**
1762 * @test verify that debug logs are written to console during boostrap if
1763 * log_level is set to DEBUG in bootstrap configuration file.
1764 */
TEST_F(RouterLoggingTest,is_debug_logs_enabled_if_bootstrap_config_file)1765 TEST_F(RouterLoggingTest, is_debug_logs_enabled_if_bootstrap_config_file) {
1766 const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1767
1768 TempDirectory bootstrap_dir;
1769 TempDirectory bootstrap_conf;
1770
1771 const auto server_port = port_pool_.get_next_available();
1772
1773 // launch mock server and wait for it to start accepting connections
1774 auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1775 ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1776
1777 // launch the router in bootstrap mode
1778 std::string logger_section = "[logger]\nlevel = DEBUG\n";
1779 auto conf_params = get_DEFAULT_defaults();
1780 // we want to log to the console
1781 conf_params["logging_folder"] = "";
1782 std::string conf_file = ProcessManager::create_config_file(
1783 bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf", "",
1784 false);
1785
1786 auto &router = launch_router({
1787 "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1788 "--report-host",
1789 "dont.query.dns",
1790 "--force",
1791 "-d",
1792 bootstrap_dir.name(),
1793 "-c",
1794 conf_file,
1795 });
1796
1797 // add login hook
1798 router.register_response("Please enter MySQL password for root: ",
1799 "fake-pass\n");
1800
1801 // check if the bootstraping was successful
1802 check_exit_code(router, EXIT_SUCCESS);
1803 EXPECT_THAT(router.get_full_output(), testing::HasSubstr("Executing query:"));
1804 }
1805
1806 /**
1807 * @test verify that debug logs are written to mysqlrouter.log file during
1808 * bootstrap if loggin_folder is provided in bootstrap configuration file
1809 */
TEST_F(RouterLoggingTest,is_debug_logs_written_to_file_if_logging_folder)1810 TEST_F(RouterLoggingTest, is_debug_logs_written_to_file_if_logging_folder) {
1811 const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1812
1813 TempDirectory bootstrap_dir;
1814 TempDirectory bootstrap_conf;
1815
1816 const auto server_port = port_pool_.get_next_available();
1817
1818 // launch mock server and wait for it to start accepting connections
1819 auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1820 ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1821
1822 // create config with logging_folder set to that directory
1823 std::map<std::string, std::string> params = {{"logging_folder", ""}};
1824 params.at("logging_folder") = bootstrap_conf.name();
1825 TempDirectory conf_dir("conf");
1826 const std::string conf_file =
1827 create_config_file(conf_dir.name(), "[logger]\nlevel = DEBUG\n", ¶ms);
1828
1829 auto &router = launch_router({
1830 "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1831 "--report-host",
1832 "dont.query.dns",
1833 "--force",
1834 "-d",
1835 bootstrap_dir.name(),
1836 "-c",
1837 conf_file,
1838 });
1839
1840 // add login hook
1841 router.register_response("Please enter MySQL password for root: ",
1842 "fake-pass\n");
1843
1844 // check if the bootstraping was successful
1845 check_exit_code(router, EXIT_SUCCESS);
1846
1847 auto matcher = [](const std::string &line) -> bool {
1848 return line.find("Executing query:") != line.npos;
1849 };
1850
1851 EXPECT_TRUE(find_in_file(bootstrap_conf.name() + "/mysqlrouter.log", matcher,
1852 std::chrono::milliseconds(5000)))
1853 << router.get_full_logfile("mysqlrouter.log", bootstrap_conf.name());
1854 }
1855
1856 /**
1857 * @test verify that normal output is written to stdout during bootstrap if
1858 * logging_folder is not provided in bootstrap configuration file.
1859 *
1860 * @test verify that logs are not written to stdout during bootstrap.
1861 */
TEST_F(RouterLoggingTest,bootstrap_normal_logs_written_to_stdout)1862 TEST_F(RouterLoggingTest, bootstrap_normal_logs_written_to_stdout) {
1863 const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
1864
1865 TempDirectory bootstrap_dir;
1866 TempDirectory bootstrap_conf;
1867
1868 const auto server_port = port_pool_.get_next_available();
1869
1870 // launch mock server and wait for it to start accepting connections
1871 auto &server_mock = launch_mysql_server_mock(json_stmts, server_port, false);
1872 ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
1873
1874 // launch the router in bootstrap mode
1875 std::string logger_section = "[logger]\nlevel = DEBUG\n";
1876 auto conf_params = get_DEFAULT_defaults();
1877 // we want to log to the console
1878 conf_params["logging_folder"] = "";
1879 std::string conf_file = ProcessManager::create_config_file(
1880 bootstrap_conf.name(), logger_section, &conf_params, "bootstrap.conf", "",
1881 false);
1882
1883 auto &router = launch_router(
1884 {
1885 "--bootstrap=127.0.0.1:" + std::to_string(server_port),
1886 "--report-host",
1887 "dont.query.dns",
1888 "--force",
1889 "-d",
1890 bootstrap_dir.name(),
1891 "-c",
1892 conf_file,
1893 },
1894 0, /* expected error code */
1895 false /*false = capture only stdout*/);
1896
1897 // add login hook
1898 router.register_response("Please enter MySQL password for root: ",
1899 "fake-pass\n");
1900
1901 // check if the bootstraping was successful
1902 check_exit_code(router, EXIT_SUCCESS);
1903
1904 // check if logs are not written to output
1905 EXPECT_THAT(router.get_full_output(),
1906 testing::Not(testing::HasSubstr("Executing query:")));
1907
1908 // check if normal output is written to output
1909 EXPECT_THAT(router.get_full_output(),
1910 testing::HasSubstr("After this MySQL Router has been started "
1911 "with the generated configuration"));
1912
1913 EXPECT_THAT(router.get_full_output(),
1914 testing::HasSubstr("MySQL Classic protocol"));
1915
1916 EXPECT_THAT(router.get_full_output(), testing::HasSubstr("MySQL X protocol"));
1917 }
1918
1919 class MetadataCacheLoggingTest : public RouterLoggingTest {
1920 protected:
SetUp()1921 void SetUp() override {
1922 RouterLoggingTest::SetUp();
1923
1924 mysql_harness::DIM &dim = mysql_harness::DIM::instance();
1925 // RandomGenerator
1926 dim.set_RandomGenerator(
1927 []() {
1928 static mysql_harness::RandomGenerator rg;
1929 return &rg;
1930 },
1931 [](mysql_harness::RandomGeneratorInterface *) {});
1932
1933 cluster_nodes_ports = {port_pool_.get_next_available(),
1934 port_pool_.get_next_available(),
1935 port_pool_.get_next_available()};
1936 cluster_nodes_http_ports = {port_pool_.get_next_available(),
1937 port_pool_.get_next_available(),
1938 port_pool_.get_next_available()};
1939 router_port = port_pool_.get_next_available();
1940 metadata_cache_section = get_metadata_cache_section(cluster_nodes_ports);
1941 routing_section =
1942 get_metadata_cache_routing_section("PRIMARY", "round-robin", "");
1943 }
1944
get_metadata_cache_section(std::vector<uint16_t> ports)1945 std::string get_metadata_cache_section(std::vector<uint16_t> ports) {
1946 std::string metadata_caches = "bootstrap_server_addresses=";
1947
1948 for (auto it = ports.begin(); it != ports.end(); ++it) {
1949 metadata_caches += (it == ports.begin()) ? "" : ",";
1950 metadata_caches += "mysql://localhost:" + std::to_string(*it);
1951 }
1952 metadata_caches += "\n";
1953
1954 return "[metadata_cache:test]\n"
1955 "router_id=1\n" +
1956 metadata_caches +
1957 "user=mysql_router1_user\n"
1958 "metadata_cluster=test\n"
1959 "connect_timeout=1\n"
1960 "ttl=0.1\n\n";
1961 }
1962
get_metadata_cache_routing_section(const std::string & role,const std::string & strategy,const std::string & mode="")1963 std::string get_metadata_cache_routing_section(const std::string &role,
1964 const std::string &strategy,
1965 const std::string &mode = "") {
1966 std::string result =
1967 "[routing:test_default]\n"
1968 "bind_port=" +
1969 std::to_string(router_port) + "\n" +
1970 "destinations=metadata-cache://test/default?role=" + role + "\n" +
1971 "protocol=classic\n";
1972
1973 if (!strategy.empty())
1974 result += std::string("routing_strategy=" + strategy + "\n");
1975 if (!mode.empty()) result += std::string("mode=" + mode + "\n");
1976
1977 return result;
1978 }
1979
init_keyring_and_config_file(const std::string & conf_dir,bool log_to_console=false)1980 std::string init_keyring_and_config_file(const std::string &conf_dir,
1981 bool log_to_console = false) {
1982 auto default_section = get_DEFAULT_defaults();
1983 init_keyring(default_section, temp_test_dir.name());
1984 default_section["logging_folder"] =
1985 log_to_console ? "" : get_logging_dir().str();
1986 return create_config_file(
1987 conf_dir,
1988 "[logger]\nlevel = DEBUG\n" + metadata_cache_section + routing_section,
1989 &default_section);
1990 }
1991
1992 TempDirectory temp_test_dir;
1993 std::vector<uint16_t> cluster_nodes_ports;
1994 std::vector<uint16_t> cluster_nodes_http_ports;
1995 uint16_t router_port;
1996 std::string metadata_cache_section;
1997 std::string routing_section;
1998 };
1999
2000 /**
2001 * @test verify if error message is logged if router cannot connect to any
2002 * metadata server.
2003 */
TEST_F(MetadataCacheLoggingTest,log_error_when_cannot_connect_to_any_metadata_server)2004 TEST_F(MetadataCacheLoggingTest,
2005 log_error_when_cannot_connect_to_any_metadata_server) {
2006 TempDirectory conf_dir;
2007
2008 // launch the router with metadata-cache configuration
2009 auto &router = ProcessManager::launch_router(
2010 {"-c", init_keyring_and_config_file(conf_dir.name())});
2011 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2012
2013 // expect something like this to appear on STDERR
2014 // 2017-12-21 17:22:35 metadata_cache ERROR [7ff0bb001700] Failed connecting
2015 // with any of the 3 metadata servers
2016 auto matcher = [](const std::string &line) -> bool {
2017 return line.find("metadata_cache ERROR") != line.npos &&
2018 line.find(
2019 "Failed fetching metadata from any of the 3 metadata servers") !=
2020 line.npos;
2021 };
2022
2023 auto log_file = get_logging_dir();
2024 log_file.append("mysqlrouter.log");
2025 EXPECT_TRUE(
2026 find_in_file(log_file.str(), matcher, std::chrono::milliseconds(5000)))
2027 << router.get_full_logfile();
2028 }
2029
2030 /**
2031 * @test verify if appropriate warning messages are logged when cannot connect
2032 * to first metadata server, but can connect to another one.
2033 */
TEST_F(MetadataCacheLoggingTest,log_warning_when_cannot_connect_to_first_metadata_server)2034 TEST_F(MetadataCacheLoggingTest,
2035 log_warning_when_cannot_connect_to_first_metadata_server) {
2036 TempDirectory conf_dir("conf");
2037
2038 // launch second metadata server
2039 const auto http_port = cluster_nodes_http_ports[1];
2040 auto &server = launch_mysql_server_mock(
2041 get_data_dir().join("metadata_3_nodes_first_not_accessible.js").str(),
2042 cluster_nodes_ports[1], EXIT_SUCCESS, false, http_port);
2043 ASSERT_NO_FATAL_FAILURE(check_port_ready(server, cluster_nodes_ports[1]));
2044 EXPECT_TRUE(MockServerRestClient(http_port).wait_for_rest_endpoint_ready());
2045 set_mock_metadata(http_port, "", cluster_nodes_ports);
2046
2047 // launch the router with metadata-cache configuration
2048 auto &router = ProcessManager::launch_router(
2049 {"-c", init_keyring_and_config_file(conf_dir.name())});
2050 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port));
2051
2052 // expect something like this to appear on STDERR
2053 // 2017-12-21 17:22:35 metadata_cache WARNING [7ff0bb001700] Failed connecting
2054 // with Metadata Server 127.0.0.1:7002: Can't connect to MySQL server on
2055 // '127.0.0.1' (111) (2003)
2056 auto info_matcher = [&](const std::string &line) -> bool {
2057 return line.find("metadata_cache WARNING") != line.npos &&
2058 line.find("Failed connecting with Metadata Server 127.0.0.1:" +
2059 std::to_string(cluster_nodes_ports[0])) != line.npos;
2060 };
2061
2062 EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
2063 info_matcher, 10000ms))
2064 << router.get_full_logfile();
2065
2066 auto warning_matcher = [](const std::string &line) -> bool {
2067 return line.find("metadata_cache WARNING") != line.npos &&
2068 line.find(
2069 "While updating metadata, could not establish a connection to "
2070 "replicaset") != line.npos;
2071 };
2072 EXPECT_TRUE(find_in_file(get_logging_dir().str() + "/mysqlrouter.log",
2073 warning_matcher, 10000ms))
2074 << router.get_full_logfile();
2075 }
2076
2077 #ifndef _WIN32
2078 /**
2079 * @test Checks that the logs rotation works (meaning Router will recreate
2080 * it's log file when it was moved and HUP singnal was sent to the Router).
2081 */
TEST_F(MetadataCacheLoggingTest,log_rotation_by_HUP_signal)2082 TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal) {
2083 TempDirectory conf_dir;
2084
2085 // launch the router with metadata-cache configuration
2086 auto &router = ProcessManager::launch_router(
2087 {"-c", init_keyring_and_config_file(conf_dir.name())});
2088 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2089
2090 std::this_thread::sleep_for(500ms);
2091
2092 auto log_file = get_logging_dir();
2093 log_file.append("mysqlrouter.log");
2094
2095 EXPECT_TRUE(log_file.exists());
2096
2097 // now let's simulate what logrotate script does
2098 // move the log_file appending '.1' to its name
2099 auto log_file_1 = get_logging_dir();
2100 log_file_1.append("mysqlrouter.log.1");
2101 mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2102 const auto pid = static_cast<pid_t>(router.get_pid());
2103 ::kill(pid, SIGHUP);
2104
2105 // let's wait until something new gets logged (metadata cache TTL has
2106 // expired), to be sure the default file that we moved is back.
2107 // Now both old and new files should exist
2108 unsigned retries = 10;
2109 const auto kSleep = 100ms;
2110 do {
2111 std::this_thread::sleep_for(kSleep);
2112 } while ((--retries > 0) && !log_file.exists());
2113
2114 EXPECT_TRUE(log_file.exists()) << router.get_full_logfile();
2115 EXPECT_TRUE(log_file_1.exists());
2116 }
2117
2118 /**
2119 * @test Checks that the Router continues to log to the file when the
2120 * SIGHUP gets sent to it and no file replacement is done.
2121 */
TEST_F(MetadataCacheLoggingTest,log_rotation_by_HUP_signal_no_file_move)2122 TEST_F(MetadataCacheLoggingTest, log_rotation_by_HUP_signal_no_file_move) {
2123 TempDirectory conf_dir;
2124
2125 // launch the router with metadata-cache configuration
2126 auto &router = ProcessManager::launch_router(
2127 {"-c", init_keyring_and_config_file(conf_dir.name())});
2128 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2129
2130 std::this_thread::sleep_for(500ms);
2131
2132 auto log_file = get_logging_dir();
2133 log_file.append("mysqlrouter.log");
2134
2135 EXPECT_TRUE(log_file.exists());
2136
2137 // grab the current log content
2138 const std::string log_content = router.get_full_logfile();
2139
2140 // send the log-rotate signal
2141 const auto pid = static_cast<pid_t>(router.get_pid());
2142 ::kill(pid, SIGHUP);
2143
2144 // wait until something new gets logged;
2145 std::string log_content_2;
2146 unsigned step = 0;
2147 do {
2148 std::this_thread::sleep_for(100ms);
2149 log_content_2 = router.get_full_logfile();
2150 } while ((log_content_2 == log_content) && (step++ < 20));
2151
2152 // The logfile should still exist
2153 EXPECT_TRUE(log_file.exists());
2154 // It should still contain what was there before and more (Router should keep
2155 // logging)
2156 EXPECT_THAT(log_content_2, StartsWith(log_content));
2157 EXPECT_STRNE(log_content_2.c_str(), log_content.c_str());
2158 }
2159
2160 /**
2161 * @test Checks that the logs Router continues to log to the file when the
2162 * SIGHUP gets sent to it and no file replacement is done.
2163 */
TEST_F(MetadataCacheLoggingTest,log_rotation_when_router_restarts)2164 TEST_F(MetadataCacheLoggingTest, log_rotation_when_router_restarts) {
2165 TempDirectory conf_dir;
2166
2167 // launch the router with metadata-cache configuration
2168 auto &router = ProcessManager::launch_router(
2169 {"-c", init_keyring_and_config_file(conf_dir.name())});
2170 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2171
2172 std::this_thread::sleep_for(500ms);
2173
2174 auto log_file = get_logging_dir();
2175 log_file.append("mysqlrouter.log");
2176
2177 EXPECT_TRUE(log_file.exists());
2178
2179 // now stop the router
2180 int res = router.kill();
2181 EXPECT_EQ(EXIT_SUCCESS, res) << router.get_full_output();
2182
2183 // move the log_file appending '.1' to its name
2184 auto log_file_1 = get_logging_dir();
2185 log_file_1.append("mysqlrouter.log.1");
2186 mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2187
2188 // make the new file read-only
2189 chmod(log_file_1.c_str(), S_IRUSR);
2190
2191 // start the router again and check that the new log file got created
2192 auto &router2 = ProcessManager::launch_router(
2193 {"-c", init_keyring_and_config_file(conf_dir.name())});
2194 ASSERT_NO_FATAL_FAILURE(check_port_ready(router2, router_port, 10000ms));
2195 std::this_thread::sleep_for(500ms);
2196 EXPECT_TRUE(log_file.exists());
2197 }
2198
2199 /**
2200 * @test Checks that the logs Router continues to log to the file when the
2201 * SIGHUP gets sent to it and no file replacement is done.
2202 */
TEST_F(MetadataCacheLoggingTest,log_rotation_read_only)2203 TEST_F(MetadataCacheLoggingTest, log_rotation_read_only) {
2204 TempDirectory conf_dir;
2205
2206 // launch the router with metadata-cache configuration
2207 auto &router = ProcessManager::launch_router(
2208 {"-c", init_keyring_and_config_file(conf_dir.name())}, EXIT_FAILURE);
2209 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2210
2211 auto log_file = get_logging_dir();
2212 log_file.append("mysqlrouter.log");
2213
2214 unsigned retries = 5;
2215 const auto kSleep = 100ms;
2216 do {
2217 std::this_thread::sleep_for(kSleep);
2218 } while ((--retries > 0) && !log_file.exists());
2219
2220 EXPECT_TRUE(log_file.exists());
2221
2222 // move the log_file appending '.1' to its name
2223 auto log_file_1 = get_logging_dir();
2224 log_file_1.append("mysqlrouter.log.1");
2225 mysqlrouter::rename_file(log_file.str(), log_file_1.str());
2226
2227 // "manually" recreate the log file and make it read only
2228 {
2229 std::ofstream logf(log_file.str());
2230 EXPECT_TRUE(logf.good());
2231 }
2232 chmod(log_file.c_str(), S_IRUSR);
2233
2234 // send the log-rotate signal
2235 const auto pid = static_cast<pid_t>(router.get_pid());
2236 ::kill(pid, SIGHUP);
2237
2238 // we expect the router to exit,
2239 // as the logfile is no longer usable it will fallback to logging to the
2240 // stderr
2241 check_exit_code(router, EXIT_FAILURE);
2242 EXPECT_THAT(router.get_full_output(),
2243 HasSubstr("File exists, but cannot open for writing"));
2244 EXPECT_THAT(router.get_full_output(), HasSubstr("Unloading all plugins."));
2245 }
2246
2247 /**
2248 * @test Checks that the logs rotation does not cause any crash in case of
2249 * not logging to the file (logging_foler empty == logging to the std:cerr)
2250 */
TEST_F(MetadataCacheLoggingTest,log_rotation_stdout)2251 TEST_F(MetadataCacheLoggingTest, log_rotation_stdout) {
2252 TempDirectory conf_dir;
2253
2254 // launch the router with metadata-cache configuration
2255 auto &router = ProcessManager::launch_router(
2256 {"-c",
2257 init_keyring_and_config_file(conf_dir.name(), /*log_to_console=*/true)});
2258 ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
2259
2260 std::this_thread::sleep_for(200ms);
2261 const auto pid = static_cast<pid_t>(router.get_pid());
2262 ::kill(pid, SIGHUP);
2263 std::this_thread::sleep_for(200ms);
2264 }
2265
2266 #endif
2267
2268 /**************************************************/
2269 /* Tests for valid logger filename configurations */
2270 /**************************************************/
2271
2272 #define DEFAULT_LOGFILE_NAME "mysqlrouter.log"
2273 #define USER_LOGFILE_NAME "foo.log"
2274 #define USER_LOGFILE_NAME_2 "bar.log"
2275
2276 struct LoggingConfigFilenameOkParams {
2277 std::string logger_config;
2278 std::string filename;
2279 bool console_to_stderr;
2280
LoggingConfigFilenameOkParamsLoggingConfigFilenameOkParams2281 LoggingConfigFilenameOkParams(const std::string &logger_config_,
2282 const std::string filename_)
2283 : logger_config(logger_config_),
2284 filename(filename_),
2285 console_to_stderr(true) {}
2286
LoggingConfigFilenameOkParamsLoggingConfigFilenameOkParams2287 LoggingConfigFilenameOkParams(const std::string &logger_config_,
2288 const std::string filename_,
2289 bool console_to_stderr_)
2290 : logger_config(logger_config_),
2291 filename(filename_),
2292 console_to_stderr(console_to_stderr_) {}
2293 };
2294
2295 class RouterLoggingTestConfigFilename
2296 : public RouterLoggingTest,
2297 public ::testing::WithParamInterface<LoggingConfigFilenameOkParams> {};
2298
2299 /** @test This test verifies that a proper log filename is written to
2300 * for various sinks/filename combinations.
2301 */
TEST_P(RouterLoggingTestConfigFilename,LoggingTestConfigFilename)2302 TEST_P(RouterLoggingTestConfigFilename, LoggingTestConfigFilename) {
2303 auto test_params = GetParam();
2304
2305 TempDirectory tmp_dir;
2306 auto conf_params = get_DEFAULT_defaults();
2307 conf_params["logging_folder"] = tmp_dir.name();
2308
2309 TempDirectory conf_dir("conf");
2310 const std::string conf_text = "[routing]\n\n" + test_params.logger_config;
2311 const std::string conf_file =
2312 create_config_file(conf_dir.name(), conf_text, &conf_params);
2313
2314 // empty routing section results in a failure, but while logging to file
2315 auto &router = launch_router({"-c", conf_file}, 1);
2316 check_exit_code(router, EXIT_FAILURE);
2317
2318 // check the file log if it contains what's expected
2319 const std::string file_log_txt =
2320 router.get_full_logfile(test_params.filename, tmp_dir.name());
2321
2322 EXPECT_THAT(file_log_txt, HasSubstr("plugin 'routing' init failed"))
2323 << "\file_log_txt:\n"
2324 << file_log_txt;
2325 }
2326
2327 INSTANTIATE_TEST_SUITE_P(
2328 LoggingTestConfigFilename, RouterLoggingTestConfigFilename,
2329 ::testing::Values(
2330 // default filename in logger section
2331 /*0*/
2332 LoggingConfigFilenameOkParams("[logger]\n"
2333 "filename=" DEFAULT_LOGFILE_NAME "\n",
2334 DEFAULT_LOGFILE_NAME),
2335 // TS_FR01_01 user defined logfile name in logger section
2336 /*1*/
2337 LoggingConfigFilenameOkParams("[logger]\n"
2338 "filename=" USER_LOGFILE_NAME "\n",
2339 USER_LOGFILE_NAME),
2340 // TS_FR01_02 user defined logfile name in filelog sink
2341 /*2*/
2342 LoggingConfigFilenameOkParams("[logger]\n"
2343 "sinks=filelog\n"
2344 "[filelog]\n"
2345 "filename=" USER_LOGFILE_NAME "\n",
2346 USER_LOGFILE_NAME),
2347 // TS_FR04_09 user defined logfile name in filelog sink overrides user
2348 // defined logfile name in logger section
2349 /*3*/
2350 LoggingConfigFilenameOkParams("[logger]\n"
2351 "sinks=filelog\n"
2352 "filename=" USER_LOGFILE_NAME "\n"
2353 "[filelog]\n"
2354 "filename=" USER_LOGFILE_NAME_2 "\n",
2355 USER_LOGFILE_NAME_2),
2356 // TS_FR05_01 empty logger filename logs to default logfile name
2357 /*4*/
2358 LoggingConfigFilenameOkParams("[logger]\n"
2359 "filename=\n",
2360 DEFAULT_LOGFILE_NAME),
2361 // TS_FR05_02 empty filelog filename logs to default logfile name
2362 /*5*/
2363 LoggingConfigFilenameOkParams("[logger]\n"
2364 "sinks=filelog\n"
2365 "[filelog]\n"
2366 "filename=\n",
2367 DEFAULT_LOGFILE_NAME),
2368 // TS_FR04_11 empty filelog filename logs to userdefined logger filename
2369 /*6*/
2370 LoggingConfigFilenameOkParams("[logger]\n"
2371 "filename=" USER_LOGFILE_NAME "\n"
2372 "sinks=filelog\n"
2373 "[filelog]\n"
2374 "filename=\n",
2375 USER_LOGFILE_NAME),
2376 // TS_FR04_12 undefined filelog filename logs to userdefined value for
2377 // logger filename
2378 /*7*/
2379 LoggingConfigFilenameOkParams("[logger]\n"
2380 "filename=" USER_LOGFILE_NAME "\n"
2381 "sinks=filelog\n"
2382 "[filelog]\n",
2383 USER_LOGFILE_NAME),
2384 // user defined logfile name in filelog sink overrides logger section
2385 /*8*/
2386 LoggingConfigFilenameOkParams("[logger]\n"
2387 "sinks=filelog\n"
2388 "filename=" DEFAULT_LOGFILE_NAME "\n"
2389 "[filelog]\n"
2390 "filename=" USER_LOGFILE_NAME "\n",
2391 USER_LOGFILE_NAME),
2392 // TS_FR04_01 empty filename has no effect
2393 /*9*/
2394 LoggingConfigFilenameOkParams("[logger]\n"
2395 "sinks=filelog\n"
2396 "filename=\n"
2397 "[filelog]\n"
2398 "filename=" USER_LOGFILE_NAME_2 "\n",
2399 USER_LOGFILE_NAME_2),
2400 // TS_FR04_03 empty filenames has no effect, and logs to default
2401 /*10*/
2402 LoggingConfigFilenameOkParams("[logger]\n"
2403 "sinks=filelog\n"
2404 "filename=\n"
2405 "[filelog]\n"
2406 "filename=\n",
2407 DEFAULT_LOGFILE_NAME),
2408 // TS_FR04_04 no filenames results in logging to default
2409 /*11*/
2410 LoggingConfigFilenameOkParams("[logger]\n"
2411 "sinks=filelog\n"
2412 "[filelog]\n",
2413 DEFAULT_LOGFILE_NAME)));
2414
2415 #define NOT_USED ""
2416
2417 #ifndef WIN32
2418 #define NULL_DEVICE_NAME "/dev/null"
2419 #define STDOUT_DEVICE_NAME "/dev/stdout"
2420 #define STDERR_DEVICE_NAME "/dev/stderr"
2421 #else
2422 #define NULL_DEVICE_NAME "NUL"
2423 #define STDOUT_DEVICE_NAME "CON"
2424 // No STDERR equivalent for WIN32
2425 #endif
2426
2427 class RouterLoggingTestConfigFilenameDevices
2428 : public RouterLoggingTest,
2429 public ::testing::WithParamInterface<LoggingConfigFilenameOkParams> {};
2430
2431 /** @test This test verifies that consolelog destination may be set to various
2432 * devices
2433 */
TEST_P(RouterLoggingTestConfigFilenameDevices,LoggingTestConsoleDestinationDevices)2434 TEST_P(RouterLoggingTestConfigFilenameDevices,
2435 LoggingTestConsoleDestinationDevices) {
2436 // FIXME: Unfortunately due to the limitations of our component testing
2437 // framework, this test has a flaw: it is not possible to distinguish if the
2438 // output returned from router.get_full_output() appeared on STDERR or STDOUT.
2439 // This should be fixed in the future.
2440 auto test_params = GetParam();
2441 bool console_empty =
2442 (test_params.filename.compare(NULL_DEVICE_NAME) == 0 ? true : false);
2443
2444 Path destination(test_params.filename);
2445 #ifndef WIN32
2446 EXPECT_TRUE(destination.exists());
2447 #endif
2448
2449 TempDirectory tmp_dir;
2450 auto conf_params = get_DEFAULT_defaults();
2451 conf_params["logging_folder"] = tmp_dir.name();
2452
2453 TempDirectory conf_dir("conf");
2454 const std::string conf_text =
2455 "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\ndestination=" +
2456 destination.str();
2457 const std::string conf_file =
2458 create_config_file(conf_dir.name(), conf_text, &conf_params);
2459
2460 // empty routing section results in a failure, but while logging to file
2461 auto &router =
2462 launch_router({"-c", conf_file}, 1, test_params.console_to_stderr);
2463 check_exit_code(router, EXIT_FAILURE);
2464
2465 const std::string console_log_txt = router.get_full_output();
2466 if (console_empty) {
2467 // Expect the console log to be empty
2468 EXPECT_TRUE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2469 } else {
2470 // Expect the console log to not be empty
2471 EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2472 }
2473
2474 // expect no default router file created in the logging folder
2475 Path shouldnotexist = Path(tmp_dir.name()).join(DEFAULT_LOGFILE_NAME);
2476 EXPECT_FALSE(shouldnotexist.exists());
2477 shouldnotexist = Path("/dev").join(DEFAULT_LOGFILE_NAME);
2478 EXPECT_FALSE(shouldnotexist.exists());
2479
2480 #ifndef WIN32
2481 EXPECT_TRUE(destination.exists());
2482 #endif
2483 }
2484
2485 INSTANTIATE_TEST_SUITE_P(
2486 LoggingTestConsoleDestinationDevices,
2487 RouterLoggingTestConfigFilenameDevices,
2488 ::testing::Values(
2489 // TS_FR07_03 consolelog destination /dev/null
2490 /*0*/
2491 LoggingConfigFilenameOkParams(NOT_USED, NULL_DEVICE_NAME, true),
2492 // TS_FR07_01 consolelog destination /dev/stdout
2493 /*1*/
2494 LoggingConfigFilenameOkParams(NOT_USED, STDOUT_DEVICE_NAME, false)));
2495
2496 #ifndef WIN32
2497 INSTANTIATE_TEST_SUITE_P(
2498 LoggingTestConsoleDestinationDevicesUnix,
2499 RouterLoggingTestConfigFilenameDevices,
2500 ::testing::Values(
2501 // TS_FR07_02 consolelog destination /dev/stderr
2502 /*0*/
2503 LoggingConfigFilenameOkParams(NOT_USED, STDERR_DEVICE_NAME, true)));
2504 #endif
2505
2506 struct LoggingConfigFilenameErrorParams {
2507 std::string logger_config;
2508 std::string filename;
2509 bool create_file;
2510 std::string expected_error;
2511
LoggingConfigFilenameErrorParamsLoggingConfigFilenameErrorParams2512 LoggingConfigFilenameErrorParams(const std::string &logger_config_,
2513 const std::string filename_,
2514 bool create_file_,
2515 const std::string expected_error_)
2516 : logger_config(logger_config_),
2517 filename(filename_),
2518 create_file(create_file_),
2519 expected_error(expected_error_) {}
2520 };
2521
2522 class RouterLoggingConfigFilenameError
2523 : public RouterLoggingTest,
2524 public ::testing::WithParamInterface<LoggingConfigFilenameErrorParams> {};
2525
2526 #define ABS_PATH "%%ABSPATH%%"
2527 #define ABS_DIR "%%ABSDIR%%"
2528 #define REL_PATH "%%RELPATH%%"
2529 #define REL_DIR "%%RELDIR%%"
2530 #define FILENAME "%%FILENAME%%"
2531
2532 /** @test This test verifies that absolute and relative filenames are rejected
2533 * in filename option for various sinks/filename combinations.
2534 */
TEST_P(RouterLoggingConfigFilenameError,LoggingConfigAbsRelFilenameError)2535 TEST_P(RouterLoggingConfigFilenameError, LoggingConfigAbsRelFilenameError) {
2536 auto test_params = GetParam();
2537
2538 TempDirectory tmp_dir;
2539
2540 // create the absolute and relative paths (note: order)
2541 Path abs_dir = Path(tmp_dir.name()).real_path();
2542 Path abs_path = abs_dir.join(test_params.filename);
2543 Path rel_path = Path(tmp_dir.name()).basename().join(test_params.filename);
2544
2545 auto conf_params = get_DEFAULT_defaults();
2546 conf_params["logging_folder"] = abs_dir.str();
2547
2548 // Create tmp_file once the tmp_dir is created. Removed by tmp_dir dtor.
2549 if (test_params.create_file) {
2550 std::ofstream myfile_;
2551 myfile_.open(abs_path.str());
2552 if (myfile_.is_open()) {
2553 myfile_ << "Temporary file created by router test ...\n";
2554 myfile_.flush();
2555 myfile_.close();
2556 }
2557 EXPECT_TRUE(abs_path.exists());
2558 }
2559
2560 // replace the pattern in config where applicable
2561 std::string cfg = "[keepalive]\n\n" + test_params.logger_config;
2562 while (cfg.find(FILENAME) != std::string::npos) {
2563 cfg.replace(cfg.find(FILENAME), sizeof(FILENAME) - 1,
2564 test_params.filename.c_str());
2565 }
2566 while (cfg.find(ABS_PATH) != std::string::npos) {
2567 cfg.replace(cfg.find(ABS_PATH), sizeof(ABS_PATH) - 1, abs_path.c_str());
2568 }
2569 while (cfg.find(ABS_DIR) != std::string::npos) {
2570 cfg.replace(cfg.find(ABS_DIR), sizeof(ABS_DIR) - 1, abs_dir.c_str());
2571 }
2572 while (cfg.find(REL_PATH) != std::string::npos) {
2573 cfg.replace(cfg.find(REL_PATH), sizeof(REL_PATH) - 1, rel_path.c_str());
2574 }
2575
2576 TempDirectory conf_dir("conf");
2577 const std::string conf_file =
2578 create_config_file(conf_dir.name(), cfg, &conf_params);
2579
2580 // empty routing section results in a failure, but while logging to file
2581 auto &router = launch_router({"-c", conf_file}, 1);
2582 check_exit_code(router, EXIT_FAILURE);
2583
2584 // the error happens during the logger initialization so we expect the message
2585 // on the console which is the default sink until we switch to the
2586 // configuration from the config file
2587 const std::string console_log_txt = router.get_full_output();
2588
2589 EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2590
2591 EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
2592 << "\nconsole:\n"
2593 << console_log_txt;
2594
2595 // expect no default router file created in the logging folder
2596 Path shouldnotexist = Path(abs_dir.str()).join(DEFAULT_LOGFILE_NAME);
2597 EXPECT_FALSE(shouldnotexist.exists());
2598
2599 if (!test_params.create_file) {
2600 EXPECT_FALSE(abs_path.exists());
2601 }
2602 }
2603
2604 INSTANTIATE_TEST_SUITE_P(
2605 LoggingConfigAbsRelFilenameError, RouterLoggingConfigFilenameError,
2606 ::testing::Values(
2607 // TS_FR02_01 filename with relative path in logger
2608 /*0*/ LoggingConfigFilenameErrorParams(
2609 "[logger]\n"
2610 "filename=" REL_PATH "\n",
2611 USER_LOGFILE_NAME, false, "must be a filename, not a path"),
2612 // TS_FR02_02 filename with relative path in filelog
2613 /*1*/
2614 LoggingConfigFilenameErrorParams("[logger]\n"
2615 "sinks=filelog\n"
2616 "[filelog]\n"
2617 "filename=" REL_PATH "\n",
2618 USER_LOGFILE_NAME, false,
2619 "must be a filename, not a path"),
2620 // TS_FR02_03 absolute filename in logger
2621 /*2*/
2622 LoggingConfigFilenameErrorParams("[logger]\n"
2623 "filename=" ABS_PATH "\n",
2624 USER_LOGFILE_NAME, false,
2625 "must be a filename, not a path"),
2626 // TS_FR02_04 absolute filename in filelog
2627 /*3*/
2628 LoggingConfigFilenameErrorParams("[logger]\n"
2629 "sinks=filelog\n"
2630 "[filelog]\n"
2631 "filename=" ABS_PATH "\n",
2632 USER_LOGFILE_NAME, false,
2633 "must be a filename, not a path"),
2634 // TS_FR02_05 slash filename in logger
2635 /*4*/
2636 LoggingConfigFilenameErrorParams("[logger]\n"
2637 "filename=/\n",
2638 USER_LOGFILE_NAME, false,
2639 "is not a valid log filename"),
2640 // TS_FR02_06 slash filename in filelog
2641 /*5*/
2642 LoggingConfigFilenameErrorParams("[logger]\n"
2643 "sinks=filelog\n"
2644 "[filelog]\n"
2645 "filename=/\n",
2646 USER_LOGFILE_NAME, false,
2647 "is not a valid log filename"),
2648 // TS_FR02_07 existing folder filename in filelog
2649 /*6*/
2650 LoggingConfigFilenameErrorParams("[logger]\n"
2651 "filename=" ABS_DIR "\n",
2652 USER_LOGFILE_NAME, false,
2653 "must be a filename, not a path"),
2654 // TS_FR02_08 existing folder filename in filelog
2655 /*7*/
2656 LoggingConfigFilenameErrorParams("[logger]\n"
2657 "sinks=filelog\n"
2658 "[filelog]\n"
2659 "filename=" ABS_DIR "\n",
2660 USER_LOGFILE_NAME, false,
2661 "must be a filename, not a path"),
2662 // TS_FR02_09 dot filename in logger
2663 /*8*/
2664 LoggingConfigFilenameErrorParams(
2665 "[logger]\n"
2666 "filename=.\n",
2667 USER_LOGFILE_NAME, false,
2668 "File exists, but cannot open for writing"),
2669 // TS_FR02_10 dot filename in filelog
2670 /*9*/
2671 LoggingConfigFilenameErrorParams(
2672 "[logger]\n"
2673 "sinks=filelog\n"
2674 "[filelog]\n"
2675 "filename=.\n",
2676 USER_LOGFILE_NAME, false,
2677 "File exists, but cannot open for writing"),
2678 // TS_FR04_10 filename /path triggers warning and not silent override
2679 /*10*/
2680 LoggingConfigFilenameErrorParams("[logger]\n"
2681 "filename=" USER_LOGFILE_NAME "\n"
2682 "sinks=filelog\n"
2683 "[filelog]\n"
2684 "filename=" ABS_DIR "\n",
2685 USER_LOGFILE_NAME, false,
2686 "must be a filename, not a path"),
2687 // TS_FR04_02 empty filename has no effect
2688 /*11*/
2689 LoggingConfigFilenameErrorParams("[logger]\n"
2690 "filename=\n"
2691 "sinks=filelog\n"
2692 "[filelog]\n"
2693 "filename=" ABS_DIR "\n",
2694 USER_LOGFILE_NAME, false,
2695 "must be a filename, not a path"),
2696 // TS_FR04_06 Verify [logger].filename=/path or [filelog].filename
2697 // triggers an error
2698 /*12*/
2699 LoggingConfigFilenameErrorParams("[logger]\n"
2700 "filename=" ABS_DIR "\n"
2701 "sinks=filelog\n"
2702 "[filelog]\n"
2703 "filename=" ABS_DIR "\n",
2704 USER_LOGFILE_NAME, false,
2705 "must be a filename, not a path"),
2706 // TS_FR04_07 Verify [logger].filename=/path triggers an error
2707 /*13*/
2708 LoggingConfigFilenameErrorParams("[logger]\n"
2709 "filename=" ABS_DIR "\n"
2710 "sinks=filelog\n"
2711 "[filelog]\n"
2712 "filename=\n",
2713 USER_LOGFILE_NAME, false,
2714 "must be a filename, not a path"),
2715 // TS_FR04_08 Verify [logger].filename=/path triggers an error
2716 /*14*/
2717 LoggingConfigFilenameErrorParams("[logger]\n"
2718 "filename=" ABS_DIR "\n"
2719 "sinks=filelog\n"
2720 "[filelog]\n",
2721 USER_LOGFILE_NAME, false,
2722 "must be a filename, not a path"),
2723 // TS_FR10_01 consolelog destination set to existing file
2724 /*15*/
2725 LoggingConfigFilenameErrorParams("[logger]\n"
2726 "sinks=consolelog\n"
2727 "[consolelog]\n"
2728 "destination=" FILENAME "\n",
2729 USER_LOGFILE_NAME, true,
2730 "Illegal destination"),
2731 // TS_FR10_02 consolelog destination set to non-existing file
2732 /*16*/
2733 LoggingConfigFilenameErrorParams("[logger]\n"
2734 "sinks=consolelog\n"
2735 "[consolelog]\n"
2736 "destination=" FILENAME "\n",
2737 USER_LOGFILE_NAME, false,
2738 "Illegal destination"),
2739 // TS_FR10_03 consolelog destination set to realtive file
2740 /*17*/
2741 LoggingConfigFilenameErrorParams("[logger]\n"
2742 "sinks=consolelog\n"
2743 "[consolelog]\n"
2744 "destination=" REL_PATH "\n",
2745 USER_LOGFILE_NAME, true,
2746 "Illegal destination"),
2747 // TS_FR10_04 consolelog destination set to realtive file
2748 /*18*/
2749 LoggingConfigFilenameErrorParams("[logger]\n"
2750 "sinks=consolelog\n"
2751 "[consolelog]\n"
2752 "destination=" ABS_PATH "\n",
2753 USER_LOGFILE_NAME, true,
2754 "Illegal destination"),
2755 // TS_FR10_05 consolelog destination set to realtive file
2756 /*19*/
2757 LoggingConfigFilenameErrorParams("[logger]\n"
2758 "sinks=consolelog\n"
2759 "[consolelog]\n"
2760 "destination=" ABS_DIR "\n",
2761 USER_LOGFILE_NAME, false,
2762 "Illegal destination"),
2763 // TS_FR04_05 absolute path in logger and legal filename fails
2764 /*20*/
2765 LoggingConfigFilenameErrorParams("[logger]\n"
2766 "sinks=filelog\n"
2767 "filename=" ABS_DIR "\n"
2768 "[filelog]\n"
2769 "filename=" USER_LOGFILE_NAME "\n",
2770 USER_LOGFILE_NAME, false,
2771 "must be a filename, not a path"),
2772 // TS_FR04_05a corner case
2773 /*21*/
2774 LoggingConfigFilenameErrorParams("[logger]\n"
2775 "sinks=filelog\n"
2776 "filename=/shouldfail.log\n"
2777 "[filelog]\n"
2778 "filename=" USER_LOGFILE_NAME "\n",
2779 USER_LOGFILE_NAME, false,
2780 "must be a filename, not a path"),
2781 // TS_FR04_06a corner case
2782 /*22*/
2783 LoggingConfigFilenameErrorParams("[logger]\n"
2784 "sinks=filelog\n"
2785 "filename=" USER_LOGFILE_NAME "\n"
2786 "[filelog]\n"
2787 "filename=/shouldfail.log\n",
2788 USER_LOGFILE_NAME, false,
2789 "is not a valid log filename")));
2790
2791 struct LoggingConfigFilenameLoggingFolderParams {
2792 std::string logging_folder;
2793 std::string logger_config;
2794 std::string filename;
2795 bool catch_stderr;
2796 std::string expected_error;
2797
LoggingConfigFilenameLoggingFolderParamsLoggingConfigFilenameLoggingFolderParams2798 LoggingConfigFilenameLoggingFolderParams(const std::string &logging_folder_,
2799 const std::string &logger_config_,
2800 const std::string &filename_,
2801 bool catch_stderr_,
2802 const std::string expected_error_)
2803 : logging_folder(logging_folder_),
2804 logger_config(logger_config_),
2805 filename(filename_),
2806 catch_stderr(catch_stderr_),
2807 expected_error(expected_error_) {}
2808 };
2809
2810 class TempRelativeDirectory {
2811 public:
TempRelativeDirectory(const std::string & prefix="router")2812 explicit TempRelativeDirectory(const std::string &prefix = "router")
2813 : name_{get_tmp_dir_(prefix)} {}
2814
~TempRelativeDirectory()2815 ~TempRelativeDirectory() { mysql_harness::delete_dir_recursive(name_); }
2816
name() const2817 std::string name() const { return name_; }
2818
2819 private:
2820 std::string name_;
2821
2822 #ifndef WIN32
2823 // mysql_harness::get_tmp_dir() returns a relative path on these platforms
get_tmp_dir_(const std::string & name)2824 std::string get_tmp_dir_(const std::string &name) {
2825 return mysql_harness::get_tmp_dir(name);
2826 }
2827 #else
2828 // mysql_harness::get_tmp_dir() returns an abs path under GetTempPath() on
2829 // WIN32
get_tmp_dir_(const std::string & name)2830 std::string get_tmp_dir_(const std::string &name) {
2831 auto generate_random_sequence = [](size_t len) -> std::string {
2832 std::random_device rd;
2833 std::string result;
2834 static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
2835 std::uniform_int_distribution<unsigned long> dist(0,
2836 sizeof(alphabet) - 2);
2837
2838 for (size_t i = 0; i < len; ++i) {
2839 result += alphabet[dist(rd)];
2840 }
2841
2842 return result;
2843 };
2844
2845 std::string dir_name = name + "-" + generate_random_sequence(10);
2846 std::string result = Path(dir_name).str();
2847 int err = _mkdir(result.c_str());
2848 if (err != 0) {
2849 throw std::runtime_error("Error creating temporary directory " + result);
2850 }
2851 return result;
2852 }
2853 #endif
2854 };
2855
2856 class RouterLoggingTestConfigFilenameLoggingFolder
2857 : public RouterLoggingTest,
2858 public ::testing::WithParamInterface<
2859 LoggingConfigFilenameLoggingFolderParams> {};
2860
2861 /** @test This test verifies that consolelog destination may be set to various
2862 * devices
2863 */
TEST_P(RouterLoggingTestConfigFilenameLoggingFolder,LoggingTestFilenameLoggingFolder)2864 TEST_P(RouterLoggingTestConfigFilenameLoggingFolder,
2865 LoggingTestFilenameLoggingFolder) {
2866 auto test_params = GetParam();
2867
2868 TempRelativeDirectory tmp_dir;
2869
2870 // create the absolute path (note: order)
2871 Path abs_dir = Path(tmp_dir.name()).real_path();
2872 Path rel_dir = Path(tmp_dir.name()).basename();
2873
2874 // Replace logging_folder tag with temporary directory
2875 std::string lf = test_params.logging_folder;
2876 while (lf.find(ABS_DIR) != std::string::npos) {
2877 lf.replace(lf.find(ABS_DIR), sizeof(ABS_DIR) - 1, abs_dir.c_str());
2878 }
2879 while (lf.find(REL_DIR) != std::string::npos) {
2880 lf.replace(lf.find(REL_DIR), sizeof(REL_DIR) - 1, rel_dir.c_str());
2881 }
2882
2883 auto conf_params = get_DEFAULT_defaults();
2884 conf_params["logging_folder"] = lf;
2885
2886 TempDirectory conf_dir("conf");
2887 const std::string cfg = "[routing]\n\n" + test_params.logger_config;
2888 const std::string conf_file =
2889 create_config_file(conf_dir.name(), cfg, &conf_params);
2890
2891 // empty routing section gives failure while logging to defined sink
2892 auto &router = launch_router({"-c", conf_file}, 1, test_params.catch_stderr);
2893 check_exit_code(router, EXIT_FAILURE);
2894
2895 const std::string console_log_txt = router.get_full_output();
2896 if (test_params.expected_error.empty()) {
2897 // expect something like this as error message on console/in log
2898 // 2020-03-19 10:00:00 main ERROR [7f539f628780] Configuration error: option
2899 // destinations in [routing] is required
2900 const std::string errmsg = "option destinations in [routing] is required";
2901
2902 if (lf.empty()) {
2903 // log should go to consolelog, and contain routing error
2904 Path logfile = rel_dir.join(test_params.filename);
2905 EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n"
2906 << console_log_txt;
2907 EXPECT_FALSE(logfile.exists());
2908 EXPECT_THAT(console_log_txt, HasSubstr(errmsg)) << "\nconsole:\n"
2909 << console_log_txt;
2910 } else {
2911 // log should go to logfile specified
2912 Path logfile = Path(lf).join(test_params.filename);
2913 EXPECT_TRUE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2914 EXPECT_TRUE(logfile.exists());
2915 std::string file_log_txt =
2916 router.get_full_logfile(test_params.filename, Path(lf).str());
2917 EXPECT_THAT(file_log_txt, HasSubstr(errmsg)) << "\nlog:\n"
2918 << file_log_txt;
2919 }
2920 } else {
2921 // log should go to consolelog, and contain routing error
2922 EXPECT_TRUE(!console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2923 EXPECT_THAT(console_log_txt, HasSubstr(test_params.expected_error))
2924 << "\nconsole:\n"
2925 << console_log_txt;
2926 }
2927 }
2928
2929 INSTANTIATE_TEST_SUITE_P(
2930 LoggingTestConsoleDestinationDevices,
2931 RouterLoggingTestConfigFilenameLoggingFolder,
2932 ::testing::Values(
2933 // TS_FR03_01
2934 /*0*/
2935 LoggingConfigFilenameLoggingFolderParams("",
2936 "[logger]\n"
2937 "filename=" USER_LOGFILE_NAME
2938 "\n",
2939 USER_LOGFILE_NAME, true,
2940 NOT_USED),
2941 // TS_FR03_02
2942 /*1*/
2943 LoggingConfigFilenameLoggingFolderParams(
2944 ABS_DIR, "[logger]\nfilename=" USER_LOGFILE_NAME "\n",
2945 USER_LOGFILE_NAME, false, NOT_USED),
2946 // TS_FR03_03
2947 /*2*/
2948 LoggingConfigFilenameLoggingFolderParams(
2949 REL_DIR, "[logger]\nfilename=" USER_LOGFILE_NAME "\n",
2950 USER_LOGFILE_NAME, false, NOT_USED),
2951 // TS_FR03_04
2952 /*3*/
2953 LoggingConfigFilenameLoggingFolderParams(
2954 "/non/existing/absolute/path/",
2955 "[logger]\nfilename=" USER_LOGFILE_NAME "\n", USER_LOGFILE_NAME,
2956 true, "Error when creating dir '/non/existing/absolute/path'"),
2957 // TS_FR03_05
2958 /*4*/
2959 LoggingConfigFilenameLoggingFolderParams(
2960 "non/existing/relative/path",
2961 "[logger]\nfilename=" USER_LOGFILE_NAME "\n", USER_LOGFILE_NAME,
2962 true, "Error when creating dir 'non/existing/relative/path'"),
2963 // TS_FR05_03 without [logger].filename
2964 // and TS_FR05_04 without [filesink].filename
2965 /*5*/
2966 LoggingConfigFilenameLoggingFolderParams(
2967 ABS_DIR, "[logger]\nsinks=filelog\n[filelog]\n",
2968 DEFAULT_LOGFILE_NAME, false, NOT_USED)));
2969
2970 /** @test This test verifies that output goes to console when consolelog
2971 * destination is empty (TS_FR06_01)
2972 */
TEST_F(RouterLoggingTest,log_console_destination_empty)2973 TEST_F(RouterLoggingTest, log_console_destination_empty) {
2974 // FIXME: Unfortunately due to the limitations of our component testing
2975 // framework, this test has a flaw: it is not possible to distinguish if the
2976 // output returned from router.get_full_output() appeared on STDERR or STDOUT.
2977 // This should be fixed in the future.
2978 TempDirectory tmp_dir;
2979 auto conf_params = get_DEFAULT_defaults();
2980 conf_params["logging_folder"] = tmp_dir.name();
2981
2982 TempDirectory conf_dir("conf");
2983 const std::string conf_text =
2984 "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\ndestination=";
2985 const std::string conf_file =
2986 create_config_file(conf_dir.name(), conf_text, &conf_params);
2987
2988 // empty routing section results in a failure, but while logging to
2989 // destination
2990 auto &router = launch_router({"-c", conf_file}, 1);
2991 check_exit_code(router, EXIT_FAILURE);
2992
2993 // Expect the console log to be used on empty destinaton
2994 const std::string console_log_txt = router.get_full_output();
2995 EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
2996
2997 // expect no default router file created in tmp_dir
2998 Path shouldnotexist = Path(tmp_dir.name()).join("mysqlrouter.log");
2999 EXPECT_FALSE(shouldnotexist.exists());
3000 }
3001
3002 /** @test This test verifies that output to console does not contain a warning
3003 * or the userdefined logfile name when filename not in use (TS_FR08_01)
3004 */
TEST_F(RouterLoggingTest,log_console_unused_filename_no_warning)3005 TEST_F(RouterLoggingTest, log_console_unused_filename_no_warning) {
3006 // FIXME: Unfortunately due to the limitations of our component testing
3007 // framework, this test has a flaw: it is not possible to distinguish if the
3008 // output returned from router.get_full_output() appeared on STDERR or STDOUT.
3009 // This should be fixed in the future.
3010 TempDirectory tmp_dir;
3011 auto conf_params = get_DEFAULT_defaults();
3012 conf_params["logging_folder"] = tmp_dir.name();
3013
3014 TempDirectory conf_dir("conf");
3015 const std::string conf_text =
3016 "[routing]\n\n[logger]\nfilename=" USER_LOGFILE_NAME
3017 "\nsinks=consolelog\n[consolelog]\n";
3018 const std::string conf_file =
3019 create_config_file(conf_dir.name(), conf_text, &conf_params);
3020
3021 // empty routing section results in a failure, but while logging to
3022 // destination
3023 auto &router = launch_router({"-c", conf_file}, 1);
3024 check_exit_code(router, EXIT_FAILURE);
3025
3026 // Expect the console log output to NOT contain warning or log file name
3027 const std::string console_log_txt = router.get_full_output();
3028 EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
3029
3030 EXPECT_THAT(console_log_txt, Not(HasSubstr(USER_LOGFILE_NAME)))
3031 << "\nconsole:\n"
3032 << console_log_txt;
3033
3034 EXPECT_THAT(console_log_txt, Not(HasSubstr("warning"))) << "\nconsole:\n"
3035 << console_log_txt;
3036 }
3037
3038 /** @test This test verifies non-existing [consolelog].destination uses default
3039 * value. i.e console (TS_FR06_02)
3040 */
TEST_F(RouterLoggingTest,log_console_non_existing_destination)3041 TEST_F(RouterLoggingTest, log_console_non_existing_destination) {
3042 // FIXME: Unfortunately due to the limitations of our component testing
3043 // framework, this test has a flaw: it is not possible to distinguish if the
3044 // output returned from router.get_full_output() appeared on STDERR or STDOUT.
3045 // This should be fixed in the future.
3046 TempDirectory tmp_dir;
3047 auto conf_params = get_DEFAULT_defaults();
3048 conf_params["logging_folder"] = "";
3049
3050 TempDirectory conf_dir("conf");
3051 const std::string conf_text =
3052 "[routing]\n\n[logger]\nsinks=consolelog\n[consolelog]\n";
3053 const std::string conf_file =
3054 create_config_file(conf_dir.name(), conf_text, &conf_params);
3055
3056 // empty routing section results in a failure, but while logging to
3057 // destination
3058 auto &router = launch_router({"-c", conf_file}, 1);
3059 check_exit_code(router, EXIT_FAILURE);
3060
3061 // Expect the console log output to NOT contain warning or log file name
3062 const std::string console_log_txt = router.get_full_output();
3063 EXPECT_FALSE(console_log_txt.empty()) << "\nconsole:\n" << console_log_txt;
3064 }
3065
3066 #ifndef WIN32
3067 /** @test This test verifies that filename may be set to /dev/null the ugly way
3068 */
TEST_F(RouterLoggingTest,log_filename_dev_null_ugly)3069 TEST_F(RouterLoggingTest, log_filename_dev_null_ugly) {
3070 Path dev_null("/dev/null");
3071 EXPECT_TRUE(dev_null.exists());
3072
3073 auto conf_params = get_DEFAULT_defaults();
3074 conf_params["logging_folder"] = "/dev";
3075
3076 TempDirectory conf_dir("conf");
3077 const std::string conf_text = "[routing]\n\n[logger]\nfilename=null\n";
3078 const std::string conf_file =
3079 create_config_file(conf_dir.name(), conf_text, &conf_params);
3080
3081 // empty routing section results in a failure, but while logging to file
3082 auto &router = launch_router({"-c", conf_file}, 1);
3083 check_exit_code(router, EXIT_FAILURE);
3084
3085 // expect no default router file created in /dev
3086 Path shouldnotexist("/dev/mysqlrouter.log");
3087 EXPECT_FALSE(shouldnotexist.exists());
3088
3089 EXPECT_TRUE(dev_null.exists());
3090 }
3091 #endif
3092
main(int argc,char * argv[])3093 int main(int argc, char *argv[]) {
3094 init_windows_sockets();
3095 ProcessManager::set_origin(Path(argv[0]).dirname());
3096 ::testing::InitGoogleTest(&argc, argv);
3097 return RUN_ALL_TESTS();
3098 }
3099