changeset 4273:0034f855c023

tuning log categories from command-line, and binary compat with orthanc framework 1.7.2
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 03 Nov 2020 12:24:50 +0100
parents 1661544ea94d
children 09ed936fd381
files OrthancFramework/Sources/ChunkedBuffer.cpp OrthancFramework/Sources/ChunkedBuffer.h OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h OrthancFramework/Sources/DicomParsing/DicomModification.cpp OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp OrthancFramework/Sources/FileStorage/FilesystemStorage.h OrthancFramework/Sources/HttpServer/HttpServer.cpp OrthancFramework/Sources/Images/ImageAccessor.cpp OrthancFramework/Sources/Images/ImageAccessor.h OrthancFramework/Sources/Logging.cpp OrthancFramework/Sources/Logging.h OrthancFramework/Sources/PrecompiledHeaders.h OrthancServer/Sources/OrthancInitialization.cpp OrthancServer/Sources/OrthancInitialization.h OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerEnumerations.cpp OrthancServer/Sources/ServerEnumerations.h OrthancServer/Sources/main.cpp OrthancServer/UnitTestsSources/UnitTestsMain.cpp
diffstat 26 files changed, 456 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/ChunkedBuffer.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/ChunkedBuffer.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -83,6 +83,18 @@
   }
 
 
+  ChunkedBuffer::~ChunkedBuffer()
+  {
+    Clear();
+  }
+
+
+  size_t ChunkedBuffer::GetNumBytes() const
+  {
+    return numBytes_ + pendingPos_;
+  }
+  
+
   void ChunkedBuffer::SetPendingBufferSize(size_t size)
   {
     FlushPendingBuffer();
@@ -90,6 +102,12 @@
   }
   
 
+  size_t ChunkedBuffer::GetPendingBufferSize() const
+  {
+    return pendingBuffer_.size();
+  }
+
+  
   void ChunkedBuffer::AddChunk(const void* chunkData,
                                size_t chunkSize)
   {
--- a/OrthancFramework/Sources/ChunkedBuffer.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/ChunkedBuffer.h	Tue Nov 03 12:24:50 2020 +0100
@@ -50,22 +50,13 @@
   public:
     ChunkedBuffer();
 
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
+    ~ChunkedBuffer();
 
-    size_t GetNumBytes() const
-    {
-      return numBytes_ + pendingPos_;
-    }
+    size_t GetNumBytes() const;
 
     void SetPendingBufferSize(size_t size);
 
-    size_t GetPendingBufferSize() const
-    {
-      return pendingBuffer_.size();
-    }
+    size_t GetPendingBufferSize() const;
 
     void AddChunk(const void* chunkData,
                   size_t chunkSize);
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -76,7 +76,7 @@
 
       if (payload.isWorklist)
       {
-        ParsedDicomFile answer(*responseIdentifiers);
+        const ParsedDicomFile answer(*responseIdentifiers);
         payload.answers->Add(answer);
       }
       else
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -180,7 +180,9 @@
                                 bool simplify) const
   {
     DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full);
-    GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0);
+    
+    const ParsedDicomFile& answer = GetAnswer(index);
+    answer.DatasetToJson(target, format, DicomToJsonFlags_None, 0);
   }
 
 
@@ -196,4 +198,12 @@
       target.append(answer);
     }
   }
+
+
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+  void DicomFindAnswers::Add(ParsedDicomFile& dicom)
+  {
+    return Add(const_cast<const ParsedDicomFile&>(dicom));
+  }
+#endif
 }
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Tue Nov 03 12:24:50 2020 +0100
@@ -36,6 +36,11 @@
 
     void AddAnswerInternal(ParsedDicomFile* answer);
 
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+    // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+    void Add(ParsedDicomFile& dicom);
+#endif
+
   public:
     explicit DicomFindAnswers(bool isWorklist);
 
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -328,7 +328,7 @@
     }
 
     std::string original;
-    if (!dicom.GetTagValue(original, *tag))
+    if (!const_cast<const ParsedDicomFile&>(dicom).GetTagValue(original, *tag))
     {
       original = "";
     }
@@ -1017,7 +1017,7 @@
       {
         std::string original;
         std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID);
-        toModify.GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID);
+        const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID);
         RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study);
       }
 
@@ -1025,7 +1025,7 @@
       {
         std::string original;
         std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID);
-        toModify.GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID);
+        const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID);
         RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series);
       }
 
@@ -1033,7 +1033,7 @@
       {
         std::string original;
         std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID);
-        toModify.GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID);
+        const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID);
         RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance);
       }
     }
@@ -1114,7 +1114,7 @@
 
       if (updateReferencedRelationships_)
       {
-        toModify.Apply(visitor);
+        const_cast<const ParsedDicomFile&>(toModify).Apply(visitor);
       }
       else
       {
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -1018,4 +1018,13 @@
     writer.WriteToMemory(result, *image);
   }
 #endif
+
+
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+  ImageAccessor *DicomImageDecoder::Decode(ParsedDicomFile& dataset,
+                                           unsigned int frame)
+  {
+    return Decode(*dataset.GetDcmtkObject().getDataset(), frame);
+  }
+#endif
 }
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Tue Nov 03 12:24:50 2020 +0100
@@ -84,6 +84,12 @@
                                     ImageExtractionMode mode,
                                     bool invert);
 
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+    // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+    static ImageAccessor *Decode(ParsedDicomFile& dataset,
+                                 unsigned int frame);
+#endif
+
   public:
     static bool IsPsmctRle1(DcmDataset& dataset);
 
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -41,7 +41,7 @@
 
     std::string s;
     DicomTransferSyntax a, b;
-    if (!parsed.LookupTransferSyntax(s) ||
+    if (!const_cast<const ParsedDicomFile&>(parsed).LookupTransferSyntax(s) ||
         !FromDcmtkBridge::LookupOrthancTransferSyntax(a, parsed.GetDcmtkObject()) ||
         !LookupTransferSyntax(b, s) ||
         a != b ||
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -1733,4 +1733,42 @@
       return DicomImageDecoder::Decode(*GetDcmtkObjectConst().getDataset(), frame);
     }
   }
+
+
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+  // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+  void ParsedDicomFile::DatasetToJson(Json::Value& target, 
+                                      DicomToJsonFormat format,
+                                      DicomToJsonFlags flags,
+                                      unsigned int maxStringLength)
+  {
+    return const_cast<const ParsedDicomFile&>(*this).DatasetToJson(target, format, flags, maxStringLength);
+  }
+
+  DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
+  {
+    return const_cast<ParsedDicomFile&>(*this).GetDcmtkObject();
+  }
+
+  void ParsedDicomFile::Apply(ITagVisitor& visitor)
+  {
+    const_cast<const ParsedDicomFile&>(*this).Apply(visitor);
+  }
+
+  ParsedDicomFile* ParsedDicomFile::Clone(bool keepSopInstanceUid)
+  {
+    return const_cast<const ParsedDicomFile&>(*this).Clone(keepSopInstanceUid);
+  }
+  
+  bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
+  {
+    return const_cast<const ParsedDicomFile&>(*this).LookupTransferSyntax(result);
+  }
+
+  bool ParsedDicomFile::GetTagValue(std::string& value,
+                                    const DicomTag& tag)
+  {
+    return const_cast<const ParsedDicomFile&>(*this).GetTagValue(value, tag);
+  }
+#endif
 }
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Tue Nov 03 12:24:50 2020 +0100
@@ -101,6 +101,20 @@
 
     explicit ParsedDicomFile(DcmFileFormat* dicom);  // This takes ownership (no clone)
 
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+    // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+    void DatasetToJson(Json::Value& target, 
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength);    
+    DcmFileFormat& GetDcmtkObject() const;
+    void Apply(ITagVisitor& visitor);
+    ParsedDicomFile* Clone(bool keepSopInstanceUid);
+    bool LookupTransferSyntax(std::string& result);
+    bool GetTagValue(std::string& value,
+                     const DicomTag& tag);
+#endif
+
   public:
     explicit ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -260,4 +260,13 @@
   {
     return boost::filesystem::space(root_).available;
   }
+
+
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+  FilesystemStorage::FilesystemStorage(std::string root) :
+    fsyncOnWrite_(false)
+  {
+    Setup(root);
+  }
+#endif
 }
--- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.h	Tue Nov 03 12:24:50 2020 +0100
@@ -55,6 +55,11 @@
 
     void Setup(const std::string& root);
     
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+    // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+    explicit FilesystemStorage(std::string root);
+#endif
+
   public:
     explicit FilesystemStorage(const std::string& root) :
       fsyncOnWrite_(false)
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -1088,7 +1088,7 @@
 
       std::transform(name.begin(), name.end(), name.begin(), ::tolower);
       headers.insert(std::make_pair(name, value));
-      LOG(TRACE) << "HTTP header: [" << name << "]: [" << value << "]";
+      CLOG(TRACE, REST) << "HTTP header: [" << name << "]: [" << value << "]";
     }
 
     if (server.IsHttpCompressionEnabled())
--- a/OrthancFramework/Sources/Images/ImageAccessor.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/Images/ImageAccessor.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -90,7 +90,7 @@
     target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) +
                     " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))");
   }
-
+  
 
   void* ImageAccessor::GetBuffer()
   {
@@ -293,4 +293,17 @@
 
     format_ = format;
   }
+
+
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+  void* ImageAccessor::GetBuffer() const
+  {
+    return const_cast<ImageAccessor&>(*this).GetBuffer();
+  }
+
+  void* ImageAccessor::GetRow(unsigned int y) const
+  {
+    return const_cast<ImageAccessor&>(*this).GetRow(y);
+  }
+#endif
 }
--- a/OrthancFramework/Sources/Images/ImageAccessor.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/Images/ImageAccessor.h	Tue Nov 03 12:24:50 2020 +0100
@@ -22,6 +22,7 @@
 
 #pragma once
 
+#include "../Compatibility.h"
 #include "../Enumerations.h"
 
 #include <string>
@@ -60,6 +61,12 @@
       return reinterpret_cast<T*>(row) [x];
     }
 
+#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
+    // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
+    void* GetBuffer() const;
+    void* GetRow(unsigned int y) const;
+#endif
+
   public:
     ImageAccessor()
     {
--- a/OrthancFramework/Sources/Logging.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/Logging.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -193,32 +193,68 @@
     }
 
 
-    LogCategory StringToCategory(const std::string& category)
+    bool LookupCategory(LogCategory& target,
+                        const std::string& category)
     {
       if (category == "generic")
       {
-        return LogCategory_GENERIC;
+        target = LogCategory_GENERIC;
+        return true;
       }
       else if (category == "plugins")
       {
-        return LogCategory_PLUGINS;
+        target = LogCategory_PLUGINS;
+        return true;
       }
       else if (category == "rest")
       {
-        return LogCategory_REST;
+        target = LogCategory_REST;
+        return true;
       }
       else if (category == "dicom")
       {
-        return LogCategory_DICOM;
+        target = LogCategory_DICOM;
+        return true;
       }
       else if (category == "sqlite")
       {
-        return LogCategory_SQLITE;
+        target = LogCategory_SQLITE;
+        return true;
       }
       else
       {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Unknown log category: " + category);
+        return false;
+      }
+    }
+
+
+    size_t GetCategoriesCount()
+    {
+      return 5;
+    }
+
+
+    const char* GetCategoryName(size_t i)
+    {
+      switch (i)
+      {
+        case 0:
+          return "generic";
+          
+        case 1:
+          return "plugins";
+          
+        case 2:
+          return "rest";
+          
+        case 3:
+          return "dicom";
+          
+        case 4:
+          return "sqlite";
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
     }
   }
@@ -709,20 +745,16 @@
     }
 
 
-    InternalLogger::InternalLogger(LogLevel level,
-                                   LogCategory category,
-                                   const char* file,
-                                   int line) : 
-      lock_(loggingStreamsMutex_, boost::defer_lock_t()),
-      level_(level),
-      stream_(&nullStream_)  // By default, logging to "/dev/null" is simulated
+    void InternalLogger::Setup(LogCategory category,
+                               const char* file,
+                               int line)
     {
       if (pluginContext_ != NULL)
       {
         // We are logging using the Orthanc plugin SDK
 
-        if (level == LogLevel_TRACE ||
-            !IsCategoryEnabled(level, category))
+        if (level_ == LogLevel_TRACE ||
+            !IsCategoryEnabled(level_, category))
         {
           // No trace level in plugins, directly exit as the stream is
           // set to "/dev/null"
@@ -738,7 +770,7 @@
       {
         // We are logging in a standalone application, not inside an Orthanc plugin
 
-        if (!IsCategoryEnabled(level, category))
+        if (!IsCategoryEnabled(level_, category))
         {
           // This logging level is disabled, directly exit as the
           // stream is set to "/dev/null"
@@ -746,7 +778,7 @@
         }
 
         std::string prefix;
-        GetLinePrefix(prefix, level, file, line);
+        GetLinePrefix(prefix, level_, file, line);
 
         {
           // We lock the global mutex. The mutex is locked until the
@@ -760,7 +792,7 @@
             return;
           }
 
-          switch (level)
+          switch (level_)
           {
             case LogLevel_ERROR:
               stream_ = loggingStreamsContext_->error_;
@@ -805,6 +837,29 @@
     }
 
 
+    InternalLogger::InternalLogger(LogLevel level,
+                                   LogCategory category,
+                                   const char* file,
+                                   int line) :
+      lock_(loggingStreamsMutex_, boost::defer_lock_t()),
+      level_(level),
+      stream_(&nullStream_)  // By default, logging to "/dev/null" is simulated
+    {
+      Setup(category, file, line);
+    }
+
+
+    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
+    {
+      Setup(LogCategory_GENERIC, file, line);
+    }
+
+
     InternalLogger::~InternalLogger()
     {
       if (pluginStream_.get() != NULL)
--- a/OrthancFramework/Sources/Logging.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/Logging.h	Tue Nov 03 12:24:50 2020 +0100
@@ -97,7 +97,12 @@
     ORTHANC_PUBLIC bool IsCategoryEnabled(LogLevel level,
                                           LogCategory category);
     
-    ORTHANC_PUBLIC LogCategory StringToCategory(const std::string& category);
+    ORTHANC_PUBLIC bool LookupCategory(LogCategory& target,
+                                       const std::string& category);
+
+    ORTHANC_PUBLIC size_t GetCategoriesCount();
+
+    ORTHANC_PUBLIC const char* GetCategoryName(size_t i);
 
     ORTHANC_PUBLIC void SetTargetFile(const std::string& path);
 
@@ -146,11 +151,11 @@
 #  define VLOG(unused)          ::Orthanc::Logging::NullStream()
 #  define CLOG(level, category) ::Orthanc::Logging::NullStream()
 #else /* ORTHANC_ENABLE_LOGGING == 1 */
-#  define LOG(level)     ::Orthanc::Logging::InternalLogger             \
-  (::Orthanc::Logging::LogLevel_ ## level,                              \
+#  define LOG(level)     ::Orthanc::Logging::InternalLogger     \
+  (::Orthanc::Logging::LogLevel_ ## level,                      \
    ::Orthanc::Logging::LogCategory_GENERIC, __FILE__, __LINE__)
-#  define VLOG(unused)   ::Orthanc::Logging::InternalLogger             \
-  (::Orthanc::Logging::LogLevel_TRACE,                                  \
+#  define VLOG(unused)   ::Orthanc::Logging::InternalLogger     \
+  (::Orthanc::Logging::LogLevel_TRACE,                          \
    ::Orthanc::Logging::LogCategory_GENERIC, __FILE__, __LINE__)
 #  define CLOG(level, category) ::Orthanc::Logging::InternalLogger      \
   (::Orthanc::Logging::LogLevel_ ## level,                              \
@@ -188,6 +193,15 @@
       {
       }
 
+      // For backward binary compatibility with Orthanc Framework <= 1.8.0
+      InternalLogger(LogLevel level,
+                     const char* file  /* ignored */,
+                     int line  /* ignored */) :
+        level_(level),
+        category_(LogCategory_GENERIC)
+      {
+      }
+
       ~InternalLogger();
       
       template <typename T>
@@ -225,12 +239,21 @@
       std::unique_ptr<std::stringstream>  pluginStream_;
       std::ostream*                       stream_;
 
+      void Setup(LogCategory category,
+                 const char* file,
+                 int line);
+
     public:
       InternalLogger(LogLevel level,
                      LogCategory category,
                      const char* file,
                      int line);
 
+      // For backward binary compatibility with Orthanc Framework <= 1.8.0
+      InternalLogger(LogLevel level,
+                     const char* file,
+                     int line);
+
       ~InternalLogger();
       
       template <typename T>
@@ -240,7 +263,6 @@
       }
     };
 
-
     /**
      * Set custom logging streams for the error, warning and info
      * logs. This function may not be called if a log file or folder
--- a/OrthancFramework/Sources/PrecompiledHeaders.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancFramework/Sources/PrecompiledHeaders.h	Tue Nov 03 12:24:50 2020 +0100
@@ -48,8 +48,6 @@
 #include "Enumerations.h"
 #include "Logging.h"
 #include "OrthancException.h"
-#include "OrthancFramework.h"
-#include "Toolbox.h"
 
 #if ORTHANC_ENABLE_DCMTK == 1
 // Headers from DCMTK used in Orthanc headers 
@@ -60,8 +58,6 @@
 #endif
 
 #if ORTHANC_ENABLE_DCMTK_NETWORKING == 1
-#  include "DicomNetworking/DicomServer.h"
-
 // Headers from DCMTK used in Orthanc headers 
 #  include <dcmtk/dcmnet/dimse.h>
 #endif
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -394,4 +394,89 @@
   {
     return CreateFilesystemStorage();
   }  
+
+
+  void SetGlobalVerbosity(Verbosity verbosity)
+  {
+    switch (verbosity)
+    {
+      case Verbosity_Default:
+        Logging::EnableInfoLevel(false);
+        Logging::EnableTraceLevel(false);
+        break;
+
+      case Verbosity_Verbose:
+        Logging::EnableInfoLevel(true);
+        Logging::EnableTraceLevel(false);
+        break;
+
+      case Verbosity_Trace:
+        Logging::EnableInfoLevel(true);
+        Logging::EnableTraceLevel(true);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  Verbosity GetGlobalVerbosity()
+  {
+    if (Logging::IsTraceLevelEnabled())
+    {
+      return Verbosity_Trace;
+    }
+    else if (Logging::IsInfoLevelEnabled())
+    {
+      return Verbosity_Verbose;
+    }
+    else
+    {
+      return Verbosity_Default;
+    }
+  }
+
+  
+  void SetCategoryVerbosity(Logging::LogCategory category,
+                            Verbosity verbosity)
+  {
+    switch (verbosity)
+    {
+      case Verbosity_Default:
+        Logging::SetCategoryEnabled(Logging::LogLevel_INFO, category, false);
+        Logging::SetCategoryEnabled(Logging::LogLevel_TRACE, category, false);
+        break;
+
+      case Verbosity_Verbose:
+        Logging::SetCategoryEnabled(Logging::LogLevel_INFO, category, true);
+        Logging::SetCategoryEnabled(Logging::LogLevel_TRACE, category, false);
+        break;
+
+      case Verbosity_Trace:
+        Logging::SetCategoryEnabled(Logging::LogLevel_INFO, category, true);
+        Logging::SetCategoryEnabled(Logging::LogLevel_TRACE, category, true);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+  
+
+  Verbosity GetCategoryVerbosity(Logging::LogCategory category)
+  {
+    if (Logging::IsCategoryEnabled(Logging::LogLevel_TRACE, category))
+    {
+      return Verbosity_Trace;
+    }
+    else if (Logging::IsCategoryEnabled(Logging::LogLevel_INFO, category))
+    {
+      return Verbosity_Verbose;
+    }
+    else
+    {
+      return Verbosity_Default;
+    }
+  }
 }
--- a/OrthancServer/Sources/OrthancInitialization.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/OrthancInitialization.h	Tue Nov 03 12:24:50 2020 +0100
@@ -34,6 +34,7 @@
 #pragma once
 
 #include "../../OrthancFramework/Sources/FileStorage/IStorageArea.h"
+#include "../../OrthancFramework/Sources/Logging.h"
 #include "Database/IDatabaseWrapper.h"
 
 namespace Orthanc
@@ -45,4 +46,13 @@
   IDatabaseWrapper* CreateDatabaseWrapper();
 
   IStorageArea* CreateStorageArea();
+
+  void SetGlobalVerbosity(Verbosity verbosity);
+
+  Verbosity GetGlobalVerbosity();
+
+  void SetCategoryVerbosity(Logging::LogCategory category,
+                            Verbosity verbosity);
+
+  Verbosity GetCategoryVerbosity(Logging::LogCategory category);
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -39,14 +39,10 @@
 #include "../../Plugins/Engine/OrthancPlugins.h"
 #include "../../Plugins/Engine/PluginsManager.h"
 #include "../OrthancConfiguration.h"
+#include "../OrthancInitialization.h"
 #include "../ServerContext.h"
 
 
-static const char* LOG_LEVEL_DEFAULT = "default";
-static const char* LOG_LEVEL_VERBOSE = "verbose";
-static const char* LOG_LEVEL_TRACE = "trace";
-
-
 namespace Orthanc
 {
   // System information -------------------------------------------------------
@@ -497,21 +493,7 @@
 
   static void GetLogLevel(RestApiGetCall& call)
   {
-    std::string s;
-    
-    if (Logging::IsTraceLevelEnabled())
-    {
-      s = LOG_LEVEL_TRACE;
-    }
-    else if (Logging::IsInfoLevelEnabled())
-    {
-      s = LOG_LEVEL_VERBOSE;
-    }
-    else
-    {
-      s = LOG_LEVEL_DEFAULT;
-    }
-    
+    const std::string s = EnumerationToString(GetGlobalVerbosity());
     call.GetOutput().AnswerBuffer(s, MimeType_PlainText);
   }
 
@@ -521,30 +503,8 @@
     std::string body;
     call.BodyToString(body);
 
-    if (body == LOG_LEVEL_DEFAULT)
-    {
-      Logging::EnableInfoLevel(false);
-      Logging::EnableTraceLevel(false);
-    }
-    else if (body == LOG_LEVEL_VERBOSE)
-    {
-      Logging::EnableInfoLevel(true);
-      Logging::EnableTraceLevel(false);
-    }
-    else if (body == LOG_LEVEL_TRACE)
-    {
-      Logging::EnableInfoLevel(true);
-      Logging::EnableTraceLevel(true);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange,
-                             "The log level must be one of the following values: \"" +
-                             std::string(LOG_LEVEL_DEFAULT) + "\", \"" +
-                             std::string(LOG_LEVEL_VERBOSE) + "\", of \"" +
-                             std::string(LOG_LEVEL_TRACE) + "\"");
-    }
-
+    SetGlobalVerbosity(StringToVerbosity(body));
+    
     // Success
     LOG(WARNING) << "REST API call has switched the log level to: " << body;
     call.GetOutput().AnswerBuffer("", MimeType_PlainText);
--- a/OrthancServer/Sources/ServerEnumerations.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -237,6 +237,28 @@
                              "should be \"After\", \"Before\" or \"Disabled\": " + value);
     }    
   }
+
+
+  Verbosity StringToVerbosity(const std::string& str)
+  {
+    if (str == "default")
+    {
+      return Verbosity_Default;
+    }
+    else if (str == "verbose")
+    {
+      return Verbosity_Verbose;
+    }
+    else if (str == "trace")
+    {
+      return Verbosity_Trace;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Verbosity can be \"default\", \"verbose\" or \"trace\": " + str);
+    }    
+  }
   
 
   std::string GetBasePath(ResourceType type,
@@ -367,6 +389,25 @@
     }
   }
 
+
+  const char* EnumerationToString(Verbosity verbosity)
+  {
+    switch (verbosity)
+    {
+      case Verbosity_Default:
+        return "default";
+        
+      case Verbosity_Verbose:
+        return "verbose";
+        
+      case Verbosity_Trace:
+        return "trace";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }    
+
   
   bool IsUserMetadata(MetadataType metadata)
   {
--- a/OrthancServer/Sources/ServerEnumerations.h	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.h	Tue Nov 03 12:24:50 2020 +0100
@@ -40,6 +40,13 @@
 
 namespace Orthanc
 {
+  enum Verbosity
+  {
+    Verbosity_Default,
+    Verbosity_Verbose,
+    Verbosity_Trace
+  };
+  
   enum SeriesStatus
   {
     SeriesStatus_Complete,
@@ -194,6 +201,8 @@
 
   BuiltinDecoderTranscoderOrder StringToBuiltinDecoderTranscoderOrder(const std::string& str);
 
+  Verbosity StringToVerbosity(const std::string& str);
+
   std::string EnumerationToString(FileContentType type);
 
   std::string GetFileContentMime(FileContentType type);
@@ -207,5 +216,7 @@
 
   const char* EnumerationToString(ChangeType type);
 
+  const char* EnumerationToString(Verbosity verbosity);
+
   bool IsUserMetadata(MetadataType type);
 }
--- a/OrthancServer/Sources/main.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/Sources/main.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -642,6 +642,19 @@
     << "\t\t\tthe last execution of Orthanc" << std::endl
     << "  --version\t\toutput version information and exit" << std::endl
     << std::endl
+    << "Fine-tuning of log categories:" << std::endl;
+
+  for (size_t i = 0; i < Logging::GetCategoriesCount(); i++)
+  {
+    const std::string name = Logging::GetCategoryName(i);
+    std::cout << "  --verbose-" << name
+              << "\tbe verbose in logs of category \"" << name << "\"" << std::endl;
+    std::cout << "  --trace-" << name
+              << "\tuse highest verbosity for logs of category \"" << name << "\"" << std::endl;
+  }
+  
+  std::cout
+    << std::endl
     << "Exit status:" << std::endl
     << "   0 if success," << std::endl
 #if defined(_WIN32)
@@ -1495,14 +1508,18 @@
 }
 
 
-static void SetLoggingCategories(Logging::LogLevel level,
-                                 const std::string& lst)
+static bool SetCategoryVerbosity(const Verbosity verbosity,
+                                 const std::string& category)
 {
-  std::vector<std::string> categories;
-  Toolbox::TokenizeString(categories, lst, ':');
-  for (size_t i = 0; i < categories.size(); i++)
+  Logging::LogCategory c;
+  if (LookupCategory(c, category))
   {
-    Logging::SetCategoryEnabled(level, Logging::StringToCategory(categories[i]), true);
+    SetCategoryVerbosity(c, verbosity);
+    return true;
+  }
+  else
+  {
+    return false;
   }
 }
 
@@ -1569,19 +1586,21 @@
     }
     else if (argument == "--verbose")
     {
-      Logging::EnableInfoLevel(true);
+      SetGlobalVerbosity(Verbosity_Verbose);
     }
     else if (argument == "--trace")
     {
-      Logging::EnableTraceLevel(true);
+      SetGlobalVerbosity(Verbosity_Trace);
     }
-    else if (boost::starts_with(argument, "--verbose="))  // New in Orthanc 1.8.1
+    else if (boost::starts_with(argument, "--verbose-") &&
+             SetCategoryVerbosity(Verbosity_Verbose, argument.substr(10)))
     {
-      SetLoggingCategories(Logging::LogLevel_INFO, argument.substr(10));
+      // New in Orthanc 1.8.1
     }
-    else if (boost::starts_with(argument, "--trace="))  // New in Orthanc 1.8.1
+    else if (boost::starts_with(argument, "--trace-") &&
+             SetCategoryVerbosity(Verbosity_Trace, argument.substr(8)))
     {
-      SetLoggingCategories(Logging::LogLevel_TRACE, argument.substr(8));
+      // New in Orthanc 1.8.1
     }
     else if (boost::starts_with(argument, "--logdir="))
     {
--- a/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Tue Nov 03 07:19:33 2020 +0100
+++ b/OrthancServer/UnitTestsSources/UnitTestsMain.cpp	Tue Nov 03 12:24:50 2020 +0100
@@ -126,6 +126,29 @@
   ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("Dcm4Chee")));
   ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("SyngoVia")));
   ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("AgfaImpax")));
+
+  ASSERT_STREQ("default", EnumerationToString(StringToVerbosity("default")));
+  ASSERT_STREQ("verbose", EnumerationToString(StringToVerbosity("verbose")));
+  ASSERT_STREQ("trace", EnumerationToString(StringToVerbosity("trace")));
+  ASSERT_THROW(StringToVerbosity("nope"), OrthancException);
+
+  Logging::LogCategory c;
+  ASSERT_TRUE(Logging::LookupCategory(c, "generic"));  ASSERT_EQ(Logging::LogCategory_GENERIC, c);
+  ASSERT_TRUE(Logging::LookupCategory(c, "plugins"));  ASSERT_EQ(Logging::LogCategory_PLUGINS, c);
+  ASSERT_TRUE(Logging::LookupCategory(c, "rest"));     ASSERT_EQ(Logging::LogCategory_REST, c);
+  ASSERT_TRUE(Logging::LookupCategory(c, "sqlite"));   ASSERT_EQ(Logging::LogCategory_SQLITE, c);
+  ASSERT_TRUE(Logging::LookupCategory(c, "dicom"));    ASSERT_EQ(Logging::LogCategory_DICOM, c);
+  ASSERT_FALSE(Logging::LookupCategory(c, "nope"));
+
+  ASSERT_EQ(5u, Logging::GetCategoriesCount());
+
+  for (size_t i = 0; i < Logging::GetCategoriesCount(); i++)
+  {
+    Logging::LogCategory c;
+    ASSERT_TRUE(Logging::LookupCategory(c, Logging::GetCategoryName(i)));
+  }
+
+  ASSERT_THROW(Logging::GetCategoryName(Logging::GetCategoriesCount()), OrthancException);
 }
 
 
@@ -509,7 +532,7 @@
 {
   Logging::Initialize();
   Toolbox::InitializeGlobalLocale(NULL);
-  Logging::EnableInfoLevel(true);
+  SetGlobalVerbosity(Verbosity_Verbose);
   Toolbox::DetectEndianness();
   SystemToolbox::MakeDirectory("UnitTestsResults");
   OrthancInitialize();