1 // Stream based logging mechanism.
3 // Written by Bernie Bright, 1998
5 // Copyright (C) 1998 Bernie Bright - bbright@c031.aone.net.au
7 // This library is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU Library General Public
9 // License as published by the Free Software Foundation; either
10 // version 2 of the License, or (at your option) any later version.
12 // This library 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 GNU
15 // Library General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include <simgear_config.h>
25 #include "logstream.hxx"
32 #include <boost/foreach.hpp>
34 #include <simgear/sg_inlines.h>
35 #include <simgear/threads/SGThread.hxx>
36 #include <simgear/threads/SGQueue.hxx>
37 #include <simgear/threads/SGGuard.hxx>
39 #include <simgear/misc/sgstream.hxx>
40 #include <simgear/misc/sg_path.hxx>
42 #if defined (SG_WINDOWS)
43 // for AllocConsole, OutputDebugString
47 const char* debugClassToString(sgDebugClass c)
50 case SG_NONE: return "none";
51 case SG_TERRAIN: return "terrain";
52 case SG_ASTRO: return "astro";
53 case SG_FLIGHT: return "flight";
54 case SG_INPUT: return "input";
55 case SG_GL: return "opengl";
56 case SG_VIEW: return "view";
57 case SG_COCKPIT: return "cockpit";
58 case SG_GENERAL: return "general";
59 case SG_MATH: return "math";
60 case SG_EVENT: return "event";
61 case SG_AIRCRAFT: return "aircraft";
62 case SG_AUTOPILOT: return "autopilot";
63 case SG_IO: return "io";
64 case SG_CLIPPER: return "clipper";
65 case SG_NETWORK: return "network";
66 case SG_ATC: return "atc";
67 case SG_NASAL: return "nasal";
68 case SG_INSTR: return "instruments";
69 case SG_SYSTEMS: return "systems";
70 case SG_AI: return "ai";
71 case SG_ENVIRONMENT:return "environment";
72 case SG_SOUND: return "sound";
73 case SG_NAVAID: return "navaid";
74 case SG_GUI: return "gui";
75 case SG_TERRASYNC: return "terrasync";
76 case SG_PARTICLES: return "particles";
77 default: return "unknown";
81 //////////////////////////////////////////////////////////////////////////////
86 LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
92 bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
94 return ((c & m_class) != 0 && p >= m_priority);
97 void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
103 } // of namespace simgear
105 //////////////////////////////////////////////////////////////////////////////
107 class FileLogCallback : public simgear::LogCallback
110 FileLogCallback(const SGPath& aPath, sgDebugClass c, sgDebugPriority p) :
111 simgear::LogCallback(c, p)
113 m_file.open(aPath, std::ios_base::out | std::ios_base::trunc);
116 virtual void operator()(sgDebugClass c, sgDebugPriority p,
117 const char* file, int line, const std::string& message)
119 if (!shouldLog(c, p)) return;
120 m_file << debugClassToString(c) << ":" << (int) p
121 << ":" << file << ":" << line << ":" << message << std::endl;
127 class StderrLogCallback : public simgear::LogCallback
130 StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
131 simgear::LogCallback(c, p)
135 #if defined (SG_WINDOWS)
142 virtual void operator()(sgDebugClass c, sgDebugPriority p,
143 const char* file, int line, const std::string& aMessage)
145 if (!shouldLog(c, p)) return;
147 fprintf(stderr, "%s\n", aMessage.c_str());
148 //fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
149 // file, line, aMessage.c_str());
157 class WinDebugLogCallback : public simgear::LogCallback
160 WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
161 simgear::LogCallback(c, p)
165 virtual void operator()(sgDebugClass c, sgDebugPriority p,
166 const char* file, int line, const std::string& aMessage)
168 if (!shouldLog(c, p)) return;
170 std::ostringstream os;
171 os << debugClassToString(c) << ":" << aMessage << std::endl;
172 OutputDebugStringA(os.str().c_str());
178 class LogStreamPrivate : public SGThread
182 * storage of a single log entry. Note this is not used for a persistent
183 * store, but rather for short term buffering between the submitting
184 * and output threads.
189 LogEntry(sgDebugClass c, sgDebugPriority p,
190 const char* f, int l, const std::string& msg) :
191 debugClass(c), debugPriority(p), file(f), line(l),
196 sgDebugClass debugClass;
197 sgDebugPriority debugPriority;
206 PauseThread(LogStreamPrivate* parent) : m_parent(parent)
208 m_wasRunning = m_parent->stop();
214 m_parent->startLog();
218 LogStreamPrivate* m_parent;
224 m_logPriority(SG_ALERT),
227 bool addStderr = true;
228 #if defined (SG_WINDOWS)
229 // Check for stream redirection, has to be done before we call
230 // Attach / AllocConsole
231 const bool isFile = (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_DISK); // Redirect to file?
232 if (AttachConsole(ATTACH_PARENT_PROCESS) == 0) {
233 // attach failed, don't install the callback
238 // No - OK! now set streams to attached console
239 freopen("conout$", "w", stdout);
240 freopen("conout$", "w", stderr);
244 m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
245 m_consoleCallbacks.push_back(m_callbacks.back());
247 #if defined (SG_WINDOWS) && !defined(NDEBUG)
248 m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
249 m_consoleCallbacks.push_back(m_callbacks.back());
255 BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
261 SGBlockingQueue<LogEntry> m_entries;
263 typedef std::vector<simgear::LogCallback*> CallbackVec;
264 CallbackVec m_callbacks;
265 /// subset of callbacks which correspond to stdout / console,
266 /// and hence should dynamically reflect console logging settings
267 CallbackVec m_consoleCallbacks;
269 sgDebugClass m_logClass;
270 sgDebugPriority m_logPriority;
275 SGGuard<SGMutex> g(m_lock);
276 if (m_isRunning) return;
284 LogEntry entry(m_entries.pop());
285 // special marker entry detected, terminate the thread since we are
286 // making a configuration change or quitting the app
287 if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) {
291 // submit to each installed callback in turn
292 BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
293 (*cb)(entry.debugClass, entry.debugPriority,
294 entry.file, entry.line, entry.message);
296 } // of main thread loop
301 SGGuard<SGMutex> g(m_lock);
306 // log a special marker value, which will cause the thread to wakeup,
308 log(SG_NONE, SG_ALERT, "done", -1, "");
315 void addCallback(simgear::LogCallback* cb)
317 PauseThread pause(this);
318 m_callbacks.push_back(cb);
321 void removeCallback(simgear::LogCallback* cb)
323 PauseThread pause(this);
324 CallbackVec::iterator it = std::find(m_callbacks.begin(), m_callbacks.end(), cb);
325 if (it != m_callbacks.end()) {
326 m_callbacks.erase(it);
330 void setLogLevels( sgDebugClass c, sgDebugPriority p )
332 PauseThread pause(this);
335 BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
336 cb->setLogLevels(c, p);
340 bool would_log( sgDebugClass c, sgDebugPriority p ) const
342 if (p >= SG_INFO) return true;
343 return ((c & m_logClass) != 0 && p >= m_logPriority);
346 void log( sgDebugClass c, sgDebugPriority p,
347 const char* fileName, int line, const std::string& msg)
349 LogEntry entry(c, p, fileName, line, msg);
350 m_entries.push(entry);
354 /////////////////////////////////////////////////////////////////////////////
356 static logstream* global_logstream = NULL;
357 static LogStreamPrivate* global_privateLogstream = NULL;
358 static SGMutex global_logStreamLock;
360 logstream::logstream()
362 global_privateLogstream = new LogStreamPrivate;
363 global_privateLogstream->startLog();
366 logstream::~logstream()
369 global_privateLogstream->stop();
370 delete global_privateLogstream;
374 logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
376 global_privateLogstream->setLogLevels(c, p);
380 logstream::addCallback(simgear::LogCallback* cb)
382 global_privateLogstream->addCallback(cb);
386 logstream::removeCallback(simgear::LogCallback* cb)
388 global_privateLogstream->removeCallback(cb);
392 logstream::log( sgDebugClass c, sgDebugPriority p,
393 const char* fileName, int line, const std::string& msg)
395 global_privateLogstream->log(c, p, fileName, line, msg);
399 logstream::popup( const std::string& msg)
401 popup_msgs.push_back(msg);
405 logstream::get_popup()
408 if (!popup_msgs.empty())
410 rv = popup_msgs.front();
411 popup_msgs.erase(popup_msgs.begin());
417 logstream::has_popup()
419 return (popup_msgs.size() > 0) ? true : false;
423 logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
425 return global_privateLogstream->would_log(c,p);
429 logstream::get_log_classes() const
431 return global_privateLogstream->m_logClass;
435 logstream::get_log_priority() const
437 return global_privateLogstream->m_logPriority;
441 logstream::set_log_priority( sgDebugPriority p)
443 global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
447 logstream::set_log_classes( sgDebugClass c)
449 global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
456 // Force initialization of cerr.
457 static std::ios_base::Init initializer;
459 // http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
460 // in the absence of portable memory barrier ops in Simgear,
461 // let's keep this correct & safe
462 SGGuard<SGMutex> g(global_logStreamLock);
464 if( !global_logstream )
465 global_logstream = new logstream();
466 return *global_logstream;
470 logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
472 global_privateLogstream->addCallback(new FileLogCallback(aPath, c, p));
478 void requestConsole()
480 // this is a no-op now, stub exists for compatability for the moment.
483 void shutdownLogging()
485 SGGuard<SGMutex> g(global_logStreamLock);
486 delete global_logstream;
487 global_logstream = 0;
490 } // of namespace simgear