/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, 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
* .
**/
#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
// Must be the first to be sure to use the Orthanc framework shared library
# include
#endif
#include
#include "../Sources/Logging.h"
#include "../Sources/OrthancException.h"
#include
#include
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?\n"
-->
"Foo bar"
If the log line cannot be matched, the function returns false.
*/
#define EOLSTRING "\n"
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} "
".{16} [a-zA-Z\\.\\-_]+:[0-9]+\\] (.*)" EOLSTRING "$";
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()
{
}
~LoggingMementoScope()
{
Orthanc::Logging::Reset();
}
};
/**
* std::streambuf subclass used in FunctionCallingStream
**/
template
class FuncStreamBuf : public std::stringbuf
{
public:
explicit 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_;
};
}
#if ORTHANC_ENABLE_LOGGING_STDIO == 0
TEST(FuncStreamBuf, BasicTest)
{
LoggingMementoScope loggingConfiguration;
Orthanc::Logging::EnableTraceLevel(true);
typedef void(*LoggingFunctionFunc)(const char*);
FuncStreamBuf errorStreamBuf(TestError);
std::ostream errorStream(&errorStreamBuf);
FuncStreamBuf warningStreamBuf(TestWarning);
std::ostream warningStream(&warningStreamBuf);
FuncStreamBuf infoStreamBuf(TestInfo);
std::ostream infoStream(&infoStreamBuf);
Orthanc::Logging::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_TRUE(ok);
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_TRUE(ok);
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_TRUE(ok);
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_TRUE(ok);
ASSERT_STREQ(payload.c_str(), text);
}
Orthanc::Logging::EnableTraceLevel(false); // Back to normal
}
#endif
TEST(Logging, Categories)
{
using namespace Orthanc::Logging;
// Unit tests are running in "--verbose" mode (not "--trace")
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_SQLITE));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_SQLITE));
// Cannot modify categories for ERROR and WARNING
ASSERT_THROW(SetCategoryEnabled(LogLevel_ERROR, LogCategory_GENERIC, true),
Orthanc::OrthancException);
ASSERT_THROW(SetCategoryEnabled(LogLevel_WARNING, LogCategory_GENERIC, false),
Orthanc::OrthancException);
EnableInfoLevel(false);
EnableTraceLevel(false);
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_FALSE(IsInfoLevelEnabled());
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_SQLITE));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_SQLITE));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_SQLITE));
// Test the "category" setters at INFO level
SetCategoryEnabled(LogLevel_INFO, LogCategory_DICOM, true);
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled()); // At least one category is verbose
SetCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC, true);
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
SetCategoryEnabled(LogLevel_INFO, LogCategory_DICOM, false);
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled()); // "GENERIC" is still verbose
SetCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC, false);
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_FALSE(IsInfoLevelEnabled());
// Test the "category" setters at TRACE level
SetCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM, true);
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_TRUE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
SetCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC, true);
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_TRUE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
SetCategoryEnabled(LogLevel_INFO, LogCategory_DICOM, false);
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_TRUE(IsTraceLevelEnabled()); // "GENERIC" is still at trace level
ASSERT_TRUE(IsInfoLevelEnabled());
SetCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC, false);
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
SetCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC, false);
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_FALSE(IsInfoLevelEnabled());
// Test the "macro" setters
EnableInfoLevel(true);
EnableTraceLevel(false);
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_SQLITE));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_SQLITE));
EnableInfoLevel(false);
EnableTraceLevel(true); // "--trace" implies "--verbose"
ASSERT_TRUE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_ERROR, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_WARNING, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_SQLITE));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_GENERIC));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_DICOM));
ASSERT_TRUE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_SQLITE));
// Back to normal
EnableInfoLevel(true);
EnableTraceLevel(false);
ASSERT_FALSE(IsTraceLevelEnabled());
ASSERT_TRUE(IsInfoLevelEnabled());
ASSERT_TRUE(IsCategoryEnabled(LogLevel_INFO, LogCategory_SQLITE));
ASSERT_FALSE(IsCategoryEnabled(LogLevel_TRACE, LogCategory_SQLITE));
}
TEST(Logging, Enumerations)
{
using namespace Orthanc;
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, "http")); ASSERT_EQ(Logging::LogCategory_HTTP, 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_TRUE(Logging::LookupCategory(c, "jobs")); ASSERT_EQ(Logging::LogCategory_JOBS, c);
ASSERT_TRUE(Logging::LookupCategory(c, "lua")); ASSERT_EQ(Logging::LogCategory_LUA, c);
ASSERT_FALSE(Logging::LookupCategory(c, "nope"));
ASSERT_EQ(7u, Logging::GetCategoriesCount());
std::set s;
for (size_t i = 0; i < Logging::GetCategoriesCount(); i++)
{
ASSERT_TRUE(Logging::LookupCategory(c, Logging::GetCategoryName(i)));
s.insert(Logging::GetCategoryName(i));
}
ASSERT_EQ(7u, s.size());
ASSERT_TRUE(s.find("generic") != s.end());
ASSERT_TRUE(s.find("plugins") != s.end());
ASSERT_TRUE(s.find("http") != s.end());
ASSERT_TRUE(s.find("sqlite") != s.end());
ASSERT_TRUE(s.find("dicom") != s.end());
ASSERT_TRUE(s.find("lua") != s.end());
ASSERT_TRUE(s.find("jobs") != s.end());
ASSERT_THROW(Logging::GetCategoryName(Logging::GetCategoriesCount()), OrthancException);
ASSERT_STREQ("generic", Logging::GetCategoryName(Logging::LogCategory_GENERIC));
ASSERT_STREQ("plugins", Logging::GetCategoryName(Logging::LogCategory_PLUGINS));
ASSERT_STREQ("http", Logging::GetCategoryName(Logging::LogCategory_HTTP));
ASSERT_STREQ("sqlite", Logging::GetCategoryName(Logging::LogCategory_SQLITE));
ASSERT_STREQ("dicom", Logging::GetCategoryName(Logging::LogCategory_DICOM));
ASSERT_STREQ("lua", Logging::GetCategoryName(Logging::LogCategory_LUA));
ASSERT_STREQ("jobs", Logging::GetCategoryName(Logging::LogCategory_JOBS));
}