2025-07-22 22:06:34 +03:00

258 lines
7.8 KiB
C++

// used: [crt] time_t, time, localtime_s
#include <ctime>
#include "log.h"
// using: mem_stackalloc, mem_stackfree
#include "memory.h"
// used: IsPowerOfTwo
#include "math.h"
// used: GetWorkingPath
#include "../core.h"
// console write stream
static HANDLE hConsoleStream = INVALID_HANDLE_VALUE;
// file write stream
static HANDLE hFileStream = INVALID_HANDLE_VALUE;
#pragma region log_main
bool L::AttachConsole(const wchar_t* wszWindowTitle)
{
// allocate memory for console
if (::AllocConsole() != TRUE)
return false;
// open console output stream
if (hConsoleStream = ::CreateFileW(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); hConsoleStream == INVALID_HANDLE_VALUE)
return false;
// @test: unnecessary as fas as we don't use std::cout etc
if (::SetStdHandle(STD_OUTPUT_HANDLE, hConsoleStream) != TRUE)
return false;
// set console window title
if (::SetConsoleTitleW(wszWindowTitle) != TRUE)
return false;
return true;
}
void L::DetachConsole()
{
::CloseHandle(hConsoleStream);
// free allocated memory for console
if (::FreeConsole() != TRUE)
return;
// close console window
if (const HWND hConsoleWindow = ::GetConsoleWindow(); hConsoleWindow != nullptr)
::PostMessageW(hConsoleWindow, WM_CLOSE, 0U, 0L);
}
bool L::OpenFile(const wchar_t* wszFileName)
{
wchar_t wszFilePath[MAX_PATH];
if (!CORE::GetWorkingPath(wszFilePath))
return false;
CRT::StringCat(wszFilePath, wszFileName);
// @todo: append time/date to filename and always keep up to 3 files, otherwise delete with lowest date
// open file output stream
if (hFileStream = ::CreateFileW(wszFilePath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); hFileStream == INVALID_HANDLE_VALUE)
return false;
// insert UTF-8 BOM
::WriteFile(hFileStream, "\xEF\xBB\xBF", 3UL, nullptr, nullptr);
return true;
}
void L::CloseFile()
{
::CloseHandle(hFileStream);
}
void L::WriteMessage(const char* szMessage, const std::size_t nMessageLength)
{
#ifdef CS_LOG_CONSOLE
::WriteConsoleA(hConsoleStream, szMessage, nMessageLength, nullptr, nullptr);
#endif
#ifdef CS_LOG_FILE
::WriteFile(hFileStream, szMessage, nMessageLength, nullptr, nullptr);
#endif
}
#pragma endregion
#pragma region log_stream_control
L::Stream_t::ColorMarker_t L::SetColor(const LogColorFlags_t nColorFlags)
{
return { nColorFlags };
}
L::Stream_t::PrecisionMarker_t L::SetPrecision(const int iPrecision)
{
return { iPrecision };
}
L::Stream_t::ModeMarker_t L::AddFlags(const LogModeFlags_t nModeFlags)
{
return { nModeFlags };
}
L::Stream_t::ModeMarker_t L::RemoveFlags(const LogModeFlags_t nModeFlags)
{
return { static_cast<LogModeFlags_t>(nModeFlags | LOG_MODE_REMOVE) };
}
#pragma endregion
L::Stream_t& L::Stream_t::operator()(const ELogLevel nLevel, const char* szFileBlock)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
// reset previous flags
nModeFlags = LOG_MODE_NONE;
const char* szTypeBlock = nullptr;
[[maybe_unused]] LogColorFlags_t nTypeColorFlags = LOG_COLOR_DEFAULT;
switch (nLevel)
{
case LOG_INFO:
szTypeBlock = "[info] ";
nTypeColorFlags = LOG_COLOR_FORE_CYAN;
break;
case LOG_WARNING:
szTypeBlock = "[warning] ";
nTypeColorFlags = LOG_COLOR_FORE_YELLOW;
break;
case LOG_ERROR:
szTypeBlock = "[error] ";
nTypeColorFlags = LOG_COLOR_FORE_RED;
break;
default:
break;
}
const std::time_t time = std::time(nullptr);
std::tm timePoint;
localtime_s(&timePoint, &time);
// @todo: no new line at first use / ghetto af but cheap enough but still ghetto uhhh
char szTimeBuffer[32];
const std::size_t nTimeSize = CRT::TimeToString(szTimeBuffer, sizeof(szTimeBuffer), "\n[%d-%m-%Y %T] ", &timePoint) - bFirstPrint;
#ifdef CS_LOG_CONSOLE
::SetConsoleTextAttribute(hConsoleStream, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
::WriteConsoleA(hConsoleStream, szTimeBuffer + bFirstPrint, nTimeSize, nullptr, nullptr);
if (szFileBlock != nullptr)
{
::SetConsoleTextAttribute(hConsoleStream, FOREGROUND_INTENSITY);
::WriteConsoleA(hConsoleStream, szFileBlock, CRT::StringLength(szFileBlock), nullptr, nullptr);
}
if (szTypeBlock != nullptr)
{
::SetConsoleTextAttribute(hConsoleStream, static_cast<WORD>(nTypeColorFlags));
::WriteConsoleA(hConsoleStream, szTypeBlock, CRT::StringLength(szTypeBlock), nullptr, nullptr);
}
::SetConsoleTextAttribute(hConsoleStream, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
#endif
#ifdef CS_LOG_FILE
::WriteFile(hFileStream, szTimeBuffer + bFirstPrint, nTimeSize, nullptr, nullptr);
char szBlockBuffer[MAX_PATH] = { '\0' };
char* szCurrentBlock = szBlockBuffer;
if (szFileBlock != nullptr)
szCurrentBlock = CRT::StringCat(szCurrentBlock, szFileBlock);
if (szTypeBlock != nullptr)
szCurrentBlock = CRT::StringCat(szCurrentBlock, szTypeBlock);
if (szBlockBuffer[0] != '\0')
::WriteFile(hFileStream, szBlockBuffer, static_cast<DWORD>(szCurrentBlock - szBlockBuffer), nullptr, nullptr);
#endif
bFirstPrint = false;
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const ColorMarker_t colorMarker)
{
#ifdef CS_LOG_CONSOLE
::SetConsoleTextAttribute(hConsoleStream, static_cast<WORD>(colorMarker.nColorFlags));
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const PrecisionMarker_t precisionMarker)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
this->iPrecision = precisionMarker.iPrecision;
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const ModeMarker_t modeMarker)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
CS_ASSERT(nModeFlags == 0U || MATH::IsPowerOfTwo(nModeFlags & LOG_MODE_INT_FORMAT_MASK)); // used conflicting format flags
if (modeMarker.nModeFlags & LOG_MODE_REMOVE)
nModeFlags &= ~modeMarker.nModeFlags;
else
nModeFlags |= modeMarker.nModeFlags;
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const char* szMessage)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
WriteMessage(szMessage, CRT::StringLength(szMessage));
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const wchar_t* wszMessage)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
/*
* to keep stream orientation always same, convert message to UTF-8
*
* regarding to C++ standard:
* [C++11: 27.4.1/3]:
* mixing operations on corresponding wide- and narrow-character streams follows the same semantics as mixing such operations on 'FILE's, as specified in amendation [1] of the ISO C standard
*
* [1]:
* the definition of a stream was changed to include the concept of an orientation for both text and binary streams.
* after a stream is associated with a file, but before any operations are performed on the stream, the stream is without orientation.
* if a wide-character input or output function is applied to a stream without orientation, the stream becomes wide-oriented.
* likewise, if a byte input or output operation is applied to a stream with orientation, the stream becomes byte-oriented.
* thereafter, only the 'fwide()' or 'freopen()' functions can alter the orientation of a stream.
* byte input/output functions shall not be applied to a wide-oriented stream and wide-character input/output functions shall not be applied to a byte-oriented stream.
*/
const std::size_t nMessageLength = CRT::StringLengthMultiByte(wszMessage);
char* szMessage = static_cast<char*>(MEM_STACKALLOC(nMessageLength + 1U));
CRT::StringUnicodeToMultiByte(szMessage, nMessageLength + 1U, wszMessage);
WriteMessage(szMessage, nMessageLength);
MEM_STACKFREE(szMessage);
#endif
return *this;
}
L::Stream_t& L::Stream_t::operator<<(const bool bValue)
{
#if defined(CS_LOG_CONSOLE) || defined(CS_LOG_FILE)
const char* szBoolean = ((nModeFlags & LOG_MODE_BOOL_ALPHA) ? (bValue ? "true" : "false") : (bValue ? "1" : "0"));
const std::size_t nBooleanLength = CRT::StringLength(szBoolean);
WriteMessage(szBoolean, nBooleanLength);
#endif
return *this;
}