2

I am on the latest commit of spdlog (there is an issue regarding std output, apparently resolved), and am switching my output from std::cout to spdlog.

My google tests redirect std::cout so I can check the output of stub functions:

class MyTest : public testing::Test
{
protected:
        void SetUp() override
        {            
            sbuf = std::cout.rdbuf();
            std::cout.rdbuf(buffer.rdbuf());
            auto console = spdlog::stdout_color_mt("console");
            auto err_logger = spdlog::stderr_color_mt("stderr");            
        }
        void TearDown() override
        {
            std::cout.rdbuf(sbuf);         
        }
        std::stringstream buffer;
        std::streambuf* sbuf;
}

then use as follows inside a test;

doSomethingThatWritesToStdOut();
std::string teststr = buffer.str();
EXPECT_TRUE(teststr.find("Some output string") != std::string::npos);

This doesn't work when I change the content of doSomethingThatWritesToStdOut to

spdlog::get("console")->debug("Some output string\n");

The teststr value is empty.. If I do the following

 spdlog::get("console")->debug("Some output string\n");
 std::cout << "Some output string\n";

Then I can see one instance of "Some output string" in teststr. How can I capture the output of this logger (or change the logger) so I can test in google tests?

mike
  • 1,192
  • 9
  • 32
  • I can reproduce, indeed the log output is not redirected. – pptaszni Mar 04 '21 at 12:47
  • @pptaszni you think this might be a bug with spdlog? I'll raise it on github if so – mike Mar 04 '21 at 12:48
  • I think you can try, it looks like undesired behavior. – pptaszni Mar 04 '21 at 13:02
  • Raised an issue here: https://github.com/gabime/spdlog/issues/1859 – mike Mar 04 '21 at 14:38
  • The issue I linked to in the original post above (1147 ) was about redirect stdout, not std::cout. It looks like using an osstream sink should put the output into a nominated osstream, looking into that now. – mike Mar 05 '21 at 09:19

2 Answers2

6

As I've mentioned in the comments, I had thought spdlog output to std::cout due to an earlier issue, but in fact that was related to stdout.. (facepalm)

This is nice and easy, it turns out! By using an ostream_sink, the output can be sent to a specified ostream;

I set a logger up in my test SetUp() function as follows

            auto ostream_logger = spdlog::get("gtest_logger");
            if (!ostream_logger)
            {
                auto ostream_sink = std::make_shared<spdlog::sinks::ostream_sink_st>(_oss);
                ostream_logger = std::make_shared<spdlog::logger>("gtest_logger", ostream_sink);
                ostream_logger->set_pattern(">%v<");
                ostream_logger->set_level(spdlog::level::debug);
            }
            spdlog::set_default_logger(ostream_logger);

where _oss is a std::ostringstream.

Then my tests just look at the contents of _oss, and clear it after each check:

        std::string test = _oss.str();
        // check the derived class is constructed
        EXPECT_TRUE(test.find("Constructing test class") != std::string::npos);
        _oss.str("");

The existing code using spdlog::debug, spdlog::trace etc doesn't need changing at all.

mike
  • 1,192
  • 9
  • 32
0

For me, the accepted answer didn't work, because some functions would get the logger by name, instead of the default logger.

If you don't need to worry about thread safety, and you're in a similar situation, then you can simply change your logger's sink, instead of creating a new logger:

struct LoggerState {
    spdlog::sink_ptr oldSink {nullptr};
    spdlog::level::level_enum oldLevel;
};

LoggerState redirectLogger(
    std::shared_ptr<spdlog::logger>& log,
    std::ostringstream& oss,
    spdlog::level::level_enum newLevel
){
    LoggerState ls;
    ls.oldLevel = log->level();
    std::vector<spdlog::sink_ptr>& sinks { log->sinks() };
    assert(sinks.size() == 1);

    ls.oldSink = std::move(sinks[0]);

    sinks[0] = std::make_shared<spdlog::sinks::ostream_sink_st>(oss);
    log->set_pattern( "[%l] %v" );
    log->set_level(newLevel);

    return ls;
}

void resetLogger(
    std::shared_ptr<spdlog::logger>& log,
    const LoggerState& ls
){
    log->sinks()[0] = std::move(ls.oldSink);
    log->set_level(ls.oldLevel);
}

Use it in your code as such:

std::shared_ptr<spdlog::logger> yourLogger { /* get your logger */ };
std::ostringstream oss;
LoggerState oldState = redirectLogger(yourLogger, oss, spdlog::level::warn);

/* do something that produces logger output */

resetLogger(yourLogger, oldState);

/* now oss.str() holds the captured output,
 * and your old logger should be good as new */
RL-S
  • 734
  • 6
  • 21