//
// $Id$
+#include <simgear_config.h>
+
#include "logstream.hxx"
#include <iostream>
#include <fstream>
+#include <sstream>
+#include <algorithm>
#include <boost/foreach.hpp>
+#include <simgear/sg_inlines.h>
#include <simgear/threads/SGThread.hxx>
#include <simgear/threads/SGQueue.hxx>
#include <simgear/threads/SGGuard.hxx>
#include <simgear/misc/sg_path.hxx>
+#ifdef SG_WINDOWS
+// for AllocConsole, OutputDebugString
+ #include "windows.h"
+#endif
+
const char* debugClassToString(sgDebugClass c)
{
switch (c) {
case SG_ENVIRONMENT:return "environment";
case SG_SOUND: return "sound";
case SG_NAVAID: return "navaid";
+ case SG_GUI: return "gui";
default: return "unknown";
}
}
//////////////////////////////////////////////////////////////////////////////
+namespace simgear
+{
+
+LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
+ m_class(c),
+ m_priority(p)
+{
+}
+
+bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
+{
+ return ((c & m_class) != 0 && p >= m_priority);
+}
+
+void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
+{
+ m_priority = p;
+ m_class = c;
+}
+
+} // of namespace simgear
+
+//////////////////////////////////////////////////////////////////////////////
+
class FileLogCallback : public simgear::LogCallback
{
public:
FileLogCallback(const std::string& aPath, sgDebugClass c, sgDebugPriority p) :
- m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc),
- m_class(c),
- m_priority(p)
+ simgear::LogCallback(c, p),
+ m_file(aPath.c_str(), std::ios_base::out | std::ios_base::trunc)
{
}
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& message)
{
- if ((c & m_class) == 0 || p < m_priority) return;
+ if (!shouldLog(c, p)) return;
m_file << debugClassToString(c) << ":" << (int) p
<< ":" << file << ":" << line << ":" << message << std::endl;
}
private:
std::ofstream m_file;
- sgDebugClass m_class;
- sgDebugPriority m_priority;
};
class StderrLogCallback : public simgear::LogCallback
{
public:
- StderrLogCallback()
+ StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
+ simgear::LogCallback(c, p)
{
-#ifdef _WIN32
+#ifdef SG_WINDOWS
AllocConsole(); // but only if we want a console
+ freopen("conin$", "r", stdin);
+ freopen("conout$", "w", stdout);
+ freopen("conout$", "w", stderr);
#endif
}
-
+
virtual void operator()(sgDebugClass c, sgDebugPriority p,
const char* file, int line, const std::string& aMessage)
{
- // if running under MSVC, we could use OutputDebugString here
+ if (!shouldLog(c, p)) return;
fprintf(stderr, "%s\n", aMessage.c_str());
//fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p,
// file, line, aMessage.c_str());
fflush(stderr);
}
-private:
+};
+
+
+#ifdef SG_WINDOWS
+
+class WinDebugLogCallback : public simgear::LogCallback
+{
+public:
+ WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
+ simgear::LogCallback(c, p)
+ {
+ }
+ virtual void operator()(sgDebugClass c, sgDebugPriority p,
+ const char* file, int line, const std::string& aMessage)
+ {
+ if (!shouldLog(c, p)) return;
+
+ std::ostringstream os;
+ os << debugClassToString(c) << ":" << aMessage << std::endl;
+ OutputDebugStringA(os.str().c_str());
+ }
};
-
+
+#endif
+
class LogStreamPrivate : public SGThread
{
private:
int line;
std::string message;
};
+
+ class PauseThread
+ {
+ public:
+ PauseThread(LogStreamPrivate* parent) : m_parent(parent)
+ {
+ m_wasRunning = m_parent->stop();
+ }
+
+ ~PauseThread()
+ {
+ if (m_wasRunning) {
+ m_parent->startLog();
+ }
+ }
+ private:
+ LogStreamPrivate* m_parent;
+ bool m_wasRunning;
+ };
public:
LogStreamPrivate() :
- m_logClass(SG_ALL), m_logPriority(SG_WARN),
- m_isRunning(false)
- { }
+ m_logClass(SG_ALL),
+ m_logPriority(SG_ALERT),
+ m_isRunning(false),
+ m_consoleRequested(false)
+ {
+
+#if !defined(SG_WINDOWS)
+ m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
+ m_consoleCallbacks.push_back(m_callbacks.back());
+ m_consoleRequested = true;
+#endif
+
+#if defined (SG_WINDOWS) && !defined(NDEBUG)
+ m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
+ m_consoleCallbacks.push_back(m_callbacks.back());
+#endif
+ }
SGMutex m_lock;
SGBlockingQueue<LogEntry> m_entries;
- std::vector<simgear::LogCallback*> m_callbacks;
+
+ typedef std::vector<simgear::LogCallback*> CallbackVec;
+ CallbackVec m_callbacks;
+ /// subset of callbacks which correspond to stdout / console,
+ /// and hence should dynamically reflect console logging settings
+ CallbackVec m_consoleCallbacks;
+
sgDebugClass m_logClass;
sgDebugPriority m_logPriority;
bool m_isRunning;
+ bool m_consoleRequested;
void startLog()
{
{
while (1) {
LogEntry entry(m_entries.pop());
+ // special marker entry detected, terminate the thread since we are
+ // making a configuration change or quitting the app
if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) {
return;
}
void addCallback(simgear::LogCallback* cb)
{
- bool wasRunning = stop();
+ PauseThread pause(this);
m_callbacks.push_back(cb);
- if (wasRunning) {
- startLog();
+ }
+
+ void removeCallback(simgear::LogCallback* cb)
+ {
+ PauseThread pause(this);
+ CallbackVec::iterator it = std::find(m_callbacks.begin(), m_callbacks.end(), cb);
+ if (it != m_callbacks.end()) {
+ m_callbacks.erase(it);
}
}
+ void setLogLevels( sgDebugClass c, sgDebugPriority p )
+ {
+ PauseThread pause(this);
+ m_logPriority = p;
+ m_logClass = c;
+ BOOST_FOREACH(simgear::LogCallback* cb, m_consoleCallbacks) {
+ cb->setLogLevels(c, p);
+ }
+ }
+
bool would_log( sgDebugClass c, sgDebugPriority p ) const
{
if (p >= SG_INFO) return true;
LogEntry entry(c, p, fileName, line, msg);
m_entries.push(entry);
}
+
+ void requestConsole()
+ {
+ PauseThread pause(this);
+ if (m_consoleRequested) {
+ return;
+ }
+
+ m_consoleRequested = true;
+ m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
+ m_consoleCallbacks.push_back(m_callbacks.back());
+ }
};
/////////////////////////////////////////////////////////////////////////////
logstream::logstream()
{
global_privateLogstream = new LogStreamPrivate;
- global_privateLogstream->addCallback(new StderrLogCallback);
global_privateLogstream->startLog();
}
void
logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
{
- // we don't guard writing these with a mutex, since we assume setting
- // occurs very rarely, and any data races are benign.
- global_privateLogstream->m_logClass = c;
- global_privateLogstream->m_logPriority = p;
+ global_privateLogstream->setLogLevels(c, p);
}
void
global_privateLogstream->addCallback(cb);
}
+void
+logstream::removeCallback(simgear::LogCallback* cb)
+{
+ global_privateLogstream->removeCallback(cb);
+}
+
void
logstream::log( sgDebugClass c, sgDebugPriority p,
const char* fileName, int line, const std::string& msg)
void
logstream::set_log_priority( sgDebugPriority p)
{
- global_privateLogstream->m_logPriority = p;
+ global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
}
void
logstream::set_log_classes( sgDebugClass c)
{
- global_privateLogstream->m_logClass = c;
+ global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
}
logstream&
{
// Force initialization of cerr.
static std::ios_base::Init initializer;
+
+ // http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
+ // in the absence of portable memory barrier ops in Simgear,
+ // let's keep this correct & safe
+ static SGMutex m;
+ SGGuard<SGMutex> g(m);
+
if( !global_logstream )
global_logstream = new logstream();
return *global_logstream;
global_privateLogstream->addCallback(new FileLogCallback(aPath.str(), c, p));
}
+namespace simgear
+{
+
+void requestConsole()
+{
+ sglog(); // force creation
+ global_privateLogstream->requestConsole();
+}
+
+} // of namespace simgear