258 lines
7.8 KiB
C++
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;
|
|
} |