Introduce CLogger::ScopedReplacement and FileLogger

Comments By: @sera, @vladislavbelov, @Stan
Differential Revision: https://code.wildfiregames.com/D5167
This was SVN commit r27953.
This commit is contained in:
phosit 2023-11-23 20:42:18 +00:00
parent 63a4799f56
commit 949be94aab
6 changed files with 82 additions and 49 deletions

View File

@ -643,6 +643,14 @@ static void RunGameOrAtlas(const PS::span<const char* const> argv)
{
g_Shutdown = ShutdownType::None;
// Do this as soon as possible, because it chdirs and will mess up the error reporting if
// anything crashes before the working directory is set.
InitVfs(args);
// This must come after VFS init, which sets the current directory (required for finding our
// output log files).
FileLogger logger;
if (!Init(args, flags))
{
flags &= ~INIT_MODS;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -34,6 +34,7 @@
#include <ctime>
#include <fstream>
#include <iostream>
#include <utility>
#include <boost/algorithm/string/replace.hpp>
@ -310,31 +311,30 @@ void CLogger::CleanupRenderQueue()
m_RenderMessages.erase(m_RenderMessages.begin(), m_RenderMessages.end() - RENDER_LIMIT);
}
TestLogger::TestLogger()
{
m_OldLogger = g_Logger;
g_Logger = new CLogger(&m_Stream, &blackHoleStream, false, false);
}
CLogger::ScopedReplacement::ScopedReplacement() :
m_OldLogger{std::exchange(g_Logger, &m_ThisLogger)}
{}
TestLogger::~TestLogger()
CLogger::ScopedReplacement::ScopedReplacement(std::ostream* mainLog, std::ostream* interestingLog,
const bool takeOwnership, const bool useDebugPrintf) :
m_ThisLogger{mainLog, interestingLog, takeOwnership, useDebugPrintf},
m_OldLogger{std::exchange(g_Logger, &m_ThisLogger)}
{}
CLogger::ScopedReplacement::~ScopedReplacement()
{
delete g_Logger;
g_Logger = m_OldLogger;
}
TestLogger::TestLogger() :
m_ScopedReplacement{&m_Stream, &blackHoleStream, false, false}
{}
std::string TestLogger::GetOutput()
{
return m_Stream.str();
}
TestStdoutLogger::TestStdoutLogger()
{
m_OldLogger = g_Logger;
g_Logger = new CLogger(&std::cout, &blackHoleStream, false, false);
}
TestStdoutLogger::~TestStdoutLogger()
{
delete g_Logger;
g_Logger = m_OldLogger;
}
TestStdoutLogger::TestStdoutLogger() :
m_ScopedReplacement{&std::cout, &blackHoleStream, false, false}
{}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -47,6 +47,8 @@ class CLogger
{
NONCOPYABLE(CLogger);
public:
class ScopedReplacement;
enum ELogMethod
{
Normal,
@ -109,19 +111,48 @@ private:
};
/**
* Helper class for unit tests - captures all log output while it is in scope,
* and returns it as a single string.
* Replaces `g_Logger` for as long as it's in scope.
*/
class CLogger::ScopedReplacement
{
public:
ScopedReplacement();
ScopedReplacement(std::ostream* mainLog, std::ostream* interestingLog, const bool takeOwnership,
const bool useDebugPrintf);
ScopedReplacement(const ScopedReplacement&) = delete;
ScopedReplacement& operator=(const ScopedReplacement&) = delete;
ScopedReplacement(ScopedReplacement&&) = delete;
ScopedReplacement& operator=(ScopedReplacement&&) = delete;
~ScopedReplacement();
private:
CLogger m_ThisLogger;
CLogger* m_OldLogger;
};
/**
* This is used in the engine to log the messages to the logfiles.
*/
class FileLogger
{
private:
CLogger::ScopedReplacement m_ScopedReplacement;
};
/**
* Helper class for unit tests - captures all log output, and returns it as a
* single string.
*/
class TestLogger
{
NONCOPYABLE(TestLogger);
public:
TestLogger();
~TestLogger();
std::string GetOutput();
private:
CLogger* m_OldLogger;
std::stringstream m_Stream;
CLogger::ScopedReplacement m_ScopedReplacement;
};
/**
@ -129,12 +160,10 @@ private:
*/
class TestStdoutLogger
{
NONCOPYABLE(TestStdoutLogger);
public:
TestStdoutLogger();
~TestStdoutLogger();
private:
CLogger* m_OldLogger;
CLogger::ScopedReplacement m_ScopedReplacement;
};
#endif

View File

@ -155,12 +155,10 @@ void MountMods(const Paths& paths, const std::vector<CStr>& mods)
g_VFS->Mount(L"", modUserPath / "user" / "", userFlags, InDevelopmentCopy() ? 0 : priority + 1);
}
static void InitVfs(const CmdLineArgs& args, int flags)
void InitVfs(const CmdLineArgs& args)
{
TIMER(L"InitVfs");
const bool setup_error = (flags & INIT_HAVE_DISPLAY_ERROR) == 0;
const Paths paths(args);
OsPath logs(paths.Logs());
@ -172,8 +170,7 @@ static void InitVfs(const CmdLineArgs& args, int flags)
AppHooks hooks = {0};
hooks.bundle_logs = psBundleLogs;
hooks.get_log_dir = psLogDir;
if (setup_error)
hooks.display_error = psDisplayError;
hooks.display_error = psDisplayError;
app_hooks_update(&hooks);
g_VFS = CreateVfs();
@ -407,7 +404,6 @@ from_config:
CNetHost::Deinitialize();
// should be last, since the above use them
SAFE_DELETE(g_Logger);
delete &g_Profiler;
delete &g_ProfileViewer;
@ -523,15 +519,6 @@ bool AutostartVisualReplay(const std::string& replayFile);
bool Init(const CmdLineArgs& args, int flags)
{
// Do this as soon as possible, because it chdirs
// and will mess up the error reporting if anything
// crashes before the working directory is set.
InitVfs(args, flags);
// This must come after VFS init, which sets the current directory
// (required for finding our output log files).
g_Logger = new CLogger;
new CProfileViewer;
new CProfileManager; // before any script code

View File

@ -44,10 +44,6 @@ enum InitFlags
// needed by map editor because it uses its own GUI.
INIT_NO_GUI = 2,
// avoid setting display_error app hook
// needed by map editor because it has its own wx error display
INIT_HAVE_DISPLAY_ERROR = 4,
// initialize the mod folders from command line parameters
INIT_MODS = 8,
@ -72,6 +68,8 @@ extern const std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags);
*/
extern void MountMods(const Paths& paths, const std::vector<CStr>& mods);
void InitVfs(const CmdLineArgs& args);
/**
* Returns true if successful, false if Init is aborted early (for instance if
* mods changed, or if we are using -dumpSchema).

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -43,6 +43,8 @@
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include <optional>
#if OS_WIN
// We don't include wutil header directly to prevent including Windows headers.
extern void wutil_SetAppWindow(void* hwnd);
@ -50,7 +52,8 @@ extern void wutil_SetAppWindow(void* hwnd);
namespace AtlasMessage
{
namespace
{
InputProcessor g_Input;
// This keeps track of the last in-game user input.
@ -60,12 +63,19 @@ double last_user_activity;
// see comment in GameLoop.cpp about ah_display_error before using INIT_HAVE_DISPLAY_ERROR
const int g_InitFlags = INIT_HAVE_VMODE | INIT_NO_GUI;
// This isn't used directly. When it's emplaced and when it's reset it does mutate `g_Logger`.
std::optional<FileLogger> g_FileLogger;
}
MESSAGEHANDLER(Init)
{
UNUSED2(msg);
g_Quickstart = true;
InitVfs(g_AtlasGameLoop->args);
g_FileLogger.emplace();
// Mount mods if there are any specified as command line parameters
if (!Init(g_AtlasGameLoop->args, g_InitFlags | INIT_MODS| INIT_MODS_PUBLIC))
{
@ -138,6 +148,7 @@ MESSAGEHANDLER(Shutdown)
int flags = 0;
Shutdown(flags);
g_FileLogger.reset();
}