Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CLogger.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "CLogger.h"
21 #include "CConsole.h"
22 #include "graphics/ShaderManager.h"
23 #include "graphics/TextRenderer.h"
24 #include "lib/ogl.h"
25 #include "lib/timer.h"
26 #include "lib/utf8.h"
27 #include "lib/sysdep/sysdep.h"
28 #include "ps/Font.h"
29 #include "ps/Profile.h"
30 #include "renderer/Renderer.h"
31 
32 #include <ctime>
33 #include <iostream>
34 
35 // Disable "'boost::algorithm::detail::is_classifiedF' : assignment operator could not be generated"
36 // and "find_format_store.hpp(74) : warning C4100: 'Input' : unreferenced formal parameter"
37 #if MSC_VERSION
38 #pragma warning(disable:4512)
39 #pragma warning(disable:4100)
40 #endif
41 
42 #include <boost/algorithm/string/replace.hpp>
43 
44 static const double RENDER_TIMEOUT = 10.0; // seconds before messages are deleted
45 static const double RENDER_TIMEOUT_RATE = 10.0; // number of timed-out messages deleted per second
46 static const size_t RENDER_LIMIT = 20; // maximum messages on screen at once
47 
48 static const size_t BUFFER_SIZE = 1024;
49 
50 // Set up a default logger that throws everything away, because that's
51 // better than crashing. (This is particularly useful for unit tests which
52 // don't care about any log output.)
53 struct BlackHoleStreamBuf : public std::streambuf
54 {
57 CLogger nullLogger(&blackHoleStream, &blackHoleStream, false, true);
58 
59 CLogger* g_Logger = &nullLogger;
60 
61 const char* html_header0 =
62  "<!DOCTYPE html>\n"
63  "<meta charset=\"utf-8\">\n"
64  "<title>Pyrogenesis Log</title>\n"
65  "<style>"
66  "body { background: #eee; color: black; font-family: sans-serif; } "
67  "p { background: white; margin: 3px 0 3px 0; } "
68  ".error { color: red; } "
69  ".warning { color: blue; }"
70  "</style>\n"
71  "<h2>0 A.D. ";
72 
73 const char* html_header1 = "</h2>\n";
74 
76 {
77  OsPath mainlogPath(psLogDir()/"mainlog.html");
78  m_MainLog = new std::ofstream(OsString(mainlogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
79 
80  OsPath interestinglogPath(psLogDir()/"interestinglog.html");
81  m_InterestingLog = new std::ofstream(OsString(interestinglogPath).c_str(), std::ofstream::out | std::ofstream::trunc);
82 
83  m_OwnsStreams = true;
84  m_UseDebugPrintf = true;
85 
86  Init();
87 }
88 
89 CLogger::CLogger(std::ostream* mainLog, std::ostream* interestingLog, bool takeOwnership, bool useDebugPrintf)
90 {
91  m_MainLog = mainLog;
92  m_InterestingLog = interestingLog;
93  m_OwnsStreams = takeOwnership;
94  m_UseDebugPrintf = useDebugPrintf;
95 
96  Init();
97 }
98 
100 {
101  m_RenderLastEraseTime = -1.0;
102  // this is called too early to allow us to call timer_Time(),
103  // so we'll fill in the initial value later
104 
105  m_NumberOfMessages = 0;
106  m_NumberOfErrors = 0;
107  m_NumberOfWarnings = 0;
108 
109  //Write Headers for the HTML documents
110  *m_MainLog << html_header0 << "Main log" << html_header1;
111 
112  //Write Headers for the HTML documents
113  *m_InterestingLog << html_header0 << "Main log (warnings and errors only)" << html_header1;
114 }
115 
117 {
118  char buffer[128];
119  sprintf_s(buffer, ARRAY_SIZE(buffer), " with %d message(s), %d error(s) and %d warning(s).", m_NumberOfMessages,m_NumberOfErrors,m_NumberOfWarnings);
120 
121  time_t t = time(NULL);
122  struct tm* now = localtime(&t);
123  char currentDate[17];
124  sprintf_s(currentDate, ARRAY_SIZE(currentDate), "%04d-%02d-%02d", 1900+now->tm_year, 1+now->tm_mon, now->tm_mday);
125  char currentTime[10];
126  sprintf_s(currentTime, ARRAY_SIZE(currentTime), "%02d:%02d:%02d", now->tm_hour, now->tm_min, now->tm_sec);
127 
128  //Write closing text
129 
130  *m_MainLog << "<p>Engine exited successfully on " << currentDate;
131  *m_MainLog << " at " << currentTime << buffer << "</p>\n";
132 
133  *m_InterestingLog << "<p>Engine exited successfully on " << currentDate;
134  *m_InterestingLog << " at " << currentTime << buffer << "</p>\n";
135 
136  if (m_OwnsStreams)
137  {
140  }
141 }
142 
143 static std::string ToHTML(const wchar_t* message)
144 {
145  Status err;
146  std::string cmessage = utf8_from_wstring(message, &err);
147  boost::algorithm::replace_all(cmessage, "&", "&amp;");
148  boost::algorithm::replace_all(cmessage, "<", "&lt;");
149  return cmessage;
150 }
151 
152 void CLogger::WriteMessage(const wchar_t* message, bool doRender = false)
153 {
154  std::string cmessage = ToHTML(message);
155 
156  CScopeLock lock(m_Mutex);
157 
159 // if (m_UseDebugPrintf)
160 // debug_printf(L"MESSAGE: %ls\n", message);
161 
162  *m_MainLog << "<p>" << cmessage << "</p>\n";
163  m_MainLog->flush();
164 
165  if (doRender)
166  {
167  if (g_Console) g_Console->InsertMessage(L"INFO: %ls", message);
168 
169  PushRenderMessage(Normal, message);
170  }
171 }
172 
173 void CLogger::WriteError(const wchar_t* message)
174 {
175  std::string cmessage = ToHTML(message);
176 
177  CScopeLock lock(m_Mutex);
178 
180  if (m_UseDebugPrintf)
181  debug_printf(L"ERROR: %ls\n", message);
182 
183  if (g_Console) g_Console->InsertMessage(L"ERROR: %ls", message);
184  *m_InterestingLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
185  m_InterestingLog->flush();
186 
187  *m_MainLog << "<p class=\"error\">ERROR: " << cmessage << "</p>\n";
188  m_MainLog->flush();
189 
190  PushRenderMessage(Error, message);
191 }
192 
193 void CLogger::WriteWarning(const wchar_t* message)
194 {
195  std::string cmessage = ToHTML(message);
196 
197  CScopeLock lock(m_Mutex);
198 
200  if (m_UseDebugPrintf)
201  debug_printf(L"WARNING: %ls\n", message);
202 
203  if (g_Console) g_Console->InsertMessage(L"WARNING: %ls", message);
204  *m_InterestingLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
205  m_InterestingLog->flush();
206 
207  *m_MainLog << "<p class=\"warning\">WARNING: " << cmessage << "</p>\n";
208  m_MainLog->flush();
209 
210  PushRenderMessage(Warning, message);
211 }
212 
213 void CLogger::LogMessage(const wchar_t* fmt, ...)
214 {
215  va_list argp;
216  wchar_t buffer[BUFFER_SIZE] = {0};
217 
218  va_start(argp, fmt);
219  if (sys_vswprintf(buffer, ARRAY_SIZE(buffer), fmt, argp) == -1)
220  {
221  // Buffer too small - ensure the string is nicely terminated
222  wcscpy_s(buffer+ARRAY_SIZE(buffer)-4, 4, L"...");
223  }
224  va_end(argp);
225 
226  WriteMessage(buffer);
227 }
228 
229 void CLogger::LogMessageRender(const wchar_t* fmt, ...)
230 {
231  va_list argp;
232  wchar_t buffer[BUFFER_SIZE] = {0};
233 
234  va_start(argp, fmt);
235  if (sys_vswprintf(buffer, ARRAY_SIZE(buffer), fmt, argp) == -1)
236  {
237  // Buffer too small - ensure the string is nicely terminated
238  wcscpy_s(buffer+ARRAY_SIZE(buffer)-4, 4, L"...");
239  }
240  va_end(argp);
241 
242  WriteMessage(buffer, true);
243 }
244 
245 void CLogger::LogWarning(const wchar_t* fmt, ...)
246 {
247  va_list argp;
248  wchar_t buffer[BUFFER_SIZE] = {0};
249 
250  va_start(argp, fmt);
251  if (sys_vswprintf(buffer, ARRAY_SIZE(buffer), fmt, argp) == -1)
252  {
253  // Buffer too small - ensure the string is nicely terminated
254  wcscpy_s(buffer+ARRAY_SIZE(buffer)-4, 4, L"...");
255  }
256  va_end(argp);
257 
258  WriteWarning(buffer);
259 }
260 
261 void CLogger::LogError(const wchar_t* fmt, ...)
262 {
263  va_list argp;
264  wchar_t buffer[BUFFER_SIZE] = {0};
265 
266  va_start(argp, fmt);
267  if (sys_vswprintf(buffer, ARRAY_SIZE(buffer), fmt, argp) == -1)
268  {
269  // Buffer too small - ensure the string is nicely terminated
270  wcscpy_s(buffer+ARRAY_SIZE(buffer)-4, 4, L"...");
271  }
272  va_end(argp);
273 
274  WriteError(buffer);
275 }
276 
278 {
279  PROFILE3_GPU("logger");
280 
282 
283  CStrW font_name(L"mono-stroke-10");
284  CFont font(font_name);
285  int lineSpacing = font.GetLineSpacing();
286 
287  CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
288  textTech->BeginPass();
289 
290  CTextRenderer textRenderer(textTech->GetShader());
291  textRenderer.Font(font_name);
292  textRenderer.Color(1.0f, 1.0f, 1.0f);
293 
294  // Offset by an extra 35px vertically to avoid the top bar.
295  textRenderer.Translate(4.0f, 35.0f + lineSpacing, 0.0f);
296 
297  // (Lock must come after loading the CFont, since that might log error messages
298  // and attempt to lock the mutex recursively which is forbidden)
299  CScopeLock lock(m_Mutex);
300 
301  for (std::deque<RenderedMessage>::iterator it = m_RenderMessages.begin(); it != m_RenderMessages.end(); ++it)
302  {
303  const wchar_t* type;
304  if (it->method == Normal)
305  {
306  type = L"info";
307  textRenderer.Color(0.0f, 0.8f, 0.0f);
308  }
309  else if (it->method == Warning)
310  {
311  type = L"warning";
312  textRenderer.Color(1.0f, 1.0f, 0.0f);
313  }
314  else
315  {
316  type = L"error";
317  textRenderer.Color(1.0f, 0.0f, 0.0f);
318  }
319 
320  CMatrix3D savedTransform = textRenderer.GetTransform();
321 
322  textRenderer.PrintfAdvance(L"[%8.3f] %ls: ", it->time, type);
323  // Display the actual message in white so it's more readable
324  textRenderer.Color(1.0f, 1.0f, 1.0f);
325  textRenderer.Put(0.0f, 0.0f, it->message.c_str());
326 
327  textRenderer.SetTransform(savedTransform);
328 
329  textRenderer.Translate(0.0f, (float)lineSpacing, 0.0f);
330  }
331 
332  textRenderer.Render();
333 
334  textTech->EndPass();
335 }
336 
337 void CLogger::PushRenderMessage(ELogMethod method, const wchar_t* message)
338 {
339  double now = timer_Time();
340 
341  // Add each message line separately
342  const wchar_t* pos = message;
343  const wchar_t* eol;
344  while ((eol = wcschr(pos, L'\n')) != NULL)
345  {
346  if (eol != pos)
347  {
348  RenderedMessage r = { method, now, std::wstring(pos, eol) };
349  m_RenderMessages.push_back(r);
350  }
351  pos = eol + 1;
352  }
353  // Add the last line, if we didn't end on a \n
354  if (*pos != L'\0')
355  {
356  RenderedMessage r = { method, now, std::wstring(pos) };
357  m_RenderMessages.push_back(r);
358  }
359 }
360 
362 {
363  CScopeLock lock(m_Mutex);
364 
365  if (m_RenderMessages.empty())
366  return;
367 
368  double now = timer_Time();
369 
370  // Initialise the timer on the first call (since we can't do it in the ctor)
371  if (m_RenderLastEraseTime == -1.0)
372  m_RenderLastEraseTime = now;
373 
374  // Delete old messages, approximately at the given rate limit (and at most one per frame)
376  {
377  if (m_RenderMessages[0].time + RENDER_TIMEOUT < now)
378  {
379  m_RenderMessages.pop_front();
380  m_RenderLastEraseTime = now;
381  }
382  }
383 
384  // If there's still too many then delete the oldest
385  if (m_RenderMessages.size() > RENDER_LIMIT)
387 }
388 
390 {
392  g_Logger = new CLogger(&m_Stream, &blackHoleStream, false, false);
393 }
394 
396 {
397  delete g_Logger;
398  g_Logger = m_OldLogger;
399 }
400 
401 std::wstring TestLogger::GetOutput()
402 {
403  Status err;
404  return wstring_from_utf8(m_Stream.str(), &err);
405 }
406 
408 {
410  g_Logger = new CLogger(&std::cout, &blackHoleStream, false, false);
411 }
412 
414 {
415  delete g_Logger;
416  g_Logger = m_OldLogger;
417 }
int GetLineSpacing()
Definition: Font.cpp:55
void Translate(float x, float y, float z)
Definition: Matrix3D.cpp:172
Error/warning/message logging class.
Definition: CLogger.h:45
static const size_t RENDER_LIMIT
Definition: CLogger.cpp:46
CMutex m_Mutex
Definition: CLogger.h:113
void PushRenderMessage(ELogMethod method, const wchar_t *message)
Definition: CLogger.cpp:337
void LogMessage(const wchar_t *fmt,...) WPRINTF_ARGS(2)
Definition: CLogger.cpp:213
const OsPath & psLogDir()
Definition: Pyrogenesis.cpp:96
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:208
void CleanupRenderQueue()
Definition: CLogger.cpp:361
void Init()
Definition: CLogger.cpp:99
ELogMethod
Definition: CLogger.h:49
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
static LogFn g_Logger
Definition: DLL.cpp:33
static const double RENDER_TIMEOUT
Definition: CLogger.cpp:44
Locks a CMutex over this object&#39;s lifetime.
Definition: ThreadUtil.h:73
static const size_t BUFFER_SIZE
Definition: CLogger.cpp:48
const char * html_header1
Definition: CLogger.cpp:73
shared_ptr< CShaderTechnique > CShaderTechniquePtr
void LogError(const wchar_t *fmt,...) WPRINTF_ARGS(2)
Definition: CLogger.cpp:261
BlackHoleStreamBuf blackHoleStreamBuf
Definition: CLogger.cpp:56
std::deque< RenderedMessage > m_RenderMessages
Definition: CLogger.h:109
void Render()
Definition: CLogger.cpp:277
int sprintf_s(char *buf, size_t max_chars, const char *fmt,...) PRINTF_ARGS(3)
#define ARRAY_SIZE(name)
int wcscpy_s(wchar_t *dst, size_t max_dst_chars, const wchar_t *src)
bool m_UseDebugPrintf
Definition: CLogger.h:95
#define g_Renderer
Definition: Renderer.h:61
static std::string ToHTML(const wchar_t *message)
Definition: CLogger.cpp:143
CConsole * g_Console
Definition: CConsole.cpp:46
double m_RenderLastEraseTime
Definition: CLogger.h:110
Definition: Font.h:28
Definition: path.h:75
CLogger nullLogger & blackHoleStream
Definition: CLogger.cpp:57
std::stringstream m_Stream
Definition: CLogger.h:129
#define SAFE_DELETE(p)
delete memory ensuing from new and set the pointer to zero (thus making double-frees safe / a no-op) ...
void WriteError(const wchar_t *message)
Definition: CLogger.cpp:173
i64 Status
Error handling system.
Definition: status.h:171
double timer_Time()
Definition: timer.cpp:98
std::ostream * m_InterestingLog
Definition: CLogger.h:90
void WriteWarning(const wchar_t *message)
Definition: CLogger.cpp:193
int m_NumberOfMessages
Definition: CLogger.h:98
std::wstring wstring_from_utf8(const std::string &src, Status *err)
convert UTF-8 to a wide string (UTF-16 or UCS-4, depending on the platform&#39;s wchar_t).
Definition: utf8.cpp:225
int m_NumberOfErrors
Definition: CLogger.h:99
~CLogger()
Definition: CLogger.cpp:116
CLogger * m_OldLogger
Definition: CLogger.h:128
void LogMessageRender(const wchar_t *fmt,...) WPRINTF_ARGS(2)
Definition: CLogger.cpp:229
bool m_OwnsStreams
Definition: CLogger.h:91
void Font(const CStrW &font)
Set the font for subsequent print calls.
CLogger * m_OldLogger
Definition: CLogger.h:142
std::ostream * m_MainLog
Definition: CLogger.h:89
int sys_vswprintf(wchar_t *buffer, size_t count, const wchar_t *format, va_list argptr)
sys_vswprintf: doesn&#39;t quite follow the standard for vswprintf, but works better across compilers: ...
Definition: printf.cpp:30
void WriteMessage(const wchar_t *message, bool doRender)
Definition: CLogger.cpp:152
static const double RENDER_TIMEOUT_RATE
Definition: CLogger.cpp:45
#define PROFILE3_GPU(name)
Definition: Profile.h:204
const char * html_header0
Definition: CLogger.cpp:61
std::wstring GetOutput()
Definition: CLogger.cpp:401
int m_NumberOfWarnings
Definition: CLogger.h:100
void InsertMessage(const wchar_t *szMessage,...) WPRINTF_ARGS(2)
Definition: CConsole.cpp:508
void LogWarning(const wchar_t *fmt,...) WPRINTF_ARGS(2)
Definition: CLogger.cpp:245
CLogger()
Definition: CLogger.cpp:75
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
static std::string OsString(const OsPath &path)
Definition: os_path.h:42