changeset 3363:cf4e35fef742

Merged in emscripten-logging (pull request #10) Emscripten logging
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 07 May 2019 11:12:29 +0000
parents 7569d3dc1c20 (current diff) 0b88d89e71d5 (diff)
children ea299aca479b
files
diffstat 6 files changed, 541 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue May 07 12:28:11 2019 +0200
+++ b/CMakeLists.txt	Tue May 07 11:12:29 2019 +0000
@@ -115,6 +115,7 @@
   UnitTestsSources/ImageProcessingTests.cpp
   UnitTestsSources/ImageTests.cpp
   UnitTestsSources/JpegLosslessTests.cpp
+  UnitTestsSources/LoggingTests.cpp
   UnitTestsSources/LuaTests.cpp
   UnitTestsSources/MemoryCacheTests.cpp
   UnitTestsSources/MultiThreadingTests.cpp
--- a/Core/Logging.cpp	Tue May 07 12:28:11 2019 +0200
+++ b/Core/Logging.cpp	Tue May 07 11:12:29 2019 +0000
@@ -63,7 +63,17 @@
     void EnableTraceLevel(bool enabled)
     {
     }
+    
+    bool IsTraceLevelEnabled()
+    {
+      return false;
+    }
 
+    bool IsInfoLevelEnabled()
+    {
+      return false;
+    }
+    
     void SetTargetFile(const std::string& path)
     {
     }
@@ -140,12 +150,18 @@
 #elif ORTHANC_ENABLE_LOGGING_STDIO == 1
 
 /*********************************************************
- * Logger compatible with <stdio.h>
+ * Logger compatible with <stdio.h> OR logger that sends its
+ * output to the emscripten html5 api (depending on the 
+ * definition of __EMSCRIPTEN__)
  *********************************************************/
 
 #include <stdio.h>
 #include <boost/lexical_cast.hpp>
 
+#ifdef __EMSCRIPTEN__
+#include "emscripten/html5.h"
+#endif
+
 namespace Orthanc
 {
   namespace Logging
@@ -153,6 +169,67 @@
     static bool globalVerbose_ = false;
     static bool globalTrace_ = false;
     
+#ifdef __EMSCRIPTEN__
+    void defaultErrorLogFunc(const char* msg)
+    {
+      emscripten_console_error(msg);
+    }
+
+    void defaultWarningLogFunc(const char* msg)
+    {
+      emscripten_console_warn(msg);
+    }
+
+    void defaultInfoLogFunc(const char* msg)
+    {
+      emscripten_console_log(msg);
+    }
+
+    void defaultTraceLogFunc(const char* msg)
+    {
+      emscripten_console_log(msg);
+    }
+#else
+// __EMSCRIPTEN__ not #defined
+    void defaultErrorLogFunc(const char* msg)
+    {
+      fprintf(stderr, "E: %s\n", msg);
+    }
+
+    void defaultWarningLogFunc(const char*)
+    {
+      fprintf(stdout, "W: %s\n", msg);
+    }
+
+    void defaultInfoLogFunc(const char*)
+    {
+      fprintf(stdout, "I: %s\n", msg);
+    }
+
+    void defaultTraceLogFunc(const char*)
+    {
+      fprintf(stdout, "T: %s\n", msg);
+    }
+#endif 
+// __EMSCRIPTEN__
+
+    static LoggingFunction globalErrorLogFunc = defaultErrorLogFunc;
+    static LoggingFunction globalWarningLogFunc = defaultWarningLogFunc;
+    static LoggingFunction globalInfoLogFunc = defaultInfoLogFunc;
+    static LoggingFunction globalTraceLogFunc = defaultTraceLogFunc;
+
+    void SetErrorWarnInfoTraceLoggingFunctions(
+      LoggingFunction errorLogFunc,
+      LoggingFunction warningLogfunc,
+      LoggingFunction infoLogFunc,
+      LoggingFunction traceLogFunc)
+    {
+      globalErrorLogFunc = errorLogFunc;
+      globalWarningLogFunc = warningLogfunc;
+      globalInfoLogFunc = infoLogFunc;
+      globalTraceLogFunc = traceLogFunc;
+    }
+
     InternalLogger::InternalLogger(InternalLevel level,
                                    const char* file  /* ignored */,
                                    int line  /* ignored */) :
@@ -165,29 +242,35 @@
       switch (level_)
       {
         case InternalLevel_ERROR:
-          fprintf(stderr, "E: %s\n", message_.c_str());
+          globalErrorLogFunc(message_.c_str());
           break;
 
         case InternalLevel_WARNING:
-          fprintf(stdout, "W: %s\n", message_.c_str());
+          globalWarningLogFunc(message_.c_str());
           break;
 
         case InternalLevel_INFO:
           if (globalVerbose_)
           {
-            fprintf(stdout, "I: %s\n", message_.c_str());
+            globalInfoLogFunc(message_.c_str());
+            // TODO: stone_console_info(message_.c_str());
           }
           break;
 
         case InternalLevel_TRACE:
           if (globalTrace_)
           {
-            fprintf(stdout, "T: %s\n", message_.c_str());
+            globalTraceLogFunc(message_.c_str());
           }
           break;
 
         default:
-          fprintf(stderr, "Unknown log level (%d) for message: %s\n", level_, message_.c_str());
+        {
+          std::stringstream ss;
+          ss << "Unknown log level (" << level_ << ") for message: " << message_;
+          auto s = ss.str();
+          globalErrorLogFunc(s.c_str());
+        }
       }
     }
 
@@ -200,10 +283,21 @@
       globalVerbose_ = enabled;
     }
 
+    bool IsInfoLevelEnabled()
+    {
+      return globalVerbose_;
+    }
+
     void EnableTraceLevel(bool enabled)
     {
       globalTrace_ = enabled;
     }
+
+    bool IsTraceLevelEnabled()
+    {
+      return globalTrace_;
+    }
+
   }
 }
 
@@ -257,6 +351,19 @@
     {
     }
   };
+
+  struct LoggingMementoImpl
+  {
+    bool valid_;
+    bool infoEnabled_;
+    bool traceEnabled_;
+    std::string  targetFile_;
+    std::string  targetFolder_;
+
+    std::ostream* error_;
+    std::ostream* warning_;
+    std::ostream* info_;
+  };
 }
 
 
@@ -374,6 +481,59 @@
       }
     }
 
+
+    LoggingMemento CreateLoggingMemento()
+    {
+      LoggingMementoImpl* memento = new LoggingMementoImpl();
+
+      memento->valid_ = true;
+      {
+        boost::mutex::scoped_lock lock(loggingMutex_);
+        memento->infoEnabled_ = loggingContext_->infoEnabled_;
+        memento->traceEnabled_ = loggingContext_->traceEnabled_;
+        memento->targetFile_ = loggingContext_->targetFile_;
+        memento->targetFolder_ = loggingContext_->targetFolder_;
+
+        memento->error_ = loggingContext_->error_;
+        memento->warning_ = loggingContext_->warning_;
+        memento->info_ = loggingContext_->info_;
+      }
+      return reinterpret_cast<void*>(memento);
+    }
+    
+    void RestoreLoggingMemento(LoggingMemento mementoPtr)
+    {
+      LoggingMementoImpl* memento = 
+        reinterpret_cast<LoggingMementoImpl*>(mementoPtr);
+      if (!memento->valid_)
+        throw std::runtime_error("Memento already used");
+      memento->valid_ = false;
+      {
+        boost::mutex::scoped_lock lock(loggingMutex_);
+        loggingContext_.reset(new LoggingContext);
+        loggingContext_->error_ = memento->error_;
+        loggingContext_->warning_ = memento->warning_;
+        loggingContext_->info_ = memento->info_;
+      }
+      EnableInfoLevel(memento->infoEnabled_);
+      EnableTraceLevel(memento->traceEnabled_);
+      if (!memento->targetFolder_.empty())
+      {
+        SetTargetFolder(memento->targetFolder_);
+      }
+      else if (!memento->targetFile_.empty())
+      {
+        SetTargetFile(memento->targetFile_);
+      }
+    }
+
+    void DiscardLoggingMemento(LoggingMemento mementoPtr)
+    {
+      LoggingMementoImpl* memento =
+        reinterpret_cast<LoggingMementoImpl*>(mementoPtr);
+      delete memento;
+    }
+
     void EnableInfoLevel(bool enabled)
     {
       boost::mutex::scoped_lock lock(loggingMutex_);
@@ -388,6 +548,14 @@
       }
     }
 
+    bool IsInfoLevelEnable()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      return loggingContext_->infoEnabled_;
+    }
+
     void EnableTraceLevel(bool enabled)
     {
       boost::mutex::scoped_lock lock(loggingMutex_);
@@ -402,6 +570,14 @@
       }
     }
 
+    bool IsTraceLevelEnable()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      return loggingContext_->traceEnabled_;
+    }
+
 
     static void CheckFile(std::auto_ptr<std::ofstream>& f)
     {
@@ -601,7 +777,55 @@
         loggingContext_->file_->flush();
       }
     }
+
+    void SetErrorWarnInfoLoggingStreams(std::ostream* errorStream,
+      std::ostream* warningStream,
+      std::ostream* infoStream)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      std::auto_ptr<LoggingContext> old = loggingContext_;
+      loggingContext_.reset(new LoggingContext);
+      loggingContext_->error_ = errorStream;
+      loggingContext_->warning_ = warningStream;
+      loggingContext_->info_ = infoStream;
+      lock.unlock();
+      EnableInfoLevel(old->infoEnabled_);
+      EnableTraceLevel(old->traceEnabled_);
+    }
+
+#ifdef __EMSCRIPTEN__
+
+    FuncStreamBuf<decltype(emscripten_console_error)> 
+      globalEmscriptenErrorStreamBuf(emscripten_console_error);
+    std::auto_ptr<std::ostream> globalEmscriptenErrorStream;
+
+    FuncStreamBuf<decltype(emscripten_console_warn)>
+      globalEmscriptenWarningStreamBuf(emscripten_console_warn);
+    std::auto_ptr<std::ostream> globalEmscriptenWarningStream;
+
+    FuncStreamBuf<decltype(emscripten_console_log)>
+      globalEmscriptenInfoStreamBuf(emscripten_console_log);
+    std::auto_ptr<std::ostream> globalEmscriptenInfoStream;
+
+    void EnableEmscriptenLogging()
+    {
+      globalEmscriptenErrorStream.reset(new ostream(
+        &globalEmscriptenErrorStreamBuf));
+
+      globalEmscriptenWarningStream.reset(new ostream(
+        &globalEmscriptenWarningStreamBuf));
+
+      globalEmscriptenInfoStream.reset(new ostream(
+        &globalEmscriptenInfoStreamBuf));
+
+      SetErrorWarnInfoLoggingStreams(
+        &globalEmscriptenErrorStream,
+        &globalEmscriptenWarningStream
+        &globalEmscriptenInfoStream);
+    }
+#endif
   }
 }
 
+
 #endif   // ORTHANC_ENABLE_LOGGING
--- a/Core/Logging.h	Tue May 07 12:28:11 2019 +0200
+++ b/Core/Logging.h	Tue May 07 11:12:29 2019 +0000
@@ -29,8 +29,6 @@
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
-
-
 #pragma once
 
 #include <iostream>
@@ -81,10 +79,24 @@
 
     void EnableTraceLevel(bool enabled);
 
+    bool IsTraceLevelEnabled();
+
+    bool IsInfoLevelEnabled();
+
     void SetTargetFile(const std::string& path);
 
     void SetTargetFolder(const std::string& path);
 
+#if ORTHANC_ENABLE_LOGGING_STDIO == 1
+    typedef void (*LoggingFunction)(const char*);
+    void SetErrorWarnInfoTraceLoggingFunctions(
+      LoggingFunction errorLogFunc,
+      LoggingFunction warningLogfunc,
+      LoggingFunction infoLogFunc,
+      LoggingFunction traceLogFunc);
+#endif
+
+
     struct NullStream : public std::ostream 
     {
       NullStream() : 
@@ -102,13 +114,11 @@
   }
 }
 
-
 #if ORTHANC_ENABLE_LOGGING != 1
 
 #  define LOG(level)   ::Orthanc::Logging::NullStream()
 #  define VLOG(level)  ::Orthanc::Logging::NullStream()
 
-
 #elif (ORTHANC_ENABLE_LOGGING_PLUGIN == 1 ||    \
        ORTHANC_ENABLE_LOGGING_STDIO == 1)
 
@@ -188,6 +198,70 @@
         return (*stream_) << boost::lexical_cast<std::string>(message);
       }
     };
+
+    /**
+    opaque pointer that represents the state of the logging configuration
+    */
+    typedef void* LoggingMemento;
+
+    /**
+    Returns an object that contains the logging configuration.
+
+    This function allocates resources that you must dispose of by
+    using either RestoreLoggingMemento or DiscardLoggingMemento.
+
+    This function is only to be used by tests.
+    */
+    LoggingMemento CreateLoggingMemento();
+
+    /**
+    Restores the logging configuration. The logging system is restored in 
+    the state it was in when the memento object was created through 
+    GetLoggingMemento().
+
+    After calling this function, the memento object may not be used 
+    again
+
+    This function is only to be used by tests.
+    */
+    void RestoreLoggingMemento(LoggingMemento memento);
+
+    /**
+    Call this function if you do not plan on restoring the logging 
+    configuration state that you captured with CreateLoggingMemento
+
+    This function is only to be used by tests.
+    */
+    void DiscardLoggingMemento(LoggingMemento memento);
+
+    /**
+      Set custom logging streams for the error, warning and info logs.
+      This function may not be called if a log file or folder has been 
+      set beforehand. All three pointers must be valid and cannot be NULL.
+
+      Please ensure the supplied streams remain alive and valid as long as
+      logging calls are performed.
+
+      In order to prevent dangling pointer usage, it is recommended to call
+      Orthanc::Logging::Reset() before the stream objects are destroyed and 
+      the pointers become invalid.
+    */
+    void SetErrorWarnInfoLoggingStreams(std::ostream* errorStream,
+                                        std::ostream* warningStream, 
+                                        std::ostream* infoStream);
+
+#ifdef __EMSCRIPTEN__
+    /**
+      This function will change the logging streams so that the logging functions 
+      provided by emscripten html5.h API functions are used : it will change the 
+      error_, warning_ and info_  stream objects so that their operator<< writes 
+      into the browser console using emscripten_console_error(), 
+      emscripten_console_warn() and emscripten_console_log(). This will allow for
+      logging levels to be correctly handled by the browser when the code executes
+      in Web Assembly
+    */
+    void EnableEmscriptenLogging();
+#endif
   }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/LoggingUtils.h	Tue May 07 11:12:29 2019 +0000
@@ -0,0 +1,30 @@
+#include <sstream>
+#include <iostream>
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+
+    /**
+      std::streambuf subclass used in FunctionCallingStream
+    */
+    template<typename T>
+    class FuncStreamBuf : public std::stringbuf
+    {
+    public:
+      FuncStreamBuf(T func) : func_(func) {}
+
+      virtual int sync()
+      {
+        std::string text = this->str();
+        const char* buf = text.c_str();
+        func_(buf);
+        this->str("");
+        return 0;
+      }
+    private:
+      T func_;
+    };
+  }
+}
\ No newline at end of file
--- a/LinuxCompilation.txt	Tue May 07 12:28:11 2019 +0200
+++ b/LinuxCompilation.txt	Tue May 07 11:12:29 2019 +0000
@@ -60,7 +60,7 @@
 # cmake -DCMAKE_BUILD_TYPE=Debug ~/Orthanc
 # make
 
-Note that to build the documentation, you will have to install doxyen.
+Note that to build the documentation, you will have to install doxygen.
 
 However, on some GNU/Linux distributions, it is still required to
 download and static link against some third-party dependencies,
@@ -99,21 +99,19 @@
 ------------------------------
 
 # sudo apt-get install build-essential unzip cmake mercurial \
-       	       	       uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \
-       	       	       libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \
-		       zlib1g-dev libdcmtk2-dev libboost1.48-all-dev libwrap0-dev \
+                       uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \
+                       libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \
+                       zlib1g-dev libdcmtk2-dev libboost1.48-all-dev libwrap0-dev \
                        libcharls-dev
 
 # cmake "-DDCMTK_LIBRARIES=boost_locale;CharLS;dcmjpls;wrap;oflog" \
         -DALLOW_DOWNLOADS=ON \
-	-DUSE_SYSTEM_CIVETWEB=OFF \
-	-DUSE_SYSTEM_JSONCPP=OFF \
-	-DUSE_SYSTEM_PUGIXML=OFF \
+        -DUSE_SYSTEM_CIVETWEB=OFF \
+        -DUSE_SYSTEM_JSONCPP=OFF \
+        -DUSE_SYSTEM_PUGIXML=OFF \
         -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \
         -DCMAKE_BUILD_TYPE=Release \
-	~/Orthanc
-
-
+        ~/Orthanc
 
 SUPPORTED - Ubuntu 14.04 LTS and 16.04 LTS
 ------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/LoggingTests.cpp	Tue May 07 11:12:29 2019 +0000
@@ -0,0 +1,194 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+#include <boost/regex.hpp>
+#include <sstream>
+
+#include "../Core/Logging.h"
+#include "../Core/LoggingUtils.h"
+
+using namespace Orthanc::Logging;
+
+static std::stringstream testErrorStream;
+void TestError(const char* message)
+{
+  testErrorStream << message;
+}
+
+static std::stringstream testWarningStream;
+void TestWarning(const char* message)
+{
+  testWarningStream << message;
+}
+
+static std::stringstream testInfoStream;
+void TestInfo(const char* message)
+{
+  testInfoStream << message;
+}
+
+/**
+Extracts the log line payload
+
+"E0423 16:55:43.001194 LoggingTests.cpp:102] Foo bar?\r\n"
+-->
+"Foo bar"
+
+If the log line cannot be matched, the function returns false.
+*/
+
+#ifdef WIN32
+# define EOLSTRING "\r\n"
+#else 
+# define EOLSTRING "\n"
+#endif
+
+static bool GetLogLinePayload(std::string& payload,
+  const std::string& logLine)
+{
+  const char* regexStr = "[A-Z][0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6} "
+    "[a-zA-Z\\.\\-_]+:[0-9]+\\] (.*)" EOLSTRING "$";
+
+  boost::regex regexObj(regexStr);
+
+  //std::stringstream regexSStr;
+  //regexSStr << "E[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6} "
+  //  "[a-zA-Z\\.\\-_]+:[0-9]+\\](.*)\r\n$";
+  //std::string regexStr = regexSStr.str();
+  boost::regex pattern(regexStr);
+  boost::cmatch what;
+  if (regex_match(logLine.c_str(), what, regexObj))
+  {
+    payload = what[1];
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+namespace
+{
+  class LoggingMementoScope
+  {
+  public:
+    LoggingMementoScope() : memento_(CreateLoggingMemento()) {}
+    ~LoggingMementoScope()
+    {
+      RestoreLoggingMemento(memento_);
+    }
+  private:
+    LoggingMemento memento_;
+  };
+}
+
+TEST(FuncStreamBuf, BasicTest)
+{
+  LoggingMementoScope loggingConfiguration;
+
+  EnableTraceLevel(true);
+
+  typedef void(*LoggingFunctionFunc)(const char*);
+
+  FuncStreamBuf<LoggingFunctionFunc> errorStreamBuf(TestError);
+  std::ostream errorStream(&errorStreamBuf);
+
+  FuncStreamBuf<LoggingFunctionFunc> warningStreamBuf(TestWarning);
+  std::ostream warningStream(&warningStreamBuf);
+
+  FuncStreamBuf<LoggingFunctionFunc> infoStreamBuf(TestInfo);
+  std::ostream infoStream(&infoStreamBuf);
+
+  SetErrorWarnInfoLoggingStreams(&errorStream, &warningStream, &infoStream);
+
+  {
+    const char* text = "E is the set of all sets that do not contain themselves. Does E contain itself?";
+    LOG(ERROR) << text;
+    std::string logLine = testErrorStream.str();
+    testErrorStream.str("");
+    testErrorStream.clear();
+    std::string payload;
+    bool ok = GetLogLinePayload(payload, logLine);
+    ASSERT_TRUE(ok);
+    ASSERT_STREQ(payload.c_str(), text);
+  }
+
+  // make sure loglines do not accumulate
+  {
+    const char* text = "some more nonsensical babblingiciously stupid gibberish";
+    LOG(ERROR) << text;
+    std::string logLine = testErrorStream.str();
+    testErrorStream.str("");
+    testErrorStream.clear();
+    std::string payload;
+    bool ok = GetLogLinePayload(payload, logLine);
+    ASSERT_TRUE(ok);
+    ASSERT_STREQ(payload.c_str(), text);
+  }
+
+  {
+    const char* text = "Trougoudou 53535345345353";
+    LOG(WARNING) << text;
+    std::string logLine = testWarningStream.str();
+    testWarningStream.str("");
+    testWarningStream.clear();
+    std::string payload;
+    bool ok = GetLogLinePayload(payload, logLine);
+    ASSERT_TRUE(ok);
+    ASSERT_STREQ(payload.c_str(), text);
+  }
+
+  {
+    const char* text = "Prout 111929";
+    LOG(INFO) << text;
+    std::string logLine = testInfoStream.str();
+    testInfoStream.str("");
+    testInfoStream.clear();
+    std::string payload;
+    bool ok = GetLogLinePayload(payload, logLine);
+    ASSERT_TRUE(ok);
+    ASSERT_STREQ(payload.c_str(), text);
+  }
+}
+
+
+
+
+
+
+
+
+
+