Mercurial > hg > orthanc
view OrthancFramework/Sources/Logging.cpp @ 4119:bf7b9edf6b81 framework-lgpl
re-licensing the OrthancFramework to LGPL, in order to license Stone of Orthanc under LGPL
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 19:17:56 +0200 |
parents | d25f4c0fa160 |
children | 0ae2ca210077 |
line wrap: on
line source
/** * 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 Lesser General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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