#include #include "log/Log.h" #include "common/Clock.h" #include "include/coredumpctl.h" #include "SubsystemMap.h" #include "global/global_init.h" #include "common/ceph_argparse.h" #include "global/global_context.h" #include "common/dout.h" #include #include using namespace std; using namespace ceph::logging; TEST(Log, Simple) { SubsystemMap subs; subs.set_log_level(0, 10); subs.set_gather_level(0, 10); subs.set_log_level(1, 20); subs.set_gather_level(1, 1); subs.set_log_level(2, 20); subs.set_gather_level(2, 2); subs.set_log_level(3, 10); subs.set_gather_level(3, 3); Log log(&subs); log.start(); log.set_log_file("foo"); log.reopen_log_file(); log.set_stderr_level(5, -1); for (int i=0; i<100; i++) { int sys = i % 4; int l = 5 + (i%4); if (subs.should_gather(sys, l)) { MutableEntry e(l, sys); log.submit_entry(std::move(e)); } } log.flush(); log.dump_recent(); log.stop(); } TEST(Log, ReuseBad) { SubsystemMap subs; subs.set_log_level(1, 1); subs.set_gather_level(1, 1); Log log(&subs); log.start(); log.set_log_file("foo"); log.reopen_log_file(); const int l = 0; { MutableEntry e(l, 1); auto& out = e.get_ostream(); out << (std::streambuf*)nullptr; EXPECT_TRUE(out.bad()); // writing nullptr to a stream sets its badbit log.submit_entry(std::move(e)); } { MutableEntry e(l, 1); auto& out = e.get_ostream(); EXPECT_FALSE(out.bad()); // should not see failures from previous log entry out << "hello world"; log.submit_entry(std::move(e)); } log.flush(); log.stop(); } int many = 10000; TEST(Log, ManyNoGather) { SubsystemMap subs; subs.set_log_level(1, 1); subs.set_gather_level(1, 1); Log log(&subs); log.start(); log.set_log_file("big"); log.reopen_log_file(); for (int i=0; i 0) { if (verify) { char* p = strrchr(buf, '\n'); /* verify no torn writes */ if (p == NULL) { _exit(2); } else if (p[1] != '\0') { write(2, buf, strlen(buf)); _exit(3); } } } else _exit(100); usleep(500); } } TEST(Log, StderrPipeAtomic) { int pfd[2] = {-1, -1}; int rc = pipe(pfd); ASSERT_EQ(rc, 0); pid_t pid = fork(); if (pid == 0) { close(pfd[1]); readpipe(pfd[0], 1); } else if (pid == (pid_t)-1) { ASSERT_EQ(0, 1); } close(pfd[0]); SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 10); Log log(&subs); log.start(); log.set_log_file(""); log.reopen_log_file(); log.set_stderr_fd(pfd[1]); log.set_stderr_level(1, 20); /* -128 for prefix space */ for (int i = 0; i < PIPE_BUF-128; i++) { MutableEntry e(1, 1); auto& s = e.get_ostream(); for (int j = 0; j < i; j++) { char c = 'a'; c += (j % 26); s << c; } log.submit_entry(std::move(e)); } log.flush(); log.stop(); close(pfd[1]); int status; pid_t waited = waitpid(pid, &status, 0); ASSERT_EQ(pid, waited); ASSERT_NE(WIFEXITED(status), 0); ASSERT_EQ(WEXITSTATUS(status), 0); } TEST(Log, StderrPipeBig) { int pfd[2] = {-1, -1}; int rc = pipe(pfd); ASSERT_EQ(rc, 0); pid_t pid = fork(); if (pid == 0) { /* no verification as some reads will be torn due to size > PIPE_BUF */ close(pfd[1]); readpipe(pfd[0], 0); } else if (pid == (pid_t)-1) { ASSERT_EQ(0, 1); } close(pfd[0]); SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 10); Log log(&subs); log.start(); log.set_log_file(""); log.reopen_log_file(); log.set_stderr_fd(pfd[1]); log.set_stderr_level(1, 20); /* -128 for prefix space */ for (int i = 0; i < PIPE_BUF*2; i++) { MutableEntry e(1, 1); auto& s = e.get_ostream(); for (int j = 0; j < i; j++) { char c = 'a'; c += (j % 26); s << c; } log.submit_entry(std::move(e)); } log.flush(); log.stop(); close(pfd[1]); int status; pid_t waited = waitpid(pid, &status, 0); ASSERT_EQ(pid, waited); ASSERT_NE(WIFEXITED(status), 0); ASSERT_EQ(WEXITSTATUS(status), 0); } void do_segv() { SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 1); Log log(&subs); log.start(); log.set_log_file("big"); log.reopen_log_file(); log.inject_segv(); MutableEntry e(10, 1); { PrCtl unset_dumpable; log.submit_entry(std::move(e)); // this should segv } log.flush(); log.stop(); } TEST(Log, InternalSegv) { ASSERT_DEATH(do_segv(), ".*"); } TEST(Log, LargeLog) { SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 10); Log log(&subs); log.start(); log.set_log_file("big"); log.reopen_log_file(); int l = 10; { MutableEntry e(l, 1); std::string msg(10000000, 'a'); e.get_ostream() << msg; log.submit_entry(std::move(e)); } log.flush(); log.stop(); } TEST(Log, LargeFromSmallLog) { SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 10); Log log(&subs); log.start(); log.set_log_file("big"); log.reopen_log_file(); int l = 10; { MutableEntry e(l, 1); for (int i = 0; i < 1000000; i++) { std::string msg(10, 'a'); e.get_ostream() << msg; } log.submit_entry(std::move(e)); } log.flush(); log.stop(); } // Make sure nothing bad happens when we switch TEST(Log, TimeSwitch) { SubsystemMap subs; subs.set_log_level(1, 20); subs.set_gather_level(1, 10); Log log(&subs); log.start(); log.set_log_file("time_switch_log"); log.reopen_log_file(); int l = 10; bool coarse = true; for (auto i = 0U; i < 300; ++i) { MutableEntry e(l, 1); e.get_ostream() << "SQUID THEFT! PUNISHABLE BY DEATH!"; log.submit_entry(std::move(e)); if (i % 50) log.set_coarse_timestamps(coarse = !coarse); } log.flush(); log.stop(); } TEST(Log, TimeFormat) { static constexpr auto buflen = 128u; char buf[buflen]; ceph::logging::log_clock clock; { clock.coarsen(); auto t = clock.now(); ceph::logging::append_time(t, buf, buflen); auto c = std::strrchr(buf, '.'); ASSERT_NE(c, nullptr); ASSERT_EQ(8u, strlen(c + 1)); } { clock.refine(); auto t = clock.now(); ceph::logging::append_time(t, buf, buflen); auto c = std::strrchr(buf, '.'); ASSERT_NE(c, nullptr); ASSERT_EQ(11u, std::strlen(c + 1)); } } #define dout_subsys ceph_subsys_context template struct do_log { void log(CephContext* cct); }; template struct do_log<12, x> { void log(CephContext* cct); }; template void do_log::log(CephContext* cct) { ldout(cct, 20) << "Log depth=" << depth << " x=" << x << dendl; if (rand() % 2) { do_log log; log.log(cct); } else { do_log log; log.log(cct); } } std::string recursion(CephContext* cct) { ldout(cct, 20) << "Preparing recursion string" << dendl; return "here-recursion"; } template void do_log<12, x>::log(CephContext* cct) { if ((rand() % 16) == 0) { ldout(cct, 20) << "End " << recursion(cct) << "x=" << x << dendl; } else { ldout(cct, 20) << "End x=" << x << dendl; } } TEST(Log, Speed_gather) { do_log<0,0> start; g_ceph_context->_conf->subsys.set_gather_level(ceph_subsys_context, 30); g_ceph_context->_conf->subsys.set_log_level(ceph_subsys_context, 0); for (int i=0; i<100000;i++) { ldout(g_ceph_context, 20) << "Iteration " << i << dendl; start.log(g_ceph_context); } } TEST(Log, Speed_nogather) { do_log<0,0> start; g_ceph_context->_conf->subsys.set_gather_level(ceph_subsys_context, 0); g_ceph_context->_conf->subsys.set_log_level(ceph_subsys_context, 0); for (int i=0; i<100000;i++) { ldout(g_ceph_context, 20) << "Iteration " << i << dendl; start.log(g_ceph_context); } } TEST(Log, GarbleRecovery) { static const char* test_file="log_for_moment"; Log* saved = g_ceph_context->_log; Log log(&g_ceph_context->_conf->subsys); log.start(); unlink(test_file); log.set_log_file(test_file); log.reopen_log_file(); g_ceph_context->_log = &log; std::string long_message(1000,'c'); ldout(g_ceph_context, 0) << long_message << dendl; ldout(g_ceph_context, 0) << "Prologue" << (std::streambuf*)nullptr << long_message << dendl; ldout(g_ceph_context, 0) << "Epitaph" << long_message << dendl; g_ceph_context->_log = saved; log.flush(); log.stop(); struct stat file_status; ASSERT_EQ(stat(test_file, &file_status), 0); ASSERT_GT(file_status.st_size, 2000); } int main(int argc, char **argv) { auto args = argv_to_vec(argc, argv); auto cct = global_init(nullptr, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); common_init_finish(g_ceph_context); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }