# HG changeset patch # User Alain Mazy # Date 1765364484 -3600 # Node ID 6b9133fa6e8862f454f5e5c7ca03478528cc1df3 # Parent 770d6037b79ffe4ec0f9f21f9eded0d8e2f870f0 logged errors/warnings metrics diff -r 770d6037b79f -r 6b9133fa6e88 NEWS --- a/NEWS Tue Dec 09 14:51:25 2025 +0100 +++ b/NEWS Wed Dec 10 12:01:24 2025 +0100 @@ -4,6 +4,13 @@ * Upgraded dependencies for static builds: - boost 1.89.0 +REST API +-------- + +* Added new metrics in "/tools/metrics-prometheus": + - "orthanc_logged_errors_count" + - "orthanc_logged_warnings_count" + Version 1.12.10 (2025-11-26) ============================ diff -r 770d6037b79f -r 6b9133fa6e88 OrthancFramework/Sources/Logging.cpp --- a/OrthancFramework/Sources/Logging.cpp Tue Dec 09 14:51:25 2025 +0100 +++ b/OrthancFramework/Sources/Logging.cpp Wed Dec 10 12:01:24 2025 +0100 @@ -34,6 +34,7 @@ # include #endif +#include "MetricsRegistry.h" /********************************************************* * Common section @@ -572,7 +573,8 @@ static boost::recursive_mutex threadNamesMutex_; static std::map threadNames_; static bool enableThreadNames_ = true; - +static std::list loggingListeners_; +static boost::mutex loggingListenersMutex_; namespace Orthanc @@ -706,6 +708,18 @@ return threadNames_[threadId]; } + void AddLoggingListener(ILoggingListener* listener) + { + boost::mutex::scoped_lock lock(loggingListenersMutex_); + loggingListeners_.push_back(listener); + } + + void ClearLoggingListeners() + { + boost::mutex::scoped_lock lock(loggingListenersMutex_); + loggingListeners_.clear(); + } + static void GetLinePrefix(std::string& prefix, LogLevel level, const char* pluginName, // when logging in the core but coming from a plugin, pluginName_ is NULL but this argument is != NULL @@ -1048,8 +1062,15 @@ } else if (stream_ != &nullStream_) { - *stream_ << "\n"; + *stream_ << messageStream_.str() << "\n"; stream_->flush(); + + boost::mutex::scoped_lock lock(loggingListenersMutex_); + for (std::list::iterator it = loggingListeners_.begin(); it != loggingListeners_.end(); ++it) + { + (*it)->HandleLog(level_, category_, pluginName_, file_, line_, messageStream_.str()); + } + } } diff -r 770d6037b79f -r 6b9133fa6e88 OrthancFramework/Sources/Logging.h --- a/OrthancFramework/Sources/Logging.h Tue Dec 09 14:51:25 2025 +0100 +++ b/OrthancFramework/Sources/Logging.h Wed Dec 10 12:01:24 2025 +0100 @@ -74,7 +74,20 @@ LogCategory_JOBS = (1 << 5), LogCategory_LUA = (1 << 6) }; - + + class ILoggingListener + { + public: + virtual ~ILoggingListener() {} + + virtual void HandleLog(LogLevel level, + LogCategory category, + const std::string& pluginName, + const char* file, + uint32_t line, + const std::string& message) = 0; + }; + ORTHANC_PUBLIC const char* EnumerationToString(LogLevel level); ORTHANC_PUBLIC LogLevel StringToLogLevel(const char* level); @@ -97,6 +110,10 @@ ORTHANC_PUBLIC bool HasCurrentThreadName(); + ORTHANC_PUBLIC void AddLoggingListener(ILoggingListener* listener); + + ORTHANC_PUBLIC void ClearLoggingListeners(); + ORTHANC_PUBLIC void EnableThreadNames(bool enabled); ORTHANC_PUBLIC void EnableInfoLevel(bool enabled); @@ -270,6 +287,7 @@ LogCategory category_; const char* file_; uint32_t line_; + std::stringstream messageStream_; public: InternalLogger(LogLevel level, @@ -283,7 +301,7 @@ template std::ostream& operator<< (const T& message) { - return (*stream_) << boost::lexical_cast(message); + return messageStream_ << boost::lexical_cast(message); } }; diff -r 770d6037b79f -r 6b9133fa6e88 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Tue Dec 09 14:51:25 2025 +0100 +++ b/OrthancServer/Sources/main.cpp Wed Dec 10 12:01:24 2025 +0100 @@ -1042,6 +1042,37 @@ return restart; } +// This class makes the bridge between the logging and the metrics +// to count the number of logged errors and warnings +class MetricsLoggingListener : public Orthanc::Logging::ILoggingListener +{ + MetricsRegistry& metrics_; +public: + MetricsLoggingListener(MetricsRegistry& metrics) + : metrics_(metrics) + { + } + + virtual ~MetricsLoggingListener() {}; + + virtual void HandleLog(Orthanc::Logging::LogLevel level, + Orthanc::Logging::LogCategory category, + const std::string& pluginName, + const char* file, + uint32_t line, + const std::string& message) ORTHANC_OVERRIDE + { + if (level == Orthanc::Logging::LogLevel_ERROR) + { + metrics_.IncrementIntegerValue("orthanc_logged_errors_count", 1); + } + else if (level == Orthanc::Logging::LogLevel_WARNING) + { + metrics_.IncrementIntegerValue("orthanc_logged_warnings_count", 1); + } + } + +}; static bool StartHttpServer(ServerContext& context, @@ -1282,8 +1313,13 @@ httpServer.Start(); + MetricsLoggingListener loggingMetrics(context.GetMetricsRegistry()); + Logging::AddLoggingListener(&loggingMetrics); + bool restart = WaitForExit(context, restApi); + Logging::ClearLoggingListeners(); + httpServer.Stop(); LOG(WARNING) << " HTTP server has stopped"; diff -r 770d6037b79f -r 6b9133fa6e88 TODO --- a/TODO Tue Dec 09 14:51:25 2025 +0100 +++ b/TODO Wed Dec 10 12:01:24 2025 +0100 @@ -25,6 +25,7 @@ * Toolbox::ComputeMD5() fails on files larger than 4GB * Logging: add more specific information to contextualize the logs. For a DICOM Transfer, that would be nice to include the modality in the context + a study identifier or a job id. + Note that the thread id greatly helps already wrt this topic. * (1) Accept extra DICOM tags dictionaries in the DCMTK format '.dic' (easier to use than declare them in the Orthanc configuration file). Even the standard dictionaries could be overridden by these custom dictionaries.