changeset 3353:54cdad5a7228 emscripten-logging

Added a public function that will use emscripten-specific logging functions when using its SDK + scaffolding work. Build and UT OK on msvc15 x64. Not actually tested under *nix or emscripten yet
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 23 Apr 2019 21:40:57 +0200
parents ba051f674f4b
children eb18269de57f
files CMakeLists.txt Core/Logging.cpp Core/Logging.h UnitTestsSources/LoggingTests.cpp
diffstat 4 files changed, 405 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Apr 23 08:48:49 2019 +0200
+++ b/CMakeLists.txt	Tue Apr 23 21:40:57 2019 +0200
@@ -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 Apr 23 08:48:49 2019 +0200
+++ b/Core/Logging.cpp	Tue Apr 23 21:40:57 2019 +0200
@@ -253,6 +253,19 @@
     {
     }
   };
+
+  struct LoggingMementoImpl
+  {
+    bool valid_;
+    bool infoEnabled_;
+    bool traceEnabled_;
+    std::string  targetFile_;
+    std::string  targetFolder_;
+
+    std::ostream* error_;
+    std::ostream* warning_;
+    std::ostream* info_;
+  };
 }
 
 
@@ -370,6 +383,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_);
@@ -597,7 +663,84 @@
         loggingContext_->file_->flush();
       }
     }
+
+
+ 
+
+    /*
+    struct FunctionCallingStream : std::ostream, std::streambuf
+    {
+      template<typename T>
+      FunctionCallingStream(T func) : std::ostream(this), func_(func) {}
+
+      int overflow(int c)
+      {
+        if (c != '\n')
+        {
+          currentLine_
+        }
+        else
+        {
+          func_(currentLine_.str().c_str());
+          currentLine_.str("");
+          currentLine_.clear("");
+        }
+        return 0;
+      }
+
+    private:
+      std::stringstream currentLine_;
+    };
+    */
+
+    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 Apr 23 08:48:49 2019 +0200
+++ b/Core/Logging.h	Tue Apr 23 21:40:57 2019 +0200
@@ -81,6 +81,14 @@
 
     void EnableTraceLevel(bool enabled);
 
+#ifdef __EMSCRIPTEN__
+    // calling this function 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()
+    void EnableEmscriptenLogging();
+#endif
+
     void SetTargetFile(const std::string& path);
 
     void SetTargetFolder(const std::string& path);
@@ -188,6 +196,78 @@
         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);
+
+    /**
+      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_;
+    };
+
+    /**
+      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);
   }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/LoggingTests.cpp	Tue Apr 23 21:40:57 2019 +0200
@@ -0,0 +1,181 @@
+/**
+ * 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"
+
+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.
+*/
+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]+\\] (.*)\r\n$";
+  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_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_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_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_STREQ(payload.c_str(), text);
+  }
+}
+
+
+
+
+
+
+
+
+
+