Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ endif ()
# developers.
add_compile_definitions(BOOST_ASIO_DISABLE_AWAITABLE_FRAME_RECYCLING)

# Disable nlohmann implicit conversions (e.g. json → std::string) globally so
# all TUs, including those compiled via PCH, see the same flag value.
add_compile_definitions(JSON_USE_IMPLICIT_CONVERSIONS=0)

# By default, QLever uses precompiled headers for widely used modules. However when heavily modifying the headers that
# contribute to the precompiled headers, then it might be beneficial to deactivate precompiled headers.
option(USE_PRECOMPILED_HEADERS "Use precompiled headers to reduce compile times" ON)
Expand Down Expand Up @@ -471,11 +475,9 @@ set(LOG_LEVEL_DEBUG DEBUG)
set(LOG_LEVEL_TIMING TIMING)
set(LOG_LEVEL_TRACE TRACE)

if (CMAKE_BUILD_TYPE MATCHES DEBUG)
set(LOGLEVEL DEBUG CACHE STRING "The loglevel")
else ()
set(LOGLEVEL INFO CACHE STRING "The loglevel")
endif ()
set(LOGLEVEL DEBUG CACHE STRING
"The maximum log level QLever is compiled with. Less verbose log levels can be chosen at runtime via the 'log-level' runtime parameter."
)
set_property(CACHE LOGLEVEL PROPERTY STRINGS FATAL ERROR WARN INFO DEBUG TIMING TRACE)
add_compile_definitions(LOGLEVEL=${LOG_LEVEL_${LOGLEVEL}})

Expand Down
6 changes: 6 additions & 0 deletions src/ServerMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ int main(int argc, char** argv) {
"prefix are rejected. To disable all federated queries, set this option "
"to an invalid IRI prefix like `-`. Magic services (for example spatial "
"search or materialized views) are never affected.");
add("log-level",
optionFactory.getProgramOption<&RuntimeParameters::logLevel_>(),
"Runtime log level: FATAL, ERROR, WARN, INFO, DEBUG, TIMING, or TRACE. "
"Default is INFO. The compile-time level (CMake -DLOGLEVEL=...) applies "
"as an upper bound — messages above it are never emitted regardless of "
"this setting.");
po::variables_map optionsMap;

try {
Expand Down
7 changes: 7 additions & 0 deletions src/global/RuntimeParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ RuntimeParameters::RuntimeParameters() {
add(permutationWriterNumThreads_);
add(vacuumMinimumBlockSize_);
add(disableCaching_);
add(logLevel_);

// Propagate runtime log level changes immediately to the global atomic in
// Log.h. The action fires once immediately on registration, so the atomic is
// in sync with the parameter default from the start.
logLevel_.setOnUpdateAction(
[](LogLevel level) { ad_utility::setRuntimeLogLevel(level); });

defaultQueryTimeout_.setParameterConstraint(
[](std::chrono::seconds value, std::string_view parameterName) {
Expand Down
11 changes: 11 additions & 0 deletions src/global/RuntimeParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#ifndef QLEVER_RUNTIMEPARAMETERS_H
#define QLEVER_RUNTIMEPARAMETERS_H

#include <algorithm>

#include "util/Log.h"
#include "util/Parameters.h"

// A set of parameters that can be accessed with a runtime and a compile time
Expand All @@ -22,6 +25,9 @@ struct RuntimeParameters {
using SpaceSeparatedStrings =
ad_utility::detail::parameterShortNames::SpaceSeparatedStrings;

using LogLevelParameter =
ad_utility::Parameter<LogLevel, LogLevel::FromString, LogLevel::ToString>;

// ___________________________________________________________________________
// IMPORTANT NOTE: IF YOU ADD PARAMETERS BELOW, ALSO REGISTER THEM IN THE
// CONSTRUCTOR, S.T. THEY CAN ALSO BE ACCESSED VIA THE RUNTIME INTERFACE.
Expand Down Expand Up @@ -167,6 +173,11 @@ struct RuntimeParameters {
// Only blocks of this size or larger will be considered for vacuuming.
SizeT vacuumMinimumBlockSize_{100, "vacuum-minimum-block-size"};

// The runtime log level. Messages with a higher level are suppressed. The
// compile-time level (CMake LOGLEVEL) still applies as an upper bound.
LogLevelParameter logLevel_{LogLevel{ad_utility::detail::defaultLogLevel},
"log-level"};

// ___________________________________________________________________________
// IMPORTANT NOTE: IF YOU ADD PARAMETERS ABOVE, ALSO REGISTER THEM IN THE
// CONSTRUCTOR, S.T. THEY CAN ALSO BE ACCESSED VIA THE RUNTIME INTERFACE.
Expand Down
18 changes: 17 additions & 1 deletion src/util/EnumWithStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@

#include "backports/keywords.h"
#include "backports/three_way_comparison.h"
#include "util/Exception.h"
#include "util/Random.h"
#include "util/json.h"
// Use the raw nlohmann header rather than `util/json.h` to avoid pulling in
// `util/File.h` (which uses AD_LOG macros) during the processing of Log.h.
#include <nlohmann/json.hpp>
Comment on lines +26 to +28

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no cleaner way to do this? For example, do we really need all the methods in File to be in the .h file? And I only see one AD_LOG_... in the file, inside of the deleteFile method, which is certainly not performance-critical

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the #define to the CMakeLists.txt. Then we can safely also directly include the nlohmann/json.hpp. The util/jsoh contains some advanced heljper functions which we don't need here anyway, so that solution is fine for now, and the ugly part is gone.


namespace ad_utility {

Expand Down Expand Up @@ -114,6 +117,19 @@ CPP_template(typename Derived,
Derived::descriptions_.at(static_cast<size_t>(it - descs.begin())));
}

// Functors wrapping fromString() and toString() for use with
// ad_utility::Parameter and similar template-functor APIs.
struct FromString {
Derived operator()(const std::string& s) const {
return Derived::fromString(s);
}
};
struct ToString {
std::string operator()(const Derived& d) const {
return std::string{d.toString()};
}
};

// Return all the possible enum values as a comma-separated single string.
static std::string getListOfSupportedValues() {
return absl::StrJoin(descriptions(), ", ");
Expand Down
120 changes: 87 additions & 33 deletions src/util/Log.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,31 @@
#ifndef QLEVER_SRC_UTIL_LOG_H
#define QLEVER_SRC_UTIL_LOG_H

#include <absl/strings/str_cat.h>
#include <absl/strings/str_format.h>
#include <absl/time/clock.h>
#include <absl/time/time.h>

#include <algorithm>
#include <atomic>
#include <iostream>
#include <locale>
#include <mutex>
#include <sstream>
#include <string>

#include "backports/keywords.h"
#include "util/ConstexprMap.h"
#include "util/EnumWithStrings.h"
#include "util/TypeTraits.h"

#ifndef LOGLEVEL
#define LOGLEVEL INFO
#endif

#define AD_LOG(x) \
if (x > LOGLEVEL) \
; \
else \
ad_utility::Log::getLog<x>() // NOLINT
namespace ad_utility {

enum class LogLevel {
namespace detail {
enum class LogLevelEnum {
FATAL = 0,
ERROR = 1,
WARN = 2,
Expand All @@ -39,19 +40,87 @@
TIMING = 5,
TRACE = 6
};
}

// Macros for the different log levels.
#define AD_LOG_FATAL AD_LOG(LogLevel::FATAL)
#define AD_LOG_ERROR AD_LOG(LogLevel::ERROR)
#define AD_LOG_WARN AD_LOG(LogLevel::WARN)
#define AD_LOG_INFO AD_LOG(LogLevel::INFO)
#define AD_LOG_DEBUG AD_LOG(LogLevel::DEBUG)
#define AD_LOG_TIMING AD_LOG(LogLevel::TIMING)
#define AD_LOG_TRACE AD_LOG(LogLevel::TRACE)
// Log level wrapper using the `EnumWithStrings` CRTP base to provide string
// conversion, JSON serialization, and boost::program_options integration.
class LogLevel : public EnumWithStrings<LogLevel, detail::LogLevelEnum> {
public:
using Enum = detail::LogLevelEnum;
static constexpr std::array<std::pair<Enum, std::string_view>, 7>
descriptions_{{{Enum::FATAL, "FATAL"},
{Enum::ERROR, "ERROR"},
{Enum::WARN, "WARN"},
{Enum::INFO, "INFO"},
{Enum::DEBUG, "DEBUG"},
{Enum::TIMING, "TIMING"},
{Enum::TRACE, "TRACE"}}};
static constexpr std::string_view typeName() { return "log level"; }
using EnumWithStrings::EnumWithStrings;
};

} // namespace ad_utility

// Global type alias and using-enum so that `LogLevel::FATAL` etc. and the
// compile-time `LOGLEVEL` macro keep working outside `namespace ad_utility`.
using LogLevel = ad_utility::LogLevel;
using enum LogLevel::Enum;

// Both the compile-time level (LOGLEVEL) and the runtime level must pass for a
// message to be logged. The LogLock temporary is held for the entire <<
// chain and released at the semicolon that ends the statement.
#define AD_LOG(x) \
if (x > LOGLEVEL || x > ::ad_utility::detail::runtimeLogLevel.load( \
std::memory_order_relaxed)) \
; \
else \
(::ad_utility::detail::LogLock{::ad_utility::detail::logMutex}, \
::ad_utility::Log::getLog<x>()) // NOLINT

using enum LogLevel;
// Macros for the different log levels.
#define AD_LOG_FATAL AD_LOG(LogLevel::Enum::FATAL)
#define AD_LOG_ERROR AD_LOG(LogLevel::Enum::ERROR)
#define AD_LOG_WARN AD_LOG(LogLevel::Enum::WARN)
#define AD_LOG_INFO AD_LOG(LogLevel::Enum::INFO)
#define AD_LOG_DEBUG AD_LOG(LogLevel::Enum::DEBUG)
#define AD_LOG_TIMING AD_LOG(LogLevel::Enum::TIMING)
#define AD_LOG_TRACE AD_LOG(LogLevel::Enum::TRACE)

namespace ad_utility {

namespace detail {
// Global mutex to ensure log messages from different threads are not
// interleaved (acquired via the comma-operator trick in the AD_LOG macro).
inline std::mutex logMutex;

Check failure on line 94 in src/util/Log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Global variables should be const.

See more on https://sonarcloud.io/project/issues?id=ad-freiburg_qlever&issues=AZ6JeKRGI1TU_bZby4eE&open=AZ6JeKRGI1TU_bZby4eE&pullRequest=2834

static constexpr LogLevel::Enum defaultLogLevel =
std::min(LOGLEVEL, LogLevel::Enum::INFO);
// Runtime log level; messages with a higher level than this are suppressed.
// Defaults to the less verbose of INFO and the compile-time LOGLEVEL so that
// the runtime level is never set to something the binary cannot log.
inline std::atomic<LogLevel::Enum> runtimeLogLevel = defaultLogLevel;

Check warning on line 101 in src/util/Log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid explicitly specifying the template arguments by relying on the class template argument deduction.

See more on https://sonarcloud.io/project/issues?id=ad-freiburg_qlever&issues=AZ6J8vWLdiMsS2582Gks&open=AZ6J8vWLdiMsS2582Gks&pullRequest=2834

Check failure on line 101 in src/util/Log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Global variables should be const.

See more on https://sonarcloud.io/project/issues?id=ad-freiburg_qlever&issues=AZ6J8vWLdiMsS2582Gkr&open=AZ6J8vWLdiMsS2582Gkr&pullRequest=2834
// Non-[[nodiscard]] wrapper so the comma-operator pattern doesn't trigger
// -Wunused-value warnings (std::lock_guard itself is [[nodiscard]] in libc++).
struct LogLock {
std::lock_guard<std::mutex> lock_;

Check warning on line 105 in src/util/Log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of "std::lock_guard" with "std::scoped_lock"

See more on https://sonarcloud.io/project/issues?id=ad-freiburg_qlever&issues=AZ6JeKRGI1TU_bZby4eH&open=AZ6JeKRGI1TU_bZby4eH&pullRequest=2834
explicit LogLock(std::mutex& m) : lock_{m} {}
};
} // namespace detail

// Set the runtime log level. Throws if `level` is more verbose than the
// compile-time LOGLEVEL, because such messages are compiled out and can never
// appear regardless of the runtime setting.
inline void setRuntimeLogLevel(LogLevel level) {
if (level.value() > LOGLEVEL) {
throw std::runtime_error{absl::StrCat(
"Cannot set runtime log level to `", level.toString(),
"` because the compile-time log level is `",
LogLevel{LOGLEVEL}.toString(), "`. Recompile with -DLOGLEVEL=",
level.toString(), " or higher to enable this log level.")};
}
detail::runtimeLogLevel.store(level.value(), std::memory_order_relaxed);

Check failure on line 121 in src/util/Log.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'std::memory_order::seq_cst' (or remove this argument to use its default value) to ensure sequential consistency.

See more on https://sonarcloud.io/project/issues?id=ad-freiburg_qlever&issues=AZ6JeKRGI1TU_bZby4eI&open=AZ6JeKRGI1TU_bZby4eI&pullRequest=2834
}

// A singleton that holds a pointer to a single `std::ostream`. This enables us
// to globally redirect the `AD_LOG_...` macros to another output stream.
struct LogstreamChoice {
Expand Down Expand Up @@ -92,11 +161,11 @@
// The class that actually does the logging.
class Log {
public:
template <LogLevel LEVEL>
template <LogLevel::Enum LEVEL>
static std::ostream& getLog() {
// use the singleton logging stream as target.
return LogstreamChoice::get().getStream()
<< getTimeStamp() << " - " << getLevel<LEVEL>() << ": ";
<< getTimeStamp() << " - " << LogLevel{LEVEL}.toString() << ": ";
}

static void imbue(const std::locale& locale) { std::cout.imbue(locale); }
Expand All @@ -105,21 +174,6 @@
return absl::FormatTime("%Y-%m-%d %H:%M:%E3S", absl::Now(),
absl::LocalTimeZone());
}

template <LogLevel LEVEL>
static QL_CONSTEVAL std::string_view getLevel() {
using P = ConstexprMapPair<LogLevel, std::string_view>;
constexpr ConstexprMap map{std::array<P, 7>{
P(TRACE, "TRACE"),
P(TIMING, "TIMING"),
P(DEBUG, "DEBUG"),
P(INFO, "INFO"),
P(WARN, "WARN"),
P(ERROR, "ERROR"),
P(FATAL, "FATAL"),
}};
return map.at(LEVEL);
}
};
} // namespace ad_utility

Expand Down
11 changes: 3 additions & 8 deletions src/util/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,22 @@ Convenience header for Nlohmann::Json that sets the default options. Also
#define EOF std::char_traits<char>::eof()
#endif

// Disallow implicit conversions from nlohmann::json to other types,
// most notably to std::string.
#include <stdexcept>

#include "util/File.h"
#include "util/TypeTraits.h"
#define JSON_USE_IMPLICIT_CONVERSIONS 0

#include <absl/strings/str_cat.h>

#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <stdexcept>
#include <utility>
#include <variant>

#include "backports/StartsWithAndEndsWith.h"
#include "backports/type_traits.h"
#include "util/ConstexprUtils.h"
#include "util/Exception.h"
#include "util/File.h"
#include "util/SourceLocation.h"
#include "util/TypeTraits.h"

// For higher flexibility of the custom json helper functions.
template <typename T>
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,5 @@ addLinkAndDiscoverTestNoLibs(EnumWithStringsTest Boost::program_options)
addLinkAndDiscoverTest(ConstructTripleGeneratorTest engine)

addLinkAndDiscoverTest(FilesystemHelpersTest util)

addLinkAndDiscoverTest(LogTest)
10 changes: 10 additions & 0 deletions test/EnumWithStringsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,13 @@ TEST(EnumWithStrings, BoostProgramOptions) {
}

#endif // __wasm__

// _____________________________________________________________________________
TEST(EnumWithStrings, FromStringAndToStringFunctors) {
using V = ad_utility::VocabularyType;
EXPECT_EQ(V::OnDiskCompressed, V::FromString{}("on-disk-compressed"));
EXPECT_EQ("on-disk-compressed", V::ToString{}(V::OnDiskCompressed));
EXPECT_EQ(V::InMemoryUncompressed, V::FromString{}("in-memory-uncompressed"));
EXPECT_EQ("in-memory-uncompressed", V::ToString{}(V::InMemoryUncompressed));
EXPECT_THROW(V::FromString{}("not-a-valid-type"), std::runtime_error);
}
2 changes: 1 addition & 1 deletion test/IndexTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ TEST(IndexTest, trivialGettersAndSetters) {
}

TEST(IndexTest, updateInputFileSpecificationsAndLog) {
SKIP_IF_LOGLEVEL_IS_LOWER(WARN);
SKIP_IF_LOGLEVEL_IS_LOWER(INFO);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change intentional?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is intentional, as it was wrong and this test in fact fails when being run on loglevel WARN, which was detected by the new infrastructure (which sets the loglevel as part of the macro).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that part of the test requires info while another part only requires warn (the deprecation messages).

using enum qlever::Filetype;
std::vector<qlever::InputFileSpecification> singleFileSpec = {
{"singleFile.ttl", Turtle, std::nullopt}};
Expand Down
Loading
Loading