Files
Kyty/source/emulator/src/Log.cpp
T
2022-05-25 20:07:51 +10:00

305 lines
6.0 KiB
C++

#include "Emulator/Log.h"
#include "Kyty/Core/DbgAssert.h"
#include "Kyty/Core/File.h"
#include "Kyty/Core/String.h"
#include "Kyty/Core/Subsystems.h"
#include "Kyty/Core/Threads.h"
#include "Kyty/Core/Vector.h"
#include "Emulator/Common.h"
#include "Emulator/Config.h"
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
// IWYU pragma: no_include <handleapi.h>
// IWYU pragma: no_include <minwindef.h>
// IWYU pragma: no_include <processenv.h>
// IWYU pragma: no_include <winbase.h>
// IWYU pragma: no_include <wincon.h>
#endif
#ifdef KYTY_EMU_ENABLED
namespace Kyty {
namespace Log {
static bool g_log_initialized = false;
static Core::Mutex* g_mutex = nullptr;
static Direction g_dir = Direction::Console;
static Core::File* g_file = nullptr;
static bool g_colored_printf = false;
static thread_local Core::File* g_thread_local_file = nullptr;
static Vector<Core::File*>* g_thread_local_files = nullptr;
static bool EnableVTMode()
{
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
// Set output mode to handle virtual terminal sequences
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
if (h == INVALID_HANDLE_VALUE)
{
return false;
}
DWORD dw_mode = 0;
if (GetConsoleMode(h, &dw_mode) == 0)
{
return false;
}
dw_mode |= static_cast<DWORD>(ENABLE_VIRTUAL_TERMINAL_PROCESSING);
return (SetConsoleMode(h, dw_mode) != 0);
#endif
return true;
}
bool IsColoredPrintf()
{
return g_colored_printf;
}
String RemoveColors(const String& str)
{
uint32_t start = 0;
String ret;
for (;;)
{
auto index = str.FindIndex(U'\x1b', start);
if (!str.IndexValid(index))
{
ret += str.Mid(start);
break;
}
ret += str.Mid(start, index - start);
index = str.FindIndex(U'm', index);
if (!str.IndexValid(index))
{
break;
}
start = index + 1;
}
return ret;
}
static void Close()
{
if (g_log_initialized)
{
g_mutex->Lock();
if (g_dir == Direction::File && g_file != nullptr)
{
g_file->Flush();
g_file->Close();
delete g_file;
g_file = nullptr;
}
if (g_dir == Direction::Directory && !g_thread_local_files->IsEmpty())
{
for (auto* file: *g_thread_local_files)
{
file->Flush();
file->Close();
delete file;
}
g_thread_local_files->Clear();
}
g_mutex->Unlock();
}
}
KYTY_SUBSYSTEM_INIT(Log)
{
if (!g_log_initialized)
{
g_mutex = new Core::Mutex;
g_log_initialized = true;
}
auto dir = Config::GetPrintfDirection();
SetDirection(dir);
if (dir == Log::Direction::File)
{
SetOutputFile(Config::GetPrintfOutputFile());
}
g_thread_local_files = new Vector<Core::File*>;
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log)
{
Close();
}
KYTY_SUBSYSTEM_DESTROY(Log)
{
Close();
}
void SetDirection(Direction dir)
{
EXIT_IF(!Log::g_log_initialized);
EXIT_IF(!Core::Thread::IsMainThread());
if (dir == Direction::Console)
{
g_colored_printf = EnableVTMode();
if (!g_colored_printf)
{
::printf("Colored printf is not supported\n");
}
} else
{
g_colored_printf = false;
}
g_dir = dir;
}
Direction GetDirection()
{
EXIT_IF(!Log::g_log_initialized);
return g_dir;
}
void SetOutputFile(const String& file_name, Core::File::Encoding enc)
{
EXIT_IF(!Log::g_log_initialized);
EXIT_IF(!Core::Thread::IsMainThread());
EXIT_IF(Log::g_dir != Log::Direction::File);
EXIT_IF(Log::g_file != nullptr);
g_file = new Core::File;
g_file->Create(file_name);
if (g_file->IsInvalid())
{
::printf("Can't create log file: %s\n", file_name.C_Str());
delete g_file;
g_file = nullptr;
} else
{
g_file->SetEncoding(enc);
g_file->WriteBOM();
}
}
void SetOutputThreadLocalFile(const String& file_name, Core::File::Encoding enc)
{
EXIT_IF(!Log::g_log_initialized);
EXIT_IF(Log::g_dir != Log::Direction::Directory);
EXIT_IF(Log::g_thread_local_file != nullptr);
EXIT_IF(g_thread_local_files == nullptr);
Core::File::CreateDirectories(file_name.DirectoryWithoutFilename());
g_thread_local_file = new Core::File;
g_thread_local_file->Create(file_name);
if (g_thread_local_file->IsInvalid())
{
::printf("Can't create log file: %s\n", file_name.C_Str());
delete g_thread_local_file;
g_thread_local_file = nullptr;
} else
{
g_thread_local_file->SetEncoding(enc);
g_thread_local_file->WriteBOM();
}
g_mutex->Lock();
g_thread_local_files->Add(g_thread_local_file);
g_mutex->Unlock();
}
void CreateThreadLocalFile()
{
auto file_name = String::FromPrintf("%s/%d.txt", Config::GetPrintfOutputFolder().C_Str(), Core::Thread::GetThreadIdUnique());
SetOutputThreadLocalFile(file_name, Core::File::Encoding::Utf8);
}
} // namespace Log
void emu_printf(const char* format, ...)
{
EXIT_IF(!Log::g_log_initialized);
EXIT_IF(Log::g_mutex == nullptr);
Log::g_mutex->Lock();
{
va_list args {};
va_start(args, format);
String s;
s.Printf(format, args);
va_end(args);
if (!Log::g_colored_printf)
{
s = Log::RemoveColors(s);
}
::printf("%s", s.C_Str());
if (Log::g_dir == Log::Direction::File && Log::g_file != nullptr)
{
Log::g_file->Write(s);
}
}
Log::g_mutex->Unlock();
}
void printf(const char* format, ...)
{
EXIT_IF(!Log::g_log_initialized);
if (Log::g_dir == Log::Direction::Silent)
{
return;
}
EXIT_IF(Log::g_mutex == nullptr);
va_list args {};
va_start(args, format);
String s;
s.Printf(format, args);
va_end(args);
if (!Log::g_colored_printf)
{
s = Log::RemoveColors(s);
}
if (Log::g_dir == Log::Direction::Console)
{
Log::g_mutex->Lock();
::printf("%s", s.C_Str());
Log::g_mutex->Unlock();
} else if (Log::g_dir == Log::Direction::File && Log::g_file != nullptr)
{
Log::g_mutex->Lock();
Log::g_file->Write(s);
Log::g_mutex->Unlock();
} else if (Log::g_dir == Log::Direction::Directory)
{
if (Log::g_thread_local_file == nullptr)
{
Log::CreateThreadLocalFile();
}
if (Log::g_thread_local_file != nullptr)
{
Log::g_thread_local_file->Write(s);
}
}
}
} // namespace Kyty
#endif // KYTY_EMU_ENABLED