1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
22 #include "nel/misc/types_nl.h"
25 # define IsDebuggerPresent() false
31 # include <sys/types.h>
32 # include <sys/stat.h>
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"
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 ()
76 return dateToHumanString (date
);
79 const char *IDisplayer::dateToHumanString (time_t date
)
81 static char cstime
[25];
82 struct tm
*tms
= localtime(&date
);
84 strftime (cstime
, 25, "%Y/%m/%d %H:%M:%S", tms
);
86 sprintf(cstime
, "bad date %d", (uint32
)date
);
90 const char *IDisplayer::dateToComputerString (time_t date
)
92 static char cstime
[25];
93 smprintf (cstime
, 25, "%ld", &date
);
97 const char *IDisplayer::HeaderString ()
99 static char header
[1024];
100 smprintf(header
, 1024, "\nLog Starting [%s]\n", dateToHumanString());
105 IDisplayer::IDisplayer(const char *displayerName
)
107 _Mutex
= new CMutex (string(displayerName
)+"DISP");
108 DisplayerName
= displayerName
;
111 IDisplayer::~IDisplayer()
117 * Display the string where it does.
119 void IDisplayer::display ( const CLog::TDisplayInfo
& args
, const char *message
)
124 doDisplay( args
, message
);
126 catch (const Exception
&)
134 // Log format : "<LogType> <ThreadNo> <FileName> <Line> <ProcessName> : <Msg>"
135 void CStdDisplayer::doDisplay ( const CLog::TDisplayInfo
& args
, const char *message
)
137 bool needSpace
= false;
141 bool colorSet
= false;
144 if (args
.LogType
!= CLog::LOG_NO
)
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
154 //ss << logTypeToString(args.LogType);
155 str
+= logTypeToString(args
.LogType
);
159 // Write thread identifier
160 if ( args
.ThreadId
!= 0 )
162 //ss << setw(5) << args.ThreadId;
163 if (needSpace
) { str
+= " "; needSpace
= false; }
165 str
+= NLMISC::toString("%5x", args
.ThreadId
);
167 str
+= NLMISC::toString("%08x", args
.ThreadId
);
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
);
183 //if (needSpace) { ss << " "; needSpace = false; }
184 if (needSpace
) { str
+= " "; needSpace
= false; }
186 str
+= NLMISC::toString(args
.Line
);
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
;
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
;
208 //if (needSpace) { ss << " : "; needSpace = false; }
209 if (needSpace
) { str
+= " : "; needSpace
= false; }
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
;
225 CloseHandle (handle
);
226 consoleModeTest
= true;
228 #endif // NL_OS_WINDOWS
240 // we don't use cout because sometimes, it crashs because cout isn't already init, printf doesn t crash.
242 printf ("%s", str
.c_str());
244 if (!args
.CallstackAndLog
.empty())
245 printf ("%s", args
.CallstackAndLog
.c_str());
251 // display the string in the debugger is the application is started with the debugger
252 if (IsDebuggerPresent ())
258 if (args
.FileName
!= NULL
) str2
+= args
.FileName
;
262 str2
+= "(" + NLMISC::toString(args
.Line
) + ")";
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
);
276 // Write thread identifier
277 if ( args
.ThreadId
!= 0 )
279 str2
+= NLMISC::toString("%5x: ", args
.ThreadId
);
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
));
297 uint n
= (uint
)strlen(message
);
298 std::string
s(&str2
.c_str()[0], (str2
.size() - n
));
299 OutputDebugStringW(nlUtf8ToWide(s
));
304 if((n
- count
) < maxOutString
)
306 s
= std::string(&message
[count
], (n
- count
));
307 OutputDebugStringW(nlUtf8ToWide(s
));
308 OutputDebugStringW(L
"\n");
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
326 if (pos
+1000 < args
.CallstackAndLog
.size ())
328 splited
= args
.CallstackAndLog
.substr (pos
, 1000);
329 OutputDebugStringW(nlUtf8ToWide(splited
));
334 splited
= args
.CallstackAndLog
.substr (pos
);
335 OutputDebugStringW(nlUtf8ToWide(splited
));
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
);
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");
378 /* ofstream ofs (filename.c_str(), ios::out | ios::trunc);
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
387 bool fileExist
= true;
390 string fileToDelete
= CFile::getPath (filename
) + CFile::getFilenameWithoutExtension (filename
) +
391 toString ("%03d.", i
) + CFile::getExtension (filename
);
392 fileExist
= CFile::isExists (fileToDelete
);
394 CFile::deleteFile (fileToDelete
);
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;
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
);
422 if (args
.LogType
!= CLog::LOG_NO
&& !_Raw
)
424 if (needSpace
) { str
+= " "; needSpace
= false; }
425 str
+= logTypeToString(args
.LogType
);
429 // Write thread identifier
430 if ( args
.ThreadId
!= 0 && !_Raw
)
432 if (needSpace
) { str
+= " "; needSpace
= false; }
434 str
+= NLMISC::toString("%4x", args
.ThreadId
);
436 str
+= NLMISC::toString("%4u", args
.ThreadId
);
441 if (!args
.ProcessName
.empty() && !_Raw
)
443 if (needSpace
) { str
+= " "; needSpace
= false; }
444 str
+= args
.ProcessName
;
448 if (args
.FileName
!= NULL
&& !_Raw
)
450 if (needSpace
) { str
+= " "; needSpace
= false; }
451 str
+= CFile::getFilename(args
.FileName
);
455 if (args
.Line
!= -1 && !_Raw
)
457 if (needSpace
) { str
+= " "; needSpace
= false; }
458 str
+= NLMISC::toString(args
.Line
);
462 if (args
.FuncName
!= NULL
&& !_Raw
)
464 if (needSpace
) { str
+= " "; needSpace
= false; }
465 str
+= args
.FuncName
;
469 if (needSpace
) { str
+= " : "; needSpace
= false; }
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)
500 const char *hs
= HeaderString();
502 if (fwrite(hs
, strlen(hs
), 1, _FilePointer
) != 1)
504 printf("Unable to write header: %s\n", hs
);
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;
538 // create the string for the clipboard
542 str
+= dateToHumanString(args
.Date
);
546 if (args
.LogType
!= CLog::LOG_NO
)
548 if (needSpace
) { str
+= " "; needSpace
= false; }
549 str
+= logTypeToString(args
.LogType
);
553 if (!args
.ProcessName
.empty())
555 if (needSpace
) { str
+= " "; needSpace
= false; }
556 str
+= args
.ProcessName
;
560 if (args
.FileName
!= NULL
)
562 if (needSpace
) { str
+= " "; needSpace
= false; }
563 str
+= CFile::getFilename(args
.FileName
);
569 if (needSpace
) { str
+= " "; needSpace
= false; }
570 str
+= NLMISC::toString(args
.Line
);
574 if (args
.FuncName
!= NULL
)
576 if (needSpace
) { str
+= " "; needSpace
= false; }
577 str
+= args
.FuncName
;
581 if (needSpace
) { str
+= ": "; needSpace
= false; }
585 CSystemUtils::copyTextToClipboard(str
);
587 // create the string on the screen
592 if (!args
.ProcessName
.empty())
594 if (needSpace
) { str2
+= " "; needSpace
= false; }
595 str2
+= args
.ProcessName
;
599 if (args
.FileName
!= NULL
)
601 if (needSpace
) { str2
+= " "; needSpace
= false; }
602 str2
+= CFile::getFilename(args
.FileName
);
608 if (needSpace
) { str2
+= " "; needSpace
= false; }
609 str2
+= NLMISC::toString(args
.Line
);
613 if (args
.FuncName
!= NULL
)
615 if (needSpace
) { str2
+= " "; needSpace
= false; }
616 str2
+= args
.FuncName
;
620 if (needSpace
) { str2
+= ": "; needSpace
= false; }
625 str2
+= "\n\n(this message was copied in the clipboard)";
627 /* if (IsDebuggerPresent ())
629 // Must break in assert call
630 DebugNeedAssert = true;
635 // Display the report
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";
645 body
+= "File: " + string(args
.FileName
) + "\n";
646 body
+= "Line: " + toString(args
.Line
) + "\n";
647 if (args
.FuncName
== NULL
)
648 body
+= "FuncName: <Unknown>\n";
650 body
+= "FuncName: " + string(args
.FuncName
) + "\n";
651 body
+= "Reason: " + toString(message
);
653 body
+= args
.CallstackAndLog
;
657 // procname is host/service_name-sid we only want the service_name to avoid redondant mail
659 string::size_type pos
= args
.ProcessName
.find ("/");
660 if (pos
== string::npos
)
662 procname
= args
.ProcessName
;
666 string::size_type pos2
= args
.ProcessName
.find ("-", pos
+1);
667 if (pos2
== string::npos
)
669 procname
= args
.ProcessName
.substr (pos
+1);
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;
695 INelContext::getInstance().setDebugNeedAssert(true);
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
);
711 // no more sent mail for crash
712 setCrashAlreadyReported(true);