Files
KytyPS5/src/common/logging/log.cpp
T
Ali Almohaya 6a0a666a41 loader: keep the output and log of a game LoadExec relaunches (#1480)
* loader: keep the output of a process LoadExec relaunches

LoadExec on Windows starts a new emulator process and exits the current
one. The new process was created without a console and without the
standard handles, so everything it printed was lost: a redirected log of
a game that relaunches itself ended at the relaunch.

Hand the new process the standard input, output and error handles,
inheriting only those, and let it share the console when there is one.

* loader: flush output before LoadExec starts the new program

On Windows, LoadExec flushed the C streams only after the new process
had been created, so output the old process still held could appear
after the new one's first lines. On Linux they were not flushed at all
before exec, which discards them.

Flush the log and every stream before starting the new program.

* loader: continue the log file across LoadExec

With --printf-direction File, the program LoadExec starts is given the
same --printf-output-file and opened it anew, emptying it. Everything
the first program had logged, its LoadExec line included, was lost.

LoadExec now sets KYTY_RELAUNCHED for the program it starts, which then
appends to the log file instead of replacing it. On Linux the variable
goes into the environment passed to exec rather than this process's
own, which other threads may be reading.

* loader: show LoadExec switches on the console

LoadExec logged the program it switched to only when guest output was
logged, and then only into the log: on a console the new program's
startup output followed the old one's without anything marking where
one program ended and the next began.

Write the switch to the console as well as the log, with the program's
arguments.
2026-10-16 03:01:17 +02:00

197 lines
4.9 KiB
C++

#include "common/logging/log.h"
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fmt/format.h>
#include <memory>
#include <mutex>
#include <spdlog/formatter.h>
#include <spdlog/logger.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/null_sink.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <string_view>
namespace {
class RawFormatter final: public spdlog::formatter {
public:
void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override {
const std::string_view payload(msg.payload.data(), msg.payload.size());
dest.append(payload.data(), payload.data() + payload.size());
}
std::unique_ptr<spdlog::formatter> clone() const override {
return std::make_unique<RawFormatter>();
}
};
std::shared_ptr<spdlog::logger> MakeLogger(std::string name, spdlog::sink_ptr sink) {
sink->set_formatter(std::make_unique<RawFormatter>());
auto logger = std::make_shared<spdlog::logger>(std::move(name), std::move(sink));
logger->set_level(spdlog::level::trace);
return logger;
}
std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name,
const std::filesystem::path& path) {
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
const bool truncate = std::getenv(Log::RelaunchedVariable) == nullptr;
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), truncate);
return MakeLogger(std::move(name), std::move(sink));
}
static bool HasStyle(fmt::text_style style) {
return style != fmt::text_style {};
}
void WriteStdout(std::string_view text, fmt::text_style style = {}) {
if (HasStyle(style)) {
fmt::print(stdout, style, "{}", text);
} else {
std::fwrite(text.data(), 1, text.size(), stdout);
}
std::fflush(stdout);
}
} // namespace
namespace Log {
static bool g_initialized = false;
static Direction g_direction = Direction::Console;
static std::filesystem::path g_output_file;
static std::mutex g_logger_mutex;
static std::shared_ptr<spdlog::logger> g_logger;
void Flush() {
if (g_logger != nullptr) {
g_logger->flush();
}
}
static void SetupLogger() {
std::lock_guard lock(g_logger_mutex);
Flush();
g_logger.reset();
switch (g_direction) {
case Direction::Silent:
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::null_sink_mt>());
break;
case Direction::Console:
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::stdout_sink_mt>());
break;
case Direction::File:
if (!g_output_file.empty()) {
g_logger = MakeFileLogger("kyty", g_output_file);
}
break;
}
}
static std::shared_ptr<spdlog::logger> GetLogger() {
std::lock_guard lock(g_logger_mutex);
return g_logger;
}
static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
if (text.empty()) {
return;
}
if (!g_initialized) {
WriteStdout(text, style);
return;
}
if (g_direction == Direction::Silent) {
return;
}
if (auto logger = GetLogger()) {
if (g_direction == Direction::Console && HasStyle(style)) {
const auto styled = fmt::format(style, "{}", text);
logger->log(spdlog::level::info, spdlog::string_view_t(styled.data(), styled.size()));
} else {
logger->log(spdlog::level::info, spdlog::string_view_t(text.data(), text.size()));
}
}
}
void WriteToConsoleAndLog(std::string_view text) {
WriteImpl(text);
if (g_initialized && g_direction != Direction::Console) {
WriteStdout(text);
}
Flush();
}
void WriteFatal(std::string_view text) {
if (g_direction == Direction::Silent || !g_initialized) {
WriteStdout(text);
} else {
WriteStdout(text);
WriteImpl(text);
}
Flush();
}
void WriteFatal(fmt::text_style style, std::string_view text) {
if (g_direction == Direction::Silent || !g_initialized) {
WriteStdout(text, style);
} else {
WriteStdout(text, style);
WriteImpl(text, style);
}
Flush();
}
void Initialize() {
g_initialized = true;
switch (Config::GetPrintfDirection()) {
case Config::LogDirection::Silent: g_direction = Direction::Silent; break;
case Config::LogDirection::Console: g_direction = Direction::Console; break;
case Config::LogDirection::File: g_direction = Direction::File; break;
}
g_output_file =
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
SetupLogger();
}
void Shutdown() {
Flush();
std::lock_guard lock(g_logger_mutex);
g_logger.reset();
}
Direction GetDirection() {
EXIT_IF(!g_initialized);
return g_direction;
}
bool IsSilent() {
// Before init LOGF must keep writing to stdout, so report non-silent.
return g_initialized && g_direction == Direction::Silent;
}
void Write(std::string_view text) {
WriteImpl(text);
}
void Write(fmt::text_style style, std::string_view text) {
WriteImpl(text, style);
}
} // namespace Log