]> git.mxchange.org Git - simgear.git/blobdiff - simgear/debug/logstream.cxx
Merge branch 'next' of git.mxchange.org:/var/cache/git/repos/simgear into next
[simgear.git] / simgear / debug / logstream.cxx
index 664038b93a713e57ebcfb368ca0ea5407e236dba..453ad0670000770d2dadc508312d360aef50d534 100644 (file)
 //
 // $Id$
 
-#include <iostream>
+#include <simgear_config.h>
 
 #include "logstream.hxx"
 
-logstream *logstream::global_logstream = 0;
+#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/sgstream.hxx>
+#include <simgear/misc/sg_path.hxx>
+
+#if defined (SG_WINDOWS)
+// for AllocConsole, OutputDebugString
+    #include <windows.h>
+#endif
+
+const char* debugClassToString(sgDebugClass c)
+{
+    switch (c) {
+    case SG_NONE:       return "none";
+    case SG_TERRAIN:    return "terrain";
+    case SG_ASTRO:      return "astro";
+    case SG_FLIGHT:     return "flight";
+    case SG_INPUT:      return "input";
+    case SG_GL:         return "opengl";
+    case SG_VIEW:       return "view";
+    case SG_COCKPIT:    return "cockpit";
+    case SG_GENERAL:    return "general";
+    case SG_MATH:       return "math";
+    case SG_EVENT:      return "event";
+    case SG_AIRCRAFT:   return "aircraft";
+    case SG_AUTOPILOT:  return "autopilot";
+    case SG_IO:         return "io";
+    case SG_CLIPPER:    return "clipper";
+    case SG_NETWORK:    return "network";
+    case SG_ATC:        return "atc";
+    case SG_NASAL:      return "nasal";
+    case SG_INSTR:      return "instruments";
+    case SG_SYSTEMS:    return "systems";
+    case SG_AI:         return "ai";
+    case SG_ENVIRONMENT:return "environment";
+    case SG_SOUND:      return "sound";
+    case SG_NAVAID:     return "navaid";
+    case SG_GUI:        return "gui";
+    case SG_TERRASYNC:  return "terrasync";
+    case SG_PARTICLES:  return "particles";
+    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 SGPath& aPath, sgDebugClass c, sgDebugPriority p) :
+           simgear::LogCallback(c, p)
+    {
+        m_file.open(aPath, 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 (!shouldLog(c, p)) return;
+        m_file << debugClassToString(c) << ":" << (int) p
+            << ":" << file << ":" << line << ":" << message << std::endl;
+    }
+private:
+    sg_ofstream m_file;
+};
+
+class StderrLogCallback : public simgear::LogCallback
+{
+public:
+    StderrLogCallback(sgDebugClass c, sgDebugPriority p) :
+               simgear::LogCallback(c, p)
+    {
+    }
 
-bool            logbuf::logging_enabled = true;
-#ifdef _WIN32
-   bool         logbuf::has_console = true;
+#if defined (SG_WINDOWS)
+    ~StderrLogCallback()
+    {
+        FreeConsole();
+    }
 #endif
-sgDebugClass    logbuf::logClass        = SG_NONE;
-sgDebugPriority logbuf::logPriority     = SG_INFO;
-streambuf*      logbuf::sbuf            = NULL;
 
-namespace {
-struct ignore_me
+    virtual void operator()(sgDebugClass c, sgDebugPriority p,
+        const char* file, int line, const std::string& aMessage)
+    {
+        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);
+    }
+};
+
+
+#ifdef SG_WINDOWS
+
+class WinDebugLogCallback : public simgear::LogCallback
 {
-    ignore_me()
+public:
+    WinDebugLogCallback(sgDebugClass c, sgDebugPriority p) :
+               simgear::LogCallback(c, p)
     {
-        logstream::initGlobalLogstream();
+    }
+
+    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());
     }
 };
-static ignore_me im;
+
+#endif
+
+class LogStreamPrivate : public SGThread
+{
+private:
+    /**
+     * storage of a single log entry. Note this is not used for a persistent
+     * store, but rather for short term buffering between the submitting
+     * and output threads.
+     */
+    class LogEntry
+    {
+    public:
+        LogEntry(sgDebugClass c, sgDebugPriority p,
+            const char* f, int l, const std::string& msg) :
+            debugClass(c), debugPriority(p), file(f), line(l),
+                message(msg)
+        {
+        }
+
+        sgDebugClass debugClass;
+        sgDebugPriority debugPriority;
+        const char* file;
+        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_ALERT),
+        m_isRunning(false)
+    {
+        bool addStderr = true;
+#if defined (SG_WINDOWS)
+        // Check for stream redirection, has to be done before we call
+        // Attach / AllocConsole
+        const bool isFile = (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_DISK); // Redirect to file?
+        if (AttachConsole(ATTACH_PARENT_PROCESS) == 0) {
+            // attach failed, don't install the callback
+            addStderr = false;
+        }
+
+        if (!isFile) {
+            // No - OK! now set streams to attached console
+            freopen("conout$", "w", stdout);
+            freopen("conout$", "w", stderr);
+        }
+#endif
+        if (addStderr) {
+            m_callbacks.push_back(new StderrLogCallback(m_logClass, m_logPriority));
+            m_consoleCallbacks.push_back(m_callbacks.back());
+        }
+#if defined (SG_WINDOWS) && !defined(NDEBUG)
+               m_callbacks.push_back(new WinDebugLogCallback(m_logClass, m_logPriority));
+               m_consoleCallbacks.push_back(m_callbacks.back());
+#endif
+    }
+
+    ~LogStreamPrivate()
+    {
+        BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
+            delete cb;
+        }
+    }
+
+    SGMutex m_lock;
+    SGBlockingQueue<LogEntry> m_entries;
+
+    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;
+
+    void startLog()
+    {
+        SGGuard<SGMutex> g(m_lock);
+        if (m_isRunning) return;
+        m_isRunning = true;
+        start();
+    }
+
+    virtual void run()
+    {
+        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;
+            }
+
+            // submit to each installed callback in turn
+            BOOST_FOREACH(simgear::LogCallback* cb, m_callbacks) {
+                (*cb)(entry.debugClass, entry.debugPriority,
+                    entry.file, entry.line, entry.message);
+            }
+        } // of main thread loop
+    }
+
+    bool stop()
+    {
+        SGGuard<SGMutex> g(m_lock);
+        if (!m_isRunning) {
+            return false;
+        }
+
+        // log a special marker value, which will cause the thread to wakeup,
+        // and then exit
+        log(SG_NONE, SG_ALERT, "done", -1, "");
+        join();
+
+        m_isRunning = false;
+        return true;
+    }
+
+    void addCallback(simgear::LogCallback* cb)
+    {
+        PauseThread pause(this);
+        m_callbacks.push_back(cb);
+    }
+
+    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;
+        return ((c & m_logClass) != 0 && p >= m_logPriority);
+    }
+
+    void log( sgDebugClass c, sgDebugPriority p,
+            const char* fileName, int line, const std::string& msg)
+    {
+        LogEntry entry(c, p, fileName, line, msg);
+        m_entries.push(entry);
+    }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+static logstream* global_logstream = NULL;
+static LogStreamPrivate* global_privateLogstream = NULL;
+static SGMutex global_logStreamLock;
+
+logstream::logstream()
+{
+    global_privateLogstream = new LogStreamPrivate;
+    global_privateLogstream->startLog();
 }
 
-logbuf::logbuf()
+logstream::~logstream()
 {
-//     if ( sbuf == NULL )
-//     sbuf = cerr.rdbuf();
+    popup_msgs.clear();
+    global_privateLogstream->stop();
+    delete global_privateLogstream;
 }
 
-logbuf::~logbuf()
+void
+logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
 {
-    if ( sbuf )
-           sync();
+    global_privateLogstream->setLogLevels(c, p);
 }
 
 void
-logbuf::set_sb( streambuf* sb )
+logstream::addCallback(simgear::LogCallback* cb)
 {
-    if ( sbuf )
-           sync();
+    global_privateLogstream->addCallback(cb);
+}
 
-    sbuf = sb;
+void
+logstream::removeCallback(simgear::LogCallback* cb)
+{
+    global_privateLogstream->removeCallback(cb);
 }
 
 void
-logbuf::set_log_level( sgDebugClass c, sgDebugPriority p )
+logstream::log( sgDebugClass c, sgDebugPriority p,
+        const char* fileName, int line, const std::string& msg)
 {
-    logClass = c;
-    logPriority = p;
+    global_privateLogstream->log(c, p, fileName, line, msg);
 }
 
 void
-logbuf::set_log_classes (sgDebugClass c)
+logstream::popup( const std::string& msg)
 {
-    logClass = c;
+    popup_msgs.push_back(msg);
 }
 
-sgDebugClass
-logbuf::get_log_classes ()
+std::string
+logstream::get_popup()
 {
-    return logClass;
+    std::string rv = "";
+    if (!popup_msgs.empty())
+    {
+        rv = popup_msgs.front();
+        popup_msgs.erase(popup_msgs.begin());
+    }
+    return rv;
 }
 
-void
-logbuf::set_log_priority (sgDebugPriority p)
+bool
+logstream::has_popup()
+{
+    return (popup_msgs.size() > 0) ? true : false;
+}
+
+bool
+logstream::would_log( sgDebugClass c, sgDebugPriority p ) const
 {
-    logPriority = p;
+    return global_privateLogstream->would_log(c,p);
+}
+
+sgDebugClass
+logstream::get_log_classes() const
+{
+    return global_privateLogstream->m_logClass;
 }
 
 sgDebugPriority
-logbuf::get_log_priority ()
+logstream::get_log_priority() const
 {
-    return logPriority;
+    return global_privateLogstream->m_logPriority;
 }
 
 void
-logstream::setLogLevels( sgDebugClass c, sgDebugPriority p )
+logstream::set_log_priority( sgDebugPriority p)
+{
+    global_privateLogstream->setLogLevels(global_privateLogstream->m_logClass, p);
+}
+
+void
+logstream::set_log_classes( sgDebugClass c)
 {
-    logbuf::set_log_level( c, p );
+    global_privateLogstream->setLogLevels(c, global_privateLogstream->m_logPriority);
 }
 
-logstream *
-logstream::initGlobalLogstream()
+
+logstream&
+sglog()
 {
     // 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
+    SGGuard<SGMutex> g(global_logStreamLock);
+
     if( !global_logstream )
-        global_logstream = new logstream(std::cerr);
-    return global_logstream;
+        global_logstream = new logstream();
+    return *global_logstream;
 }
+
+void
+logstream::logToFile( const SGPath& aPath, sgDebugClass c, sgDebugPriority p )
+{
+    global_privateLogstream->addCallback(new FileLogCallback(aPath, c, p));
+}
+
+namespace simgear
+{
+
+void requestConsole()
+{
+    // this is a no-op now, stub exists for compatability for the moment.
+}
+
+void shutdownLogging()
+{
+    SGGuard<SGMutex> g(global_logStreamLock);
+    delete global_logstream;
+    global_logstream = 0;
+}
+
+} // of namespace simgear