mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Servatrice] Fail loudly when startup initialization fails (#7602)
* [Servatrice] Exit non-zero when server initialization fails initServer() reported every startup failure through a zero exit code: the database could not be opened, the server printed nothing of its own, and the process looked like a clean shutdown. Log the two refusal reasons at critical level and return 1 so supervisors and the Docker exit status can tell the difference. * [Servatrice] Keep console logging independent of the logfile logMessage() returned before the console echo unless a logfile was open and server/writelog was true, so --log-to-console produced nothing when server/logfile was empty or could not be opened. That is how a refused database connection could end in a silent exit. Print to the console before the logfile gates, drop the duplicate echo from flushBuffer(), and read writelog/logfilters once in main instead of calling the non-thread-safe global QSettings from every logging thread. * [Servatrice] Uninstall the log handler before tearing down globals The handler reaches into the global logger and settingsCache. Leaving it installed while main deletes those objects turns any Qt warning emitted during QCoreApplication/QFactoryLoader teardown into a use-after-free; with QT_DEBUG_PLUGINS=1 that warning exists, and Servatrice died with SIGSEGV in QSettings::value() on the way out. * [Servatrice] Buffer console logs on the logger thread Address review feedback on the startup failure change: - Append console lines to a buffer drained by the logger thread, so a stalled stdout reader cannot block game threads. - Report islServer->errorString() instead of the literal call text. - Let the constructor reuse reloadConfig() instead of parsing the filter list twice. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
ae43659b91
commit
825aba953a
7 changed files with 311 additions and 62 deletions
179
tests/server_logger_test.cpp
Normal file
179
tests/server_logger_test.cpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#include "server_logger.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <QFile>
|
||||
#include <QMetaObject>
|
||||
#include <QTemporaryDir>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Redirects std::cout for the lifetime of the object so the logger's console
|
||||
* sink can be asserted on.
|
||||
*/
|
||||
class CoutCapture
|
||||
{
|
||||
public:
|
||||
CoutCapture() : previousBuffer(std::cout.rdbuf(captured.rdbuf()))
|
||||
{
|
||||
}
|
||||
~CoutCapture()
|
||||
{
|
||||
std::cout.rdbuf(previousBuffer);
|
||||
}
|
||||
|
||||
QString text() const
|
||||
{
|
||||
return QString::fromStdString(captured.str());
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostringstream captured;
|
||||
std::streambuf *previousBuffer;
|
||||
};
|
||||
|
||||
// The console sink is written by the logger thread; drive it directly here, as the event
|
||||
// loop would in the server.
|
||||
void flushLogger(ServerLogger &logger)
|
||||
{
|
||||
QMetaObject::invokeMethod(&logger, "flushBuffer", Qt::DirectConnection);
|
||||
}
|
||||
|
||||
// Regression: --log-to-console used to be gated behind a successfully opened logfile, so an
|
||||
// empty server/logfile silenced every diagnostic, including critical startup errors.
|
||||
TEST(ServerLoggerTest, ConsoleSinkDoesNotDependOnLogFile)
|
||||
{
|
||||
ServerLogger logger(true, true, QString());
|
||||
logger.startLog(QString());
|
||||
|
||||
CoutCapture capture;
|
||||
logger.logMessage("could not open the database");
|
||||
flushLogger(logger);
|
||||
|
||||
EXPECT_TRUE(capture.text().contains("could not open the database")) << capture.text().toStdString();
|
||||
}
|
||||
|
||||
// writelog only describes the logfile; disabling it must not silence the console.
|
||||
TEST(ServerLoggerTest, ConsoleSinkSurvivesDisabledFileLog)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
ServerLogger logger(true, false, QString());
|
||||
logger.startLog(dir.filePath("server.log"));
|
||||
|
||||
CoutCapture capture;
|
||||
logger.logMessage("startup failed");
|
||||
flushLogger(logger);
|
||||
|
||||
EXPECT_TRUE(capture.text().contains("startup failed")) << capture.text().toStdString();
|
||||
}
|
||||
|
||||
// logfilters selects what goes into the logfile; the console is not filtered.
|
||||
TEST(ServerLoggerTest, ConsoleSinkIgnoresLogFilters)
|
||||
{
|
||||
ServerLogger logger(true, true, "database");
|
||||
logger.startLog(QString());
|
||||
|
||||
CoutCapture capture;
|
||||
logger.logMessage("unrelated message");
|
||||
flushLogger(logger);
|
||||
|
||||
EXPECT_TRUE(capture.text().contains("unrelated message")) << capture.text().toStdString();
|
||||
}
|
||||
|
||||
TEST(ServerLoggerTest, StaysQuietWhenConsoleIsDisabled)
|
||||
{
|
||||
ServerLogger logger(false, false, QString());
|
||||
logger.startLog(QString());
|
||||
|
||||
CoutCapture capture;
|
||||
logger.logMessage("not for the console");
|
||||
flushLogger(logger);
|
||||
|
||||
EXPECT_TRUE(capture.text().isEmpty()) << capture.text().toStdString();
|
||||
}
|
||||
|
||||
TEST(ServerLoggerTest, WritesMatchingFilteredMessagesToTheLogFile)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
const QString logPath = dir.filePath("server.log");
|
||||
|
||||
{
|
||||
ServerLogger logger(false, true, "database");
|
||||
logger.startLog(logPath);
|
||||
logger.logMessage("database is unavailable");
|
||||
logger.logMessage("unrelated message");
|
||||
}
|
||||
|
||||
QFile logFile(logPath);
|
||||
ASSERT_TRUE(logFile.open(QIODevice::ReadOnly));
|
||||
const QString contents = QString::fromUtf8(logFile.readAll());
|
||||
EXPECT_TRUE(contents.contains("database is unavailable")) << contents.toStdString();
|
||||
EXPECT_FALSE(contents.contains("unrelated message")) << contents.toStdString();
|
||||
}
|
||||
|
||||
// SIGHUP reloads the configuration; the logger must pick up the new filter.
|
||||
TEST(ServerLoggerTest, ReloadConfigAppliesANewFilter)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
const QString logPath = dir.filePath("server.log");
|
||||
|
||||
{
|
||||
ServerLogger logger(false, true, "database");
|
||||
logger.startLog(logPath);
|
||||
logger.logMessage("filtered out");
|
||||
logger.reloadConfig(true, QString());
|
||||
logger.logMessage("kept after reload");
|
||||
}
|
||||
|
||||
QFile logFile(logPath);
|
||||
ASSERT_TRUE(logFile.open(QIODevice::ReadOnly));
|
||||
const QString contents = QString::fromUtf8(logFile.readAll());
|
||||
EXPECT_FALSE(contents.contains("filtered out")) << contents.toStdString();
|
||||
EXPECT_TRUE(contents.contains("kept after reload")) << contents.toStdString();
|
||||
}
|
||||
|
||||
TEST(ServerLoggerTest, DisabledFileLogLeavesTheLogFileEmpty)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
const QString logPath = dir.filePath("server.log");
|
||||
|
||||
{
|
||||
ServerLogger logger(false, false, QString());
|
||||
logger.startLog(logPath);
|
||||
logger.logMessage("not for the logfile");
|
||||
}
|
||||
|
||||
QFile logFile(logPath);
|
||||
ASSERT_TRUE(logFile.open(QIODevice::ReadOnly));
|
||||
EXPECT_TRUE(logFile.readAll().isEmpty());
|
||||
}
|
||||
|
||||
// A filter list consisting only of separators activates filtering with no entries; the old
|
||||
// trimmed-empty check suppressed the logfile in that case and this preserves it.
|
||||
TEST(ServerLoggerTest, DegenerateFilterSuppressesTheLogFile)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
const QString logPath = dir.filePath("server.log");
|
||||
|
||||
{
|
||||
ServerLogger logger(false, true, ",");
|
||||
logger.startLog(logPath);
|
||||
logger.logMessage("anything");
|
||||
}
|
||||
|
||||
QFile logFile(logPath);
|
||||
ASSERT_TRUE(logFile.open(QIODevice::ReadOnly));
|
||||
EXPECT_TRUE(logFile.readAll().isEmpty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Loading…
Add table
Add a link
Reference in a new issue