Mercurial > hg > orthanc
diff OrthancFramework/Sources/Logging.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Core/Logging.cpp@05a363186da6 |
children | bf7b9edf6b81 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/Logging.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,817 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 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 "PrecompiledHeaders.h" +#include "Logging.h" + +#include "OrthancException.h" + + +namespace Orthanc +{ + namespace Logging + { + const char* EnumerationToString(LogLevel level) + { + switch (level) + { + case LogLevel_ERROR: + return "ERROR"; + + case LogLevel_WARNING: + return "WARNING"; + + case LogLevel_INFO: + return "INFO"; + + case LogLevel_TRACE: + return "TRACE"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + LogLevel StringToLogLevel(const char *level) + { + if (strcmp(level, "ERROR") == 0) + { + return LogLevel_ERROR; + } + else if (strcmp(level, "WARNING") == 0) + { + return LogLevel_WARNING; + } + else if (strcmp(level, "INFO") == 0) + { + return LogLevel_INFO; + } + else if (strcmp(level, "TRACE") == 0) + { + return LogLevel_TRACE; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + } +} + + +#if ORTHANC_ENABLE_LOGGING != 1 + +namespace Orthanc +{ + namespace Logging + { + void InitializePluginContext(void* pluginContext) + { + } + + void Initialize() + { + } + + void Finalize() + { + } + + void Reset() + { + } + + void Flush() + { + } + + void EnableInfoLevel(bool enabled) + { + } + + void EnableTraceLevel(bool enabled) + { + } + + bool IsTraceLevelEnabled() + { + return false; + } + + bool IsInfoLevelEnabled() + { + return false; + } + + void SetTargetFile(const std::string& path) + { + } + + void SetTargetFolder(const std::string& path) + { + } + } +} + + +#elif ORTHANC_ENABLE_LOGGING_STDIO == 1 + +/********************************************************* + * 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> + +#ifdef __EMSCRIPTEN__ +# include <emscripten/html5.h> +#endif + +namespace Orthanc +{ + namespace Logging + { + static bool infoEnabled_ = false; + static bool traceEnabled_ = false; + +#ifdef __EMSCRIPTEN__ + static void ErrorLogFunc(const char* msg) + { + emscripten_console_error(msg); + } + + static void WarningLogFunc(const char* msg) + { + emscripten_console_warn(msg); + } + + static void InfoLogFunc(const char* msg) + { + emscripten_console_log(msg); + } + + static void TraceLogFunc(const char* msg) + { + emscripten_console_log(msg); + } +#else /* __EMSCRIPTEN__ not #defined */ + static void ErrorLogFunc(const char* msg) + { + fprintf(stderr, "E: %s\n", msg); + } + + static void WarningLogFunc(const char*) + { + fprintf(stdout, "W: %s\n", msg); + } + + static void InfoLogFunc(const char*) + { + fprintf(stdout, "I: %s\n", msg); + } + + static void TraceLogFunc(const char*) + { + fprintf(stdout, "T: %s\n", msg); + } +#endif /* __EMSCRIPTEN__ */ + + + InternalLogger::~InternalLogger() + { + std::string message = messageStream_.str(); + + switch (level_) + { + case LogLevel_ERROR: + ErrorLogFunc(message.c_str()); + break; + + case LogLevel_WARNING: + WarningLogFunc(message.c_str()); + break; + + case LogLevel_INFO: + if (infoEnabled_) + { + InfoLogFunc(message.c_str()); + // TODO: stone_console_info(message_.c_str()); + } + break; + + case LogLevel_TRACE: + if (traceEnabled_) + { + TraceLogFunc(message.c_str()); + } + break; + + default: + { + std::stringstream ss; + ss << "Unknown log level (" << level_ << ") for message: " << message; + std::string s = ss.str(); + ErrorLogFunc(s.c_str()); + } + } + } + + void InitializePluginContext(void* pluginContext) + { + } + + void Initialize() + { + } + + void Finalize() + { + } + + void Reset() + { + } + + void Flush() + { + } + + void EnableInfoLevel(bool enabled) + { + infoEnabled_ = enabled; + + if (!enabled) + { + // Also disable the "TRACE" level when info-level debugging is disabled + traceEnabled_ = false; + } + } + + bool IsInfoLevelEnabled() + { + return infoEnabled_; + } + + void EnableTraceLevel(bool enabled) + { + traceEnabled_ = enabled; + } + + bool IsTraceLevelEnabled() + { + return traceEnabled_; + } + + void SetTargetFile(const std::string& path) + { + } + + void SetTargetFolder(const std::string& path) + { + } + } +} + + +#else + +/********************************************************* + * Logger compatible with the Orthanc plugin SDK, or that + * mimics behavior from Google Log. + *********************************************************/ + +#include <cassert> + +namespace +{ + /** + * This is minimal implementation of the context for an Orthanc + * plugin, limited to the logging facilities, and that is binary + * compatible with the definitions of "OrthancCPlugin.h" + **/ + typedef enum + { + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + void (*Free) (void* buffer); + int32_t (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; +} + + +#include "Enumerations.h" +#include "SystemToolbox.h" + +#include <fstream> +#include <boost/filesystem.hpp> +#include <boost/thread.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + + +namespace +{ + struct LoggingStreamsContext + { + std::string targetFile_; + std::string targetFolder_; + + std::ostream* error_; + std::ostream* warning_; + std::ostream* info_; + + std::unique_ptr<std::ofstream> file_; + + LoggingStreamsContext() : + error_(&std::cerr), + warning_(&std::cerr), + info_(&std::cerr) + { + } + }; +} + + + +static std::unique_ptr<LoggingStreamsContext> loggingStreamsContext_; +static boost::mutex loggingStreamsMutex_; +static Orthanc::Logging::NullStream nullStream_; +static OrthancPluginContext* pluginContext_ = NULL; +static bool infoEnabled_ = false; +static bool traceEnabled_ = false; + + +namespace Orthanc +{ + namespace Logging + { + static void GetLogPath(boost::filesystem::path& log, + boost::filesystem::path& link, + const std::string& suffix, + const std::string& directory) + { + /** + From Google Log documentation: + + Unless otherwise specified, logs will be written to the filename + "<program name>.<hostname>.<user name>.log<suffix>.", + followed by the date, time, and pid (you can't prevent the date, + time, and pid from being in the filename). + + In this implementation : "hostname" and "username" are not used + **/ + + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + boost::filesystem::path root(directory); + boost::filesystem::path exe(SystemToolbox::GetPathToExecutable()); + + if (!boost::filesystem::exists(root) || + !boost::filesystem::is_directory(root)) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + + char date[64]; + sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d", + static_cast<int>(now.date().year()), + now.date().month().as_number(), + now.date().day().as_number(), + static_cast<int>(now.time_of_day().hours()), + static_cast<int>(now.time_of_day().minutes()), + static_cast<int>(now.time_of_day().seconds()), + SystemToolbox::GetProcessId()); + + std::string programName = exe.filename().replace_extension("").string(); + + log = (root / (programName + ".log" + suffix + "." + std::string(date))); + link = (root / (programName + ".log" + suffix)); + } + + + static void PrepareLogFolder(std::unique_ptr<std::ofstream>& file, + const std::string& suffix, + const std::string& directory) + { + boost::filesystem::path log, link; + GetLogPath(log, link, suffix, directory); + +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) + boost::filesystem::remove(link); + boost::filesystem::create_symlink(log.filename(), link); +#endif + + file.reset(new std::ofstream(log.string().c_str())); + } + + + // "loggingStreamsMutex_" must be locked + static void CheckFile(std::unique_ptr<std::ofstream>& f) + { + if (loggingStreamsContext_->file_.get() == NULL || + !loggingStreamsContext_->file_->is_open()) + { + throw OrthancException(ErrorCode_CannotWriteFile); + } + } + + + static void GetLinePrefix(std::string& prefix, + LogLevel level, + const char* file, + int line) + { + boost::filesystem::path path(file); + boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration duration = now.time_of_day(); + + /** + From Google Log documentation: + + "Log lines have this form: + + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... + + where the fields are defined as follows: + + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) + file The file name + line The line number + msg The user-supplied message" + + In this implementation, "threadid" is not printed. + **/ + + char c; + switch (level) + { + case LogLevel_ERROR: + c = 'E'; + break; + + case LogLevel_WARNING: + c = 'W'; + break; + + case LogLevel_INFO: + c = 'I'; + break; + + case LogLevel_TRACE: + c = 'T'; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + char date[64]; + sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", + c, + now.date().month().as_number(), + now.date().day().as_number(), + static_cast<int>(duration.hours()), + static_cast<int>(duration.minutes()), + static_cast<int>(duration.seconds()), + static_cast<int>(duration.fractional_seconds())); + + prefix = (std::string(date) + path.filename().string() + ":" + + boost::lexical_cast<std::string>(line) + "] "); + } + + + void InitializePluginContext(void* pluginContext) + { + assert(sizeof(_OrthancPluginService) == sizeof(int32_t)); + + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + loggingStreamsContext_.reset(NULL); + pluginContext_ = reinterpret_cast<OrthancPluginContext*>(pluginContext); + } + + + void Initialize() + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + + if (loggingStreamsContext_.get() == NULL) + { + loggingStreamsContext_.reset(new LoggingStreamsContext); + } + } + + void Finalize() + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + loggingStreamsContext_.reset(NULL); + } + + void Reset() + { + // Recover the old logging context + std::unique_ptr<LoggingStreamsContext> old; + + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + if (loggingStreamsContext_.get() == NULL) + { + return; + } + else + { +#if __cplusplus < 201103L + old.reset(loggingStreamsContext_.release()); +#else + old = std::move(loggingStreamsContext_); +#endif + + // Create a new logging context, + loggingStreamsContext_.reset(new LoggingStreamsContext); + } + } + + if (!old->targetFolder_.empty()) + { + SetTargetFolder(old->targetFolder_); + } + else if (!old->targetFile_.empty()) + { + SetTargetFile(old->targetFile_); + } + } + + + void EnableInfoLevel(bool enabled) + { + infoEnabled_ = enabled; + + if (!enabled) + { + // Also disable the "TRACE" level when info-level debugging is disabled + traceEnabled_ = false; + } + } + + bool IsInfoLevelEnabled() + { + return infoEnabled_; + } + + void EnableTraceLevel(bool enabled) + { + traceEnabled_ = enabled; + + if (enabled) + { + // Also enable the "INFO" level when trace-level debugging is enabled + infoEnabled_ = true; + } + } + + bool IsTraceLevelEnabled() + { + return traceEnabled_; + } + + + void SetTargetFolder(const std::string& path) + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + if (loggingStreamsContext_.get() != NULL) + { + PrepareLogFolder(loggingStreamsContext_->file_, "" /* no suffix */, path); + CheckFile(loggingStreamsContext_->file_); + + loggingStreamsContext_->targetFile_.clear(); + loggingStreamsContext_->targetFolder_ = path; + loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get(); + loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get(); + loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get(); + } + } + + + void SetTargetFile(const std::string& path) + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + + if (loggingStreamsContext_.get() != NULL) + { + loggingStreamsContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app)); + CheckFile(loggingStreamsContext_->file_); + + loggingStreamsContext_->targetFile_ = path; + loggingStreamsContext_->targetFolder_.clear(); + loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get(); + loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get(); + loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get(); + } + } + + + InternalLogger::InternalLogger(LogLevel level, + const char* file, + int line) : + lock_(loggingStreamsMutex_, boost::defer_lock_t()), + level_(level), + stream_(&nullStream_) // By default, logging to "/dev/null" is simulated + { + if (pluginContext_ != NULL) + { + // We are logging using the Orthanc plugin SDK + + if (level == LogLevel_TRACE) + { + // No trace level in plugins, directly exit as the stream is + // set to "/dev/null" + return; + } + else + { + pluginStream_.reset(new std::stringstream); + stream_ = pluginStream_.get(); + } + } + else + { + // We are logging in a standalone application, not inside an Orthanc plugin + + if ((level == LogLevel_INFO && !infoEnabled_) || + (level == LogLevel_TRACE && !traceEnabled_)) + { + // This logging level is disabled, directly exit as the + // stream is set to "/dev/null" + return; + } + + std::string prefix; + GetLinePrefix(prefix, level, file, line); + + { + // We lock the global mutex. The mutex is locked until the + // destructor is called: No change in the output can be done. + lock_.lock(); + + if (loggingStreamsContext_.get() == NULL) + { + fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); + lock_.unlock(); + return; + } + + switch (level) + { + case LogLevel_ERROR: + stream_ = loggingStreamsContext_->error_; + break; + + case LogLevel_WARNING: + stream_ = loggingStreamsContext_->warning_; + break; + + case LogLevel_INFO: + case LogLevel_TRACE: + stream_ = loggingStreamsContext_->info_; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (stream_ == &nullStream_) + { + // The logging is disabled for this level, we can release + // the global mutex. + lock_.unlock(); + } + else + { + try + { + (*stream_) << prefix; + } + catch (...) + { + // Something is going really wrong, probably running out of + // memory. Fallback to a degraded mode. + stream_ = loggingStreamsContext_->error_; + (*stream_) << "E???? ??:??:??.?????? ] "; + } + } + } + } + } + + + InternalLogger::~InternalLogger() + { + if (pluginStream_.get() != NULL) + { + // We are logging through the Orthanc SDK + + std::string message = pluginStream_->str(); + + if (pluginContext_ != NULL) + { + switch (level_) + { + case LogLevel_ERROR: + pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogError, message.c_str()); + break; + + case LogLevel_WARNING: + pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogWarning, message.c_str()); + break; + + case LogLevel_INFO: + pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogInfo, message.c_str()); + break; + + default: + break; + } + } + } + else if (stream_ != &nullStream_) + { + *stream_ << "\n"; + stream_->flush(); + } + } + + + void Flush() + { + if (pluginContext_ != NULL) + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + + if (loggingStreamsContext_.get() != NULL && + loggingStreamsContext_->file_.get() != NULL) + { + loggingStreamsContext_->file_->flush(); + } + } + } + + + void SetErrorWarnInfoLoggingStreams(std::ostream& errorStream, + std::ostream& warningStream, + std::ostream& infoStream) + { + boost::mutex::scoped_lock lock(loggingStreamsMutex_); + + loggingStreamsContext_.reset(new LoggingStreamsContext); + loggingStreamsContext_->error_ = &errorStream; + loggingStreamsContext_->warning_ = &warningStream; + loggingStreamsContext_->info_ = &infoStream; + } + } +} + + +#endif // ORTHANC_ENABLE_LOGGING