many C-string -> std::string replacements and some additional code taken from OpenLieroX
git-svn-id: https://clonekeenplus.svn.sourceforge.net/svnroot/clonekeenplus/cgenius/trunk@87 4df4b0f3-56ce-47cb-b001-ed939b7d65a6
This commit is contained in:
584
src/Debug.cpp
Normal file
584
src/Debug.cpp
Normal file
@@ -0,0 +1,584 @@
|
||||
/*
|
||||
* Debug.cpp
|
||||
* OpenLieroX
|
||||
*
|
||||
* Created by Albert Zeyer on 01.01.09.
|
||||
* code under LGPL
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Debug.h"
|
||||
#include "StringUtils.h"
|
||||
#include "CrashHandler.h"
|
||||
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
void RaiseDebugger() {
|
||||
#ifdef DEBUG
|
||||
// HINT: ignored when not in debugger
|
||||
// If it just does nothing then, remove the surrounding #ifdef DEBUG
|
||||
// I read about a Win32's IsDebuggerPresent() function, perhaps you should use that one here.
|
||||
__asm { int 3 };
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#include <cassert>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
// Based on Apple's recommended method as described in
|
||||
// http://developer.apple.com/qa/qa2004/qa1361.html
|
||||
static bool AmIBeingDebugged()
|
||||
// Returns true if the current process is being debugged (either
|
||||
// running under the debugger or has a debugger attached post facto).
|
||||
{
|
||||
// Initialize mib, which tells sysctl what info we want. In this case,
|
||||
// we're looking for information about a specific process ID.
|
||||
int mib[] =
|
||||
{
|
||||
CTL_KERN,
|
||||
KERN_PROC,
|
||||
KERN_PROC_PID,
|
||||
getpid()
|
||||
};
|
||||
|
||||
// Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and
|
||||
// binary interfaces may change.
|
||||
struct kinfo_proc info;
|
||||
size_t info_size = sizeof ( info );
|
||||
|
||||
int sysctl_result = sysctl ( mib, sizeof(mib) / sizeof(*mib), &info, &info_size, NULL, 0 );
|
||||
if ( sysctl_result != 0 )
|
||||
return false;
|
||||
|
||||
// This process is being debugged if the P_TRACED flag is set.
|
||||
return ( info.kp_proc.p_flag & P_TRACED ) != 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
static bool AmIBeingDebugged() {
|
||||
// We can look in /proc/self/status for TracerPid. We are likely used in crash
|
||||
// handling, so we are careful not to use the heap or have side effects.
|
||||
int status_fd = open("/proc/self/status", O_RDONLY);
|
||||
if (status_fd == -1)
|
||||
return false;
|
||||
|
||||
// We assume our line will be in the first 1024 characters and that we can
|
||||
// read this much all at once. In practice this will generally be true.
|
||||
// This simplifies and speeds up things considerably.
|
||||
char buf[1024];
|
||||
|
||||
ssize_t num_read = read(status_fd, buf, sizeof(buf));
|
||||
fix_markend(buf);
|
||||
close(status_fd);
|
||||
if (num_read <= 0) return false;
|
||||
|
||||
const char* searchStr = "TracerPid:\t";
|
||||
const char* f = strstr(buf, searchStr);
|
||||
if(f == NULL) return false;
|
||||
|
||||
// Our pid is 0 without a debugger, assume this for any pid starting with 0.
|
||||
f += strlen(searchStr);
|
||||
return f < &buf[num_read] && *f != '0';
|
||||
}
|
||||
|
||||
#endif // OSX/LINUX
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
void RaiseDebugger() {
|
||||
if(AmIBeingDebugged()) {
|
||||
printf("I am being debugged, raising debugger ...\n");
|
||||
CrashHandler::get()->disable();
|
||||
// TODO: We need a way to set another ucontext here. (And that should be specified via a parameter
|
||||
// to RaiseDebugger().) E.g. when we use this function in the debugger thread, we want to set the
|
||||
// ucontext of the main loop thread.
|
||||
raise(SIGABRT);
|
||||
CrashHandler::get()->enable();
|
||||
} else
|
||||
printf("I am not being debugged, ignoring debugger raise.\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include "AuxLib.h" // for Windows.h
|
||||
|
||||
#include <DbgHelp.h>
|
||||
#include <ShlObj.h>
|
||||
|
||||
#include "LieroX.h"
|
||||
#include "CClient.h"
|
||||
#include "CServer.h"
|
||||
#include "DedicatedControl.h"
|
||||
#include "StringUtils.h"
|
||||
#include "ConversationLogger.h"
|
||||
#include "CGameMode.h"
|
||||
|
||||
#define itoa _itoa
|
||||
|
||||
void *ReadGameStateForReport(char *buffer, size_t bufsize)
|
||||
{
|
||||
memset(buffer, 0, bufsize);
|
||||
__try {
|
||||
if (cClient) {
|
||||
strncat(buffer, "Game state:\n", bufsize);
|
||||
if (cClient->getStatus() == NET_CONNECTED) {
|
||||
if (cClient->getGameReady())
|
||||
strncat(buffer, "In game, selecting weapons.", bufsize);
|
||||
else
|
||||
strncat(buffer, "In lobby.", bufsize);
|
||||
} else if (cClient->getStatus() == NET_PLAYING) {
|
||||
strncat(buffer, "In game, playing.", bufsize);
|
||||
} else if (cClient->getStatus() == NET_CONNECTING) {
|
||||
strncat(buffer, "Connecting to a server.", bufsize);
|
||||
} else if (cClient->getStatus() == NET_DISCONNECTED) {
|
||||
strncat(buffer, "Disconnected.\n", bufsize);
|
||||
} else {
|
||||
strncat(buffer, "Unknown state.\n", bufsize);
|
||||
}
|
||||
}
|
||||
buffer[bufsize - 1] = '\0';
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{ return buffer; }
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void *ReadGameInfoForReport(char *buffer, size_t bufsize)
|
||||
{
|
||||
memset(buffer, 0, bufsize);
|
||||
if (!tLXOptions || !tLX)
|
||||
return buffer;
|
||||
char tmp[32];
|
||||
__try {
|
||||
|
||||
// Game type
|
||||
strncat(buffer, "iGameType = ", bufsize);
|
||||
switch (tLX->iGameType) {
|
||||
case GME_LOCAL:
|
||||
strncat(buffer, "GME_LOCAL", bufsize);
|
||||
break;
|
||||
case GME_HOST:
|
||||
strncat(buffer, "GME_HOST", bufsize);
|
||||
break;
|
||||
case GME_JOIN:
|
||||
strncat(buffer, "GME_JOIN", bufsize);
|
||||
break;
|
||||
default:
|
||||
itoa(tLX->iGameType, tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "UNKNOWN ", bufsize); strncat(buffer, tmp, bufsize);
|
||||
}
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Game mode
|
||||
strncat(buffer, "GameMode = ", bufsize);
|
||||
char tmp[16];
|
||||
itoa(tLXOptions->tGameInfo.gameMode->GeneralGameType(), tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Mod name
|
||||
strncat(buffer, "sModName = ", bufsize);
|
||||
if (tLXOptions->tGameInfo.sModName.size())
|
||||
strncat(buffer, tLXOptions->tGameInfo.sModName.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Map file
|
||||
strncat(buffer, "sMapFile = ", bufsize);
|
||||
if (tLXOptions->tGameInfo.sMapFile.size())
|
||||
strncat(buffer, tLXOptions->tGameInfo.sMapFile.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Map name
|
||||
strncat(buffer, "sMapName = ", bufsize);
|
||||
if (tLXOptions->tGameInfo.sMapName.size())
|
||||
strncat(buffer, tLXOptions->tGameInfo.sMapName.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Mod dir
|
||||
strncat(buffer, "sModDir = ", bufsize);
|
||||
if (tLXOptions->tGameInfo.sModDir.size())
|
||||
strncat(buffer, tLXOptions->tGameInfo.sModDir.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Loading time
|
||||
itoa(tLXOptions->tGameInfo.iLoadingTime, tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "iLoadingTimes = ", bufsize);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Server name
|
||||
strncat(buffer, "sServerName = ", bufsize);
|
||||
if (tLXOptions->sServerName.size())
|
||||
strncat(buffer, tLXOptions->sServerName.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Welcome message
|
||||
strncat(buffer, "sWelcomeMessage = ", bufsize);
|
||||
if (tLXOptions->sWelcomeMessage.size())
|
||||
strncat(buffer, tLXOptions->sWelcomeMessage.c_str(), bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Lives
|
||||
itoa(tLXOptions->tGameInfo.iLives, tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "iLives = ", bufsize);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Max kills
|
||||
itoa(tLXOptions->tGameInfo.iKillLimit, tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "iKillLimit = ", bufsize);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Time limit
|
||||
itoa((int)(tLXOptions->tGameInfo.fTimeLimit * 10), tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "fTimeLimit = ", bufsize);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Bonuses on
|
||||
strncat(buffer, "bBonusesOn = ", bufsize);
|
||||
strncat(buffer, tLXOptions->tGameInfo.bBonusesOn ? "true" : "false", bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Bonus names
|
||||
strncat(buffer, "bShowBonusName = ", bufsize);
|
||||
strncat(buffer, tLXOptions->tGameInfo.bShowBonusName ? "true" : "false", bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
|
||||
// Number of players
|
||||
if (cServer) {
|
||||
itoa(cServer->getNumPlayers(), tmp, 10);
|
||||
fix_markend(tmp);
|
||||
strncat(buffer, "iNumPlayers = ", bufsize);
|
||||
strncat(buffer, tmp, bufsize);
|
||||
strncat(buffer, "\n", bufsize);
|
||||
}
|
||||
|
||||
buffer[bufsize - 1] = '\0';
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return buffer;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// This function also used in CrashHandler.cpp
|
||||
void OlxWriteCoreDump_Win32(const char* fileName, PEXCEPTION_POINTERS pExInfo )
|
||||
{
|
||||
// MSVC-compatible core dump, GDB cannot read it :(
|
||||
// Set the exception info for the minidump
|
||||
MINIDUMP_EXCEPTION_INFORMATION eInfo;
|
||||
eInfo.ThreadId = GetCurrentThreadId();
|
||||
eInfo.ExceptionPointers = pExInfo;
|
||||
eInfo.ClientPointers = FALSE;
|
||||
|
||||
// Set the minidump info
|
||||
MINIDUMP_CALLBACK_INFORMATION cbMiniDump;
|
||||
cbMiniDump.CallbackRoutine = NULL;
|
||||
cbMiniDump.CallbackParam = 0;
|
||||
|
||||
// Additional data
|
||||
MINIDUMP_USER_STREAM pExtraInfo[3];
|
||||
|
||||
// Version info
|
||||
char version[64];
|
||||
strcpy(version, GetFullGameName());
|
||||
pExtraInfo[0].Type = LastReservedStream + 1;
|
||||
pExtraInfo[0].BufferSize = sizeof(version);
|
||||
pExtraInfo[0].Buffer = (void *)&version[0];
|
||||
|
||||
// Current game info
|
||||
char game_info[1024];
|
||||
pExtraInfo[1].Type = LastReservedStream + 2;
|
||||
pExtraInfo[1].BufferSize = sizeof(game_info);
|
||||
pExtraInfo[1].Buffer = ReadGameInfoForReport(game_info, sizeof(game_info));
|
||||
|
||||
// Current game state
|
||||
char game_state[1024];
|
||||
pExtraInfo[2].Type = LastReservedStream + 3;
|
||||
pExtraInfo[2].BufferSize = sizeof(game_state);
|
||||
pExtraInfo[2].Buffer = ReadGameStateForReport(game_state, sizeof(game_state));
|
||||
|
||||
MINIDUMP_USER_STREAM_INFORMATION iStreams;
|
||||
iStreams.UserStreamCount = sizeof(pExtraInfo)/sizeof(MINIDUMP_USER_STREAM);
|
||||
iStreams.UserStreamArray = pExtraInfo;
|
||||
|
||||
// Open the file
|
||||
HANDLE hFile = CreateFile((LPCSTR)fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
|
||||
|
||||
// Write the minidump
|
||||
if (hFile) {
|
||||
MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpScanMemory | MiniDumpWithIndirectlyReferencedMemory);
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, type, &eInfo,&iStreams,&cbMiniDump);
|
||||
}
|
||||
|
||||
// Close the file
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
void OlxWriteCoreDump(const char* fileName)
|
||||
{
|
||||
OlxWriteCoreDump_Win32(fileName, NULL);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef GCOREDUMPER
|
||||
#include <google/coredumper.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#ifndef GCOREDUMPER
|
||||
static void GdbWriteCoreDump(const char* fname) {
|
||||
// WARNING: this is terribly slow like this
|
||||
char gdbparam[1000];
|
||||
sprintf(gdbparam,
|
||||
"attach %i \n"
|
||||
"gcore %s \n"
|
||||
"detach \n"
|
||||
"quit \n",
|
||||
getpid(), fname);
|
||||
FILE* p = popen("gdb -q", "w");
|
||||
if(p) {
|
||||
fprintf(p, "%s", gdbparam);
|
||||
fflush(p);
|
||||
int status = 0; wait(&status);
|
||||
pclose(p);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void OlxWriteCoreDump(const char* file_postfix) {
|
||||
char corefile[PATH_MAX + 100];
|
||||
if(getcwd(corefile, PATH_MAX) == NULL) strcpy(corefile, "");
|
||||
strcat(corefile, "/core.OpenLieroX");
|
||||
if(file_postfix) { strcat(corefile, "."); strcat(corefile, file_postfix); }
|
||||
printf("writing coredump to %s\n", corefile);
|
||||
|
||||
printf("dumping core ... "); fflush(0);
|
||||
#ifdef GCOREDUMPER
|
||||
WriteCoreDump(corefile);
|
||||
#else
|
||||
GdbWriteCoreDump(corefile);
|
||||
#endif
|
||||
printf("ready\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ----------------- core dump code end -------------
|
||||
|
||||
|
||||
// --------------- backtrace stuff start --------------
|
||||
|
||||
#ifndef HAVE_EXECINFO
|
||||
# if defined(__linux__)
|
||||
# define HAVE_EXECINFO 1
|
||||
# elif defined(__DARWIN_VERS_1050)
|
||||
# define HAVE_EXECINFO 1
|
||||
# else
|
||||
# define HAVE_EXECINFO 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if HAVE_EXECINFO
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void DumpCallstackPrintf(void* callpnt) {
|
||||
void *callstack[128];
|
||||
int framesC = backtrace(callstack, sizeof(callstack));
|
||||
printf("backtrace() returned %d addresses\n", framesC);
|
||||
if(callpnt != NULL && framesC > 3) callstack[3] = callpnt; // expected to be called from signal handler
|
||||
char** strs = backtrace_symbols(callstack, framesC);
|
||||
for(int i = 0; i < framesC; ++i) {
|
||||
if(strs[i])
|
||||
printf("%s\n", strs[i]);
|
||||
else
|
||||
break;
|
||||
}
|
||||
free(strs);
|
||||
}
|
||||
|
||||
void DumpCallstack(void (*PrintOutFct) (const std::string&)) {
|
||||
void *callstack[128];
|
||||
int framesC = backtrace(callstack, sizeof(callstack));
|
||||
(*PrintOutFct) ("DumpCallstack: " + itoa(framesC) + " addresses:");
|
||||
char** strs = backtrace_symbols(callstack, framesC);
|
||||
for(int i = 0; i < framesC; ++i) {
|
||||
if(strs[i])
|
||||
(*PrintOutFct) (std::string(" ") + strs[i] + "\n");
|
||||
else
|
||||
break;
|
||||
}
|
||||
free(strs);
|
||||
}
|
||||
|
||||
#elif defined(WIN32)
|
||||
|
||||
#include "StackWalker.h" // Call Luke Stackwalker for help
|
||||
|
||||
typedef void (*PrintOutFct) (const std::string&);
|
||||
|
||||
// Override the default stackwalker with our own print functions
|
||||
class PrintStackWalker : public StackWalker {
|
||||
private:
|
||||
PrintOutFct m_print;
|
||||
|
||||
public:
|
||||
PrintStackWalker(PrintOutFct fct = NULL) : StackWalker(RetrieveVerbose) { m_print = fct; }
|
||||
void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OnOutput(LPCSTR szText)
|
||||
{
|
||||
if (m_print == NULL)
|
||||
printf(szText);
|
||||
else
|
||||
m_print(std::string(szText));
|
||||
StackWalker::OnOutput(szText);
|
||||
}
|
||||
};
|
||||
|
||||
void DumpCallstackPrintf(void* callpnt)
|
||||
{
|
||||
PrintStackWalker sw;
|
||||
sw.ShowCallstack();
|
||||
|
||||
}
|
||||
void DumpCallstack(void (*LineOutFct) (const std::string&))
|
||||
{
|
||||
PrintStackWalker sw(LineOutFct);
|
||||
sw.ShowCallstack();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#warning No DumpCallstack implementation for this arch/sys
|
||||
|
||||
void DumpCallstackPrintf(void* callpnt) {
|
||||
printf("DumpCallstackPrintf not implemented\n");
|
||||
}
|
||||
|
||||
void DumpCallstack(void (*PrintOutFct) (const std::string&)) {
|
||||
printf("DumpCallstack not implemented\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::string GetLogTimeStamp()
|
||||
{
|
||||
// TODO: please recode this, don't use C-strings!
|
||||
char buf[64];
|
||||
const time_t unif_time = time(NULL);
|
||||
struct tm *t = localtime(&unif_time);
|
||||
if (t == NULL)
|
||||
return "";
|
||||
|
||||
strftime(buf, sizeof(buf), "[%H:%M:%S] ", t);
|
||||
fix_markend(buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
Logger notes(0,2,1000, "n: ");
|
||||
Logger hints(0,1,100, "H: ");
|
||||
Logger warnings(0,0,10, "W: ");
|
||||
Logger errors(-1,-1,1, "E: ");
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include "ThreadPool.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
static SDL_mutex* globalCoutMutex = NULL;
|
||||
|
||||
Logger::Logger(int o, int ingame, int callst, const std::string& p)
|
||||
: minCoutVerb(o), minIngameConVerb(ingame), minCallstackVerb(callst), prefix(p), lastWasNewline(true), mutex(NULL) {
|
||||
mutex = SDL_CreateMutex();
|
||||
if(!globalCoutMutex)
|
||||
globalCoutMutex = SDL_CreateMutex();
|
||||
}
|
||||
|
||||
Logger::~Logger() {
|
||||
SDL_DestroyMutex(mutex); mutex = NULL;
|
||||
if(globalCoutMutex) {
|
||||
SDL_DestroyMutex(globalCoutMutex);
|
||||
globalCoutMutex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::lock() {
|
||||
SDL_mutexP(mutex);
|
||||
}
|
||||
|
||||
void Logger::unlock() {
|
||||
SDL_mutexV(mutex);
|
||||
}
|
||||
|
||||
static void CoutPrint(const std::string& str) {
|
||||
// TODO: We have used std::cout here before but it doesn't seem to work after a while for some reason.
|
||||
printf("%s", str.c_str());
|
||||
}
|
||||
|
||||
int Logger_Verbosity = 0;
|
||||
|
||||
// true if last was newline
|
||||
static bool logger_output(Logger& log, const std::string& buf) {
|
||||
bool ret = true;
|
||||
|
||||
std::string prefix = log.prefix;
|
||||
|
||||
if(Logger_Verbosity >= log.minCoutVerb) {
|
||||
SDL_mutexP(globalCoutMutex);
|
||||
ret = PrettyPrint(prefix, buf, CoutPrint, log.lastWasNewline);
|
||||
//std::cout.flush();
|
||||
SDL_mutexV(globalCoutMutex);
|
||||
}
|
||||
if(Logger_Verbosity >= log.minCallstackVerb) {
|
||||
DumpCallstackPrintf();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Logger& Logger::flush() {
|
||||
lock();
|
||||
lastWasNewline = logger_output(*this, buffer);
|
||||
buffer = "";
|
||||
unlock();
|
||||
return *this;
|
||||
}
|
||||
Reference in New Issue
Block a user