Linux multi-monitor fullscreen support
[ryzomcore.git] / nel / src / misc / displayer.cpp
blob149bfb79d6041bdc6799c6792ce12d69224f3d78
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdmisc.h"
22 #include "nel/misc/types_nl.h"
24 #ifndef NL_OS_WINDOWS
25 # define IsDebuggerPresent() false
26 #endif
28 #ifdef NL_OS_WINDOWS
29 # include <io.h>
30 # include <fcntl.h>
31 # include <sys/types.h>
32 # include <sys/stat.h>
33 #else
34 # include <cerrno>
35 #endif // NL_OS_WINDOWS
37 #include "nel/misc/path.h"
38 #include "nel/misc/mutex.h"
39 #include "nel/misc/report.h"
40 #include "nel/misc/system_utils.h"
41 #include "nel/misc/variable.h"
43 #include "nel/misc/debug.h"
45 #include "nel/misc/displayer.h"
47 using namespace std;
49 #ifdef DEBUG_NEW
50 #define new DEBUG_NEW
51 #endif
53 namespace NLMISC
56 CVariable<bool> StdDisplayerColor("nel", "StdDisplayerColor", "Enable colors in std displayer", true, 0, true);
58 static const char *LogTypeToString[][8] = {
59 { "", "ERR", "WRN", "INF", "DBG", "STT", "AST", "UKN" },
60 { "", "Error", "Warning", "Information", "Debug", "Statistic", "Assert", "Unknown" },
61 { "", "A fatal error occurs. The program must quit", "", "", "", "", "A failed assertion occurs", "" },
64 const char *IDisplayer::logTypeToString (CLog::TLogType logType, bool longFormat)
66 if (logType < CLog::LOG_NO || logType > CLog::LOG_UNKNOWN)
67 return "<NotDefined>";
69 return LogTypeToString[longFormat?1:0][logType];
72 const char *IDisplayer::dateToHumanString ()
74 time_t date;
75 time (&date);
76 return dateToHumanString (date);
79 const char *IDisplayer::dateToHumanString (time_t date)
81 static char cstime[25];
82 struct tm *tms = localtime(&date);
83 if (tms)
84 strftime (cstime, 25, "%Y/%m/%d %H:%M:%S", tms);
85 else
86 sprintf(cstime, "bad date %d", (uint32)date);
87 return cstime;
90 const char *IDisplayer::dateToComputerString (time_t date)
92 static char cstime[25];
93 smprintf (cstime, 25, "%ld", &date);
94 return cstime;
97 const char *IDisplayer::HeaderString ()
99 static char header[1024];
100 smprintf(header, 1024, "\nLog Starting [%s]\n", dateToHumanString());
101 return header;
105 IDisplayer::IDisplayer(const char *displayerName)
107 _Mutex = new CMutex (string(displayerName)+"DISP");
108 DisplayerName = displayerName;
111 IDisplayer::~IDisplayer()
113 delete _Mutex;
117 * Display the string where it does.
119 void IDisplayer::display ( const CLog::TDisplayInfo& args, const char *message )
121 _Mutex->enter();
124 doDisplay( args, message );
126 catch (const Exception &)
128 // silence
130 _Mutex->leave();
134 // Log format : "<LogType> <ThreadNo> <FileName> <Line> <ProcessName> : <Msg>"
135 void CStdDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message )
137 bool needSpace = false;
138 //stringstream ss;
139 string str;
140 #ifdef NL_OS_UNIX
141 bool colorSet = false;
142 #endif
144 if (args.LogType != CLog::LOG_NO)
146 #ifdef NL_OS_UNIX
147 if (StdDisplayerColor.get())
149 if (args.LogType == CLog::LOG_ERROR || args.LogType == CLog::LOG_ASSERT) { str += "\e[0;30m\e[41m"; colorSet = true; } // black text, red background
150 else if (args.LogType == CLog::LOG_WARNING) { str += "\e[0;91m"; colorSet = true; } // bright red text
151 else if (args.LogType == CLog::LOG_DEBUG) { str += "\e[0;34m"; colorSet = true; } // blue text
153 #endif
154 //ss << logTypeToString(args.LogType);
155 str += logTypeToString(args.LogType);
156 needSpace = true;
159 // Write thread identifier
160 if ( args.ThreadId != 0 )
162 //ss << setw(5) << args.ThreadId;
163 if (needSpace) { str += " "; needSpace = false; }
164 #ifdef NL_OS_WINDOWS
165 str += NLMISC::toString("%5x", args.ThreadId);
166 #else
167 str += NLMISC::toString("%08x", args.ThreadId);
168 #endif
169 needSpace = true;
172 if (args.FileName != NULL)
174 //if (needSpace) { ss << " "; needSpace = false; }
175 if (needSpace) { str += " "; needSpace = false; }
176 //ss << CFile::getFilename(args.FileName);
177 str += CFile::getFilename(args.FileName);
178 needSpace = true;
181 if (args.Line != -1)
183 //if (needSpace) { ss << " "; needSpace = false; }
184 if (needSpace) { str += " "; needSpace = false; }
185 //ss << args.Line;
186 str += NLMISC::toString(args.Line);
187 needSpace = true;
190 if (args.FuncName != NULL)
192 //if (needSpace) { ss << " "; needSpace = false; }
193 if (needSpace) { str += " "; needSpace = false; }
194 //ss << args.FuncName;
195 str += args.FuncName;
196 needSpace = true;
199 if (!args.ProcessName.empty())
201 //if (needSpace) { ss << " "; needSpace = false; }
202 if (needSpace) { str += " "; needSpace = false; }
203 //ss << args.ProcessName;
204 str += args.ProcessName;
205 needSpace = true;
208 //if (needSpace) { ss << " : "; needSpace = false; }
209 if (needSpace) { str += " : "; needSpace = false; }
211 //ss << message;
212 str += message;
214 // string s = ss.str();
216 static bool consoleMode = true;
218 #if defined(NL_OS_WINDOWS)
219 static bool consoleModeTest = false;
220 if (!consoleModeTest)
222 HANDLE handle = CreateFileA ("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
223 consoleMode = handle != INVALID_HANDLE_VALUE;
224 if (consoleMode)
225 CloseHandle (handle);
226 consoleModeTest = true;
228 #endif // NL_OS_WINDOWS
230 #ifdef NL_OS_UNIX
231 if (colorSet)
233 str += "\e[0m";
235 #endif
237 // Printf ?
238 if (consoleMode)
240 // we don't use cout because sometimes, it crashs because cout isn't already init, printf doesn t crash.
241 if (!str.empty())
242 printf ("%s", str.c_str());
244 if (!args.CallstackAndLog.empty())
245 printf ("%s", args.CallstackAndLog.c_str());
247 fflush(stdout);
250 #ifdef NL_OS_WINDOWS
251 // display the string in the debugger is the application is started with the debugger
252 if (IsDebuggerPresent ())
254 //stringstream ss2;
255 string str2;
256 needSpace = false;
258 if (args.FileName != NULL) str2 += args.FileName;
260 if (args.Line != -1)
262 str2 += "(" + NLMISC::toString(args.Line) + ")";
263 needSpace = true;
266 if (needSpace) { str2 += " : "; needSpace = false; }
268 if (args.FuncName != NULL) str2 += string(args.FuncName) + " ";
270 if (args.LogType != CLog::LOG_NO)
272 str2 += logTypeToString(args.LogType);
273 needSpace = true;
276 // Write thread identifier
277 if ( args.ThreadId != 0 )
279 str2 += NLMISC::toString("%5x: ", args.ThreadId);
282 str2 += message;
284 const sint maxOutString = 2*1024;
286 if(str2.size() < maxOutString)
288 //////////////////////////////////////////////////////////////////
289 // WARNING: READ THIS !!!!!!!!!!!!!!!! ///////////////////////////
290 // If at the release time, it freezes here, it's a microsoft bug:
291 // http://support.microsoft.com/support/kb/articles/q173/2/60.asp
292 OutputDebugStringW(nlUtf8ToWide(str2));
294 else
296 sint count = 0;
297 uint n = (uint)strlen(message);
298 std::string s(&str2.c_str()[0], (str2.size() - n));
299 OutputDebugStringW(nlUtf8ToWide(s));
301 for(;;)
304 if((n - count) < maxOutString )
306 s = std::string(&message[count], (n - count));
307 OutputDebugStringW(nlUtf8ToWide(s));
308 OutputDebugStringW(L"\n");
309 break;
311 else
313 s = std::string(&message[count] , count + maxOutString);
314 OutputDebugStringW(nlUtf8ToWide(s));
315 OutputDebugStringW(L"\n\t\t\t");
316 count += maxOutString;
321 // OutputDebugString is a big shit, we can't display big string in one time, we need to split
322 uint32 pos = 0;
323 string splited;
324 for(;;)
326 if (pos+1000 < args.CallstackAndLog.size ())
328 splited = args.CallstackAndLog.substr (pos, 1000);
329 OutputDebugStringW(nlUtf8ToWide(splited));
330 pos += 1000;
332 else
334 splited = args.CallstackAndLog.substr (pos);
335 OutputDebugStringW(nlUtf8ToWide(splited));
336 break;
340 #endif
343 CFileDisplayer::CFileDisplayer (const std::string &filename, bool eraseLastLog, const char *displayerName, bool raw) :
344 IDisplayer (displayerName), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(raw)
346 _FilePointer = (FILE*)1;
347 setParam (filename, eraseLastLog);
350 CFileDisplayer::CFileDisplayer () :
351 IDisplayer (""), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(false)
353 _FilePointer = (FILE*)1;
356 CFileDisplayer::~CFileDisplayer ()
358 if (_FilePointer > (FILE*)1)
360 fclose(_FilePointer);
361 _FilePointer = NULL;
365 void CFileDisplayer::setParam (const std::string &filename, bool eraseLastLog)
367 _FileName = filename;
369 if (filename.empty())
371 // can't do nlwarning or infinite recurs
372 printf ("CFileDisplayer::setParam(): Can't create file with empty filename\n");
373 return;
376 if (eraseLastLog)
378 /* ofstream ofs (filename.c_str(), ios::out | ios::trunc);
379 if (!ofs.is_open())
381 // can't do nlwarning or infinite recurs
382 printf ("CFileDisplayer::setParam(): Can't open and clear the log file '%s'\n", filename.c_str());
385 // Erase all the derived log files
386 int i = 0;
387 bool fileExist = true;
388 while (fileExist)
390 string fileToDelete = CFile::getPath (filename) + CFile::getFilenameWithoutExtension (filename) +
391 toString ("%03d.", i) + CFile::getExtension (filename);
392 fileExist = CFile::isExists (fileToDelete);
393 if (fileExist)
394 CFile::deleteFile (fileToDelete);
395 i++;
399 if (_FilePointer > (FILE*)1)
401 fclose (_FilePointer);
402 _FilePointer = (FILE*)1;
406 // Log format: "2000/01/15 12:05:30 <ProcessName> <LogType> <ThreadId> <FileName> <Line> : <Msg>"
407 void CFileDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message )
409 bool needSpace = false;
410 //stringstream ss;
411 string str;
413 // if the filename is not set, don't log
414 if (_FileName.empty()) return;
416 if (args.Date != 0 && !_Raw)
418 str += dateToHumanString(args.Date);
419 needSpace = true;
422 if (args.LogType != CLog::LOG_NO && !_Raw)
424 if (needSpace) { str += " "; needSpace = false; }
425 str += logTypeToString(args.LogType);
426 needSpace = true;
429 // Write thread identifier
430 if ( args.ThreadId != 0 && !_Raw)
432 if (needSpace) { str += " "; needSpace = false; }
433 #ifdef NL_OS_WINDOWS
434 str += NLMISC::toString("%4x", args.ThreadId);
435 #else
436 str += NLMISC::toString("%4u", args.ThreadId);
437 #endif
438 needSpace = true;
441 if (!args.ProcessName.empty() && !_Raw)
443 if (needSpace) { str += " "; needSpace = false; }
444 str += args.ProcessName;
445 needSpace = true;
448 if (args.FileName != NULL && !_Raw)
450 if (needSpace) { str += " "; needSpace = false; }
451 str += CFile::getFilename(args.FileName);
452 needSpace = true;
455 if (args.Line != -1 && !_Raw)
457 if (needSpace) { str += " "; needSpace = false; }
458 str += NLMISC::toString(args.Line);
459 needSpace = true;
462 if (args.FuncName != NULL && !_Raw)
464 if (needSpace) { str += " "; needSpace = false; }
465 str += args.FuncName;
466 needSpace = true;
469 if (needSpace) { str += " : "; needSpace = false; }
471 str += message;
473 if (_FilePointer > (FILE*)1)
475 // if the file is too big (>5mb), rename it and create another one (check only after 20 lines to speed up)
476 if (_LastLogSizeChecked++ > 20)
478 int res = ftell (_FilePointer);
479 if (res > 5*1024*1024)
481 fclose (_FilePointer);
482 rename (_FileName.c_str(), CFile::findNewFile (_FileName).c_str());
483 _FilePointer = (FILE*) 1;
484 _LastLogSizeChecked = 0;
489 if (_FilePointer == (FILE*)1)
491 _FilePointer = nlfopen (_FileName, "at");
492 if (_FilePointer == NULL)
493 printf ("Can't open log file '%s': %s\n", _FileName.c_str(), strerror (errno));
496 if (_FilePointer != 0)
498 if (_NeedHeader)
500 const char *hs = HeaderString();
502 if (fwrite(hs, strlen(hs), 1, _FilePointer) != 1)
504 printf("Unable to write header: %s\n", hs);
507 _NeedHeader = false;
510 if (!str.empty())
512 if (fwrite(str.c_str(), str.size(), 1, _FilePointer) != 1)
514 printf("Unable to write string: %s\n", str.c_str());
518 if (!args.CallstackAndLog.empty())
520 if (fwrite(args.CallstackAndLog.c_str(), args.CallstackAndLog.size(), 1, _FilePointer) != 1)
522 printf("Unable to write call stack: %s\n", args.CallstackAndLog.c_str());
526 fflush (_FilePointer);
530 // Log format in clipboard: "2000/01/15 12:05:30 <LogType> <ProcessName> <FileName> <Line>: <Msg>"
531 // Log format on the screen: in debug "<ProcessName> <FileName> <Line>: <Msg>"
532 // in release "<Msg>"
533 void CMsgBoxDisplayer::doDisplay ( const CLog::TDisplayInfo& args, const char *message)
535 bool needSpace = false;
536 string str;
538 // create the string for the clipboard
540 if (args.Date != 0)
542 str += dateToHumanString(args.Date);
543 needSpace = true;
546 if (args.LogType != CLog::LOG_NO)
548 if (needSpace) { str += " "; needSpace = false; }
549 str += logTypeToString(args.LogType);
550 needSpace = true;
553 if (!args.ProcessName.empty())
555 if (needSpace) { str += " "; needSpace = false; }
556 str += args.ProcessName;
557 needSpace = true;
560 if (args.FileName != NULL)
562 if (needSpace) { str += " "; needSpace = false; }
563 str += CFile::getFilename(args.FileName);
564 needSpace = true;
567 if (args.Line != -1)
569 if (needSpace) { str += " "; needSpace = false; }
570 str += NLMISC::toString(args.Line);
571 needSpace = true;
574 if (args.FuncName != NULL)
576 if (needSpace) { str += " "; needSpace = false; }
577 str += args.FuncName;
578 needSpace = true;
581 if (needSpace) { str += ": "; needSpace = false; }
583 str += message;
585 CSystemUtils::copyTextToClipboard(str);
587 // create the string on the screen
588 needSpace = false;
589 string str2;
591 #ifdef NL_DEBUG
592 if (!args.ProcessName.empty())
594 if (needSpace) { str2 += " "; needSpace = false; }
595 str2 += args.ProcessName;
596 needSpace = true;
599 if (args.FileName != NULL)
601 if (needSpace) { str2 += " "; needSpace = false; }
602 str2 += CFile::getFilename(args.FileName);
603 needSpace = true;
606 if (args.Line != -1)
608 if (needSpace) { str2 += " "; needSpace = false; }
609 str2 += NLMISC::toString(args.Line);
610 needSpace = true;
613 if (args.FuncName != NULL)
615 if (needSpace) { str2 += " "; needSpace = false; }
616 str2 += args.FuncName;
617 needSpace = true;
620 if (needSpace) { str2 += ": "; needSpace = false; }
622 #endif // NL_DEBUG
624 str2 += message;
625 str2 += "\n\n(this message was copied in the clipboard)";
627 /* if (IsDebuggerPresent ())
629 // Must break in assert call
630 DebugNeedAssert = true;
632 else
633 */ {
635 // Display the report
637 string body;
639 body += toString(LogTypeToString[2][args.LogType]) + "\n";
640 body += "ProcName: " + args.ProcessName + "\n";
641 body += "Date: " + string(dateToHumanString(args.Date)) + "\n";
642 if(args.FileName == NULL)
643 body += "File: <Unknown>\n";
644 else
645 body += "File: " + string(args.FileName) + "\n";
646 body += "Line: " + toString(args.Line) + "\n";
647 if (args.FuncName == NULL)
648 body += "FuncName: <Unknown>\n";
649 else
650 body += "FuncName: " + string(args.FuncName) + "\n";
651 body += "Reason: " + toString(message);
653 body += args.CallstackAndLog;
655 string subject;
657 // procname is host/service_name-sid we only want the service_name to avoid redondant mail
658 string procname;
659 string::size_type pos = args.ProcessName.find ("/");
660 if (pos == string::npos)
662 procname = args.ProcessName;
664 else
666 string::size_type pos2 = args.ProcessName.find ("-", pos+1);
667 if (pos2 == string::npos)
669 procname = args.ProcessName.substr (pos+1);
671 else
673 procname = args.ProcessName.substr (pos+1, pos2-pos-1);
677 subject += procname + " NeL " + toString(LogTypeToString[0][args.LogType]) + " " + (args.FileName?string(args.FileName):"") + " " + toString(args.Line) + " " + (args.FuncName?string(args.FuncName):"");
679 // Check the envvar NEL_IGNORE_ASSERT
680 if (getenv ("NEL_IGNORE_ASSERT") == NULL)
682 // yoyo: allow only to send the crash report once. Because users usually click ignore,
683 // which create noise into list of bugs (once a player crash, it will surely continues to do it).
684 std::string filename = getLogDirectory() + NL_CRASH_DUMP_FILE;
686 TReportResult reportResult = report(args.ProcessName + " NeL " + toString(logTypeToString(args.LogType, true)),
687 subject, body, filename, NL_REPORT_SYNCHRONOUS, !isCrashAlreadyReported(), NL_REPORT_DEFAULT);
689 switch (reportResult)
691 case ReportAlwaysIgnore:
692 IgnoreNextTime = true;
693 break;
694 case ReportBreak:
695 INelContext::getInstance().setDebugNeedAssert(true);
696 break;
697 case ReportAbort:
698 # ifdef NL_OS_WINDOWS
699 # ifndef NL_COMP_MINGW
700 // disable the Windows popup telling that the application aborted and disable the dr watson report.
701 _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
702 # endif
703 # endif
704 abort();
705 break;
707 default:
708 break;
711 // no more sent mail for crash
712 setCrashAlreadyReported(true);
717 } // NLMISC