// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdmisc.h" #include "nel/misc/types_nl.h" #ifndef NL_OS_WINDOWS # define IsDebuggerPresent() false #endif #ifdef NL_OS_WINDOWS # include # include # include # include #else # include #endif // NL_OS_WINDOWS #include "nel/misc/path.h" #include "nel/misc/mutex.h" #include "nel/misc/report.h" #include "nel/misc/system_utils.h" #include "nel/misc/variable.h" #include "nel/misc/debug.h" #include "nel/misc/displayer.h" using namespace std; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NLMISC { CVariable StdDisplayerColor("nel", "StdDisplayerColor", "Enable colors in std displayer", true, 0, true); static const char *LogTypeToString[][8] = { { "", "ERR", "WRN", "INF", "DBG", "STT", "AST", "UKN" }, { "", "Error", "Warning", "Information", "Debug", "Statistic", "Assert", "Unknown" }, { "", "A fatal error occurs. The program must quit", "", "", "", "", "A failed assertion occurs", "" }, }; const char *IDisplayer::logTypeToString (CLog::TLogType logType, bool longFormat) { if (logType < CLog::LOG_NO || logType > CLog::LOG_UNKNOWN) return ""; return LogTypeToString[longFormat?1:0][logType]; } const char *IDisplayer::dateToHumanString () { time_t date; time (&date); return dateToHumanString (date); } const char *IDisplayer::dateToHumanString (time_t date) { static char cstime[25]; struct tm *tms = localtime(&date); if (tms) strftime (cstime, 25, "%Y/%m/%d %H:%M:%S", tms); else sprintf(cstime, "bad date %d", (uint32)date); return cstime; } const char *IDisplayer::dateToComputerString (time_t date) { static char cstime[25]; smprintf (cstime, 25, "%ld", &date); return cstime; } const char *IDisplayer::HeaderString () { static char header[1024]; smprintf(header, 1024, "\nLog Starting [%s]\n", dateToHumanString()); return header; } IDisplayer::IDisplayer(const char *displayerName) { _Mutex = new CMutex (string(displayerName)+"DISP"); DisplayerName = displayerName; } IDisplayer::~IDisplayer() { delete _Mutex; } /* * Display the string where it does. */ void IDisplayer::display ( const CLog::TDisplayInfo& args, const char *message ) { _Mutex->enter(); try { doDisplay( args, message ); } catch (const Exception &) { // silence } _Mutex->leave(); } // Log format : " : " void CStdDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message ) { bool needSpace = false; //stringstream ss; string str; #ifdef NL_OS_UNIX bool colorSet = false; #endif if (args.LogType != CLog::LOG_NO) { #ifdef NL_OS_UNIX if (StdDisplayerColor.get()) { if (args.LogType == CLog::LOG_ERROR || args.LogType == CLog::LOG_ASSERT) { str += "\e[0;30m\e[41m"; colorSet = true; } // black text, red background else if (args.LogType == CLog::LOG_WARNING) { str += "\e[0;91m"; colorSet = true; } // bright red text else if (args.LogType == CLog::LOG_DEBUG) { str += "\e[0;34m"; colorSet = true; } // blue text } #endif //ss << logTypeToString(args.LogType); str += logTypeToString(args.LogType); needSpace = true; } // Write thread identifier if ( args.ThreadId != 0 ) { //ss << setw(5) << args.ThreadId; if (needSpace) { str += " "; needSpace = false; } #ifdef NL_OS_WINDOWS str += NLMISC::toString("%5x", args.ThreadId); #else str += NLMISC::toString("%08x", args.ThreadId); #endif needSpace = true; } if (args.FileName != NULL) { //if (needSpace) { ss << " "; needSpace = false; } if (needSpace) { str += " "; needSpace = false; } //ss << CFile::getFilename(args.FileName); str += CFile::getFilename(args.FileName); needSpace = true; } if (args.Line != -1) { //if (needSpace) { ss << " "; needSpace = false; } if (needSpace) { str += " "; needSpace = false; } //ss << args.Line; str += NLMISC::toString(args.Line); needSpace = true; } if (args.FuncName != NULL) { //if (needSpace) { ss << " "; needSpace = false; } if (needSpace) { str += " "; needSpace = false; } //ss << args.FuncName; str += args.FuncName; needSpace = true; } if (!args.ProcessName.empty()) { //if (needSpace) { ss << " "; needSpace = false; } if (needSpace) { str += " "; needSpace = false; } //ss << args.ProcessName; str += args.ProcessName; needSpace = true; } //if (needSpace) { ss << " : "; needSpace = false; } if (needSpace) { str += " : "; needSpace = false; } //ss << message; str += message; // string s = ss.str(); static bool consoleMode = true; #if defined(NL_OS_WINDOWS) static bool consoleModeTest = false; if (!consoleModeTest) { HANDLE handle = CreateFile ("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); consoleMode = handle != INVALID_HANDLE_VALUE; if (consoleMode) CloseHandle (handle); consoleModeTest = true; } #endif // NL_OS_WINDOWS #ifdef NL_OS_UNIX if (colorSet) { str += "\e[0m"; } #endif // Printf ? if (consoleMode) { // we don't use cout because sometimes, it crashs because cout isn't already init, printf doesn t crash. if (!str.empty()) printf ("%s", str.c_str()); if (!args.CallstackAndLog.empty()) printf ("%s", args.CallstackAndLog.c_str()); fflush(stdout); } #ifdef NL_OS_WINDOWS // display the string in the debugger is the application is started with the debugger if (IsDebuggerPresent ()) { //stringstream ss2; string str2; needSpace = false; if (args.FileName != NULL) str2 += args.FileName; if (args.Line != -1) { str2 += "(" + NLMISC::toString(args.Line) + ")"; needSpace = true; } if (needSpace) { str2 += " : "; needSpace = false; } if (args.FuncName != NULL) str2 += string(args.FuncName) + " "; if (args.LogType != CLog::LOG_NO) { str2 += logTypeToString(args.LogType); needSpace = true; } // Write thread identifier if ( args.ThreadId != 0 ) { str2 += NLMISC::toString("%5x: ", args.ThreadId); } str2 += message; const sint maxOutString = 2*1024; if(str2.size() < maxOutString) { ////////////////////////////////////////////////////////////////// // WARNING: READ THIS !!!!!!!!!!!!!!!! /////////////////////////// // If at the release time, it freezes here, it's a microsoft bug: // http://support.microsoft.com/support/kb/articles/q173/2/60.asp OutputDebugStringW(utf8ToWide(str2)); } else { sint count = 0; uint n = (uint)strlen(message); std::string s(&str2.c_str()[0], (str2.size() - n)); OutputDebugStringW(utf8ToWide(s)); for(;;) { if((n - count) < maxOutString ) { s = std::string(&message[count], (n - count)); OutputDebugStringW(utf8ToWide(s)); OutputDebugStringW(L"\n"); break; } else { s = std::string(&message[count] , count + maxOutString); OutputDebugStringW(utf8ToWide(s)); OutputDebugStringW(L"\n\t\t\t"); count += maxOutString; } } } // OutputDebugString is a big shit, we can't display big string in one time, we need to split uint32 pos = 0; string splited; for(;;) { if (pos+1000 < args.CallstackAndLog.size ()) { splited = args.CallstackAndLog.substr (pos, 1000); OutputDebugStringW(utf8ToWide(splited)); pos += 1000; } else { splited = args.CallstackAndLog.substr (pos); OutputDebugStringW(utf8ToWide(splited)); break; } } } #endif } CFileDisplayer::CFileDisplayer (const std::string &filename, bool eraseLastLog, const char *displayerName, bool raw) : IDisplayer (displayerName), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(raw) { _FilePointer = (FILE*)1; setParam (filename, eraseLastLog); } CFileDisplayer::CFileDisplayer () : IDisplayer (""), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(false) { _FilePointer = (FILE*)1; } CFileDisplayer::~CFileDisplayer () { if (_FilePointer > (FILE*)1) { fclose(_FilePointer); _FilePointer = NULL; } } void CFileDisplayer::setParam (const std::string &filename, bool eraseLastLog) { _FileName = filename; if (filename.empty()) { // can't do nlwarning or infinite recurs printf ("CFileDisplayer::setParam(): Can't create file with empty filename\n"); return; } if (eraseLastLog) { /* ofstream ofs (filename.c_str(), ios::out | ios::trunc); if (!ofs.is_open()) { // can't do nlwarning or infinite recurs printf ("CFileDisplayer::setParam(): Can't open and clear the log file '%s'\n", filename.c_str()); }*/ // Erase all the derived log files int i = 0; bool fileExist = true; while (fileExist) { string fileToDelete = CFile::getPath (filename) + CFile::getFilenameWithoutExtension (filename) + toString ("%03d.", i) + CFile::getExtension (filename); fileExist = CFile::isExists (fileToDelete); if (fileExist) CFile::deleteFile (fileToDelete); i++; } } if (_FilePointer > (FILE*)1) { fclose (_FilePointer); _FilePointer = (FILE*)1; } } // Log format: "2000/01/15 12:05:30 : " void CFileDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message ) { bool needSpace = false; //stringstream ss; string str; // if the filename is not set, don't log if (_FileName.empty()) return; if (args.Date != 0 && !_Raw) { str += dateToHumanString(args.Date); needSpace = true; } if (args.LogType != CLog::LOG_NO && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += logTypeToString(args.LogType); needSpace = true; } // Write thread identifier if ( args.ThreadId != 0 && !_Raw) { if (needSpace) { str += " "; needSpace = false; } #ifdef NL_OS_WINDOWS str += NLMISC::toString("%4x", args.ThreadId); #else str += NLMISC::toString("%4u", args.ThreadId); #endif needSpace = true; } if (!args.ProcessName.empty() && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += args.ProcessName; needSpace = true; } if (args.FileName != NULL && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += CFile::getFilename(args.FileName); needSpace = true; } if (args.Line != -1 && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += NLMISC::toString(args.Line); needSpace = true; } if (args.FuncName != NULL && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += args.FuncName; needSpace = true; } if (needSpace) { str += " : "; needSpace = false; } str += message; if (_FilePointer > (FILE*)1) { // if the file is too big (>5mb), rename it and create another one (check only after 20 lines to speed up) if (_LastLogSizeChecked++ > 20) { int res = ftell (_FilePointer); if (res > 5*1024*1024) { fclose (_FilePointer); rename (_FileName.c_str(), CFile::findNewFile (_FileName).c_str()); _FilePointer = (FILE*) 1; _LastLogSizeChecked = 0; } } } if (_FilePointer == (FILE*)1) { _FilePointer = nlfopen (_FileName, "at"); if (_FilePointer == NULL) printf ("Can't open log file '%s': %s\n", _FileName.c_str(), strerror (errno)); } if (_FilePointer != 0) { if (_NeedHeader) { const char *hs = HeaderString(); fwrite (hs, strlen (hs), 1, _FilePointer); _NeedHeader = false; } if(!str.empty()) fwrite (str.c_str(), str.size(), 1, _FilePointer); if(!args.CallstackAndLog.empty()) fwrite (args.CallstackAndLog.c_str(), args.CallstackAndLog.size (), 1, _FilePointer); fflush (_FilePointer); } } // Log format in clipboard: "2000/01/15 12:05:30 : " // Log format on the screen: in debug " : " // in release "" void CMsgBoxDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message) { bool needSpace = false; string str; // create the string for the clipboard if (args.Date != 0) { str += dateToHumanString(args.Date); needSpace = true; } if (args.LogType != CLog::LOG_NO) { if (needSpace) { str += " "; needSpace = false; } str += logTypeToString(args.LogType); needSpace = true; } if (!args.ProcessName.empty()) { if (needSpace) { str += " "; needSpace = false; } str += args.ProcessName; needSpace = true; } if (args.FileName != NULL) { if (needSpace) { str += " "; needSpace = false; } str += CFile::getFilename(args.FileName); needSpace = true; } if (args.Line != -1) { if (needSpace) { str += " "; needSpace = false; } str += NLMISC::toString(args.Line); needSpace = true; } if (args.FuncName != NULL) { if (needSpace) { str += " "; needSpace = false; } str += args.FuncName; needSpace = true; } if (needSpace) { str += ": "; needSpace = false; } str += message; CSystemUtils::copyTextToClipboard(str); // create the string on the screen needSpace = false; string str2; #ifdef NL_DEBUG if (!args.ProcessName.empty()) { if (needSpace) { str2 += " "; needSpace = false; } str2 += args.ProcessName; needSpace = true; } if (args.FileName != NULL) { if (needSpace) { str2 += " "; needSpace = false; } str2 += CFile::getFilename(args.FileName); needSpace = true; } if (args.Line != -1) { if (needSpace) { str2 += " "; needSpace = false; } str2 += NLMISC::toString(args.Line); needSpace = true; } if (args.FuncName != NULL) { if (needSpace) { str2 += " "; needSpace = false; } str2 += args.FuncName; needSpace = true; } if (needSpace) { str2 += ": "; needSpace = false; } #endif // NL_DEBUG str2 += message; str2 += "\n\n(this message was copied in the clipboard)"; /* if (IsDebuggerPresent ()) { // Must break in assert call DebugNeedAssert = true; } else */ { // Display the report string body; body += toString(LogTypeToString[2][args.LogType]) + "\n"; body += "ProcName: " + args.ProcessName + "\n"; body += "Date: " + string(dateToHumanString(args.Date)) + "\n"; if(args.FileName == NULL) body += "File: \n"; else body += "File: " + string(args.FileName) + "\n"; body += "Line: " + toString(args.Line) + "\n"; if (args.FuncName == NULL) body += "FuncName: \n"; else body += "FuncName: " + string(args.FuncName) + "\n"; body += "Reason: " + toString(message); body += args.CallstackAndLog; string subject; // procname is host/service_name-sid we only want the service_name to avoid redondant mail string procname; string::size_type pos = args.ProcessName.find ("/"); if (pos == string::npos) { procname = args.ProcessName; } else { string::size_type pos2 = args.ProcessName.find ("-", pos+1); if (pos2 == string::npos) { procname = args.ProcessName.substr (pos+1); } else { procname = args.ProcessName.substr (pos+1, pos2-pos-1); } } subject += procname + " NeL " + toString(LogTypeToString[0][args.LogType]) + " " + (args.FileName?string(args.FileName):"") + " " + toString(args.Line) + " " + (args.FuncName?string(args.FuncName):""); // Check the envvar NEL_IGNORE_ASSERT if (getenv ("NEL_IGNORE_ASSERT") == NULL) { // yoyo: allow only to send the crash report once. Because users usually click ignore, // which create noise into list of bugs (once a player crash, it will surely continues to do it). std::string filename = getLogDirectory() + NL_CRASH_DUMP_FILE; TReportResult reportResult = report(args.ProcessName + " NeL " + toString(logTypeToString(args.LogType, true)), subject, body, filename, NL_REPORT_SYNCHRONOUS, !isCrashAlreadyReported(), NL_REPORT_DEFAULT); switch (reportResult) { case ReportAlwaysIgnore: IgnoreNextTime = true; break; case ReportBreak: INelContext::getInstance().setDebugNeedAssert(true); break; case ReportAbort: # ifdef NL_OS_WINDOWS # ifndef NL_COMP_MINGW // disable the Windows popup telling that the application aborted and disable the dr watson report. _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); # endif # endif abort(); break; default: break; } // no more sent mail for crash setCrashAlreadyReported(true); } } } } // NLMISC