Skip to content

Make logging thread-safe and add runtime-configurable log level#2834

Open
joka921 wants to merge 5 commits intoad-freiburg:masterfrom
joka921:configurable-logging
Open

Make logging thread-safe and add runtime-configurable log level#2834
joka921 wants to merge 5 commits intoad-freiburg:masterfrom
joka921:configurable-logging

Conversation

@joka921
Copy link
Copy Markdown
Member

@joka921 joka921 commented Apr 21, 2026

Summary

  • Thread-safety: Each AD_LOG_* statement now acquires a global std::mutex (via a LogLock RAII wrapper used in the comma-operator expression) before writing to the stream. The lock covers the entire << chain and is released at the statement's closing ;, so messages from concurrent threads can no longer interleave.
  • Runtime log level: A new std::atomic<LogLevel> runtimeLogLevel (default INFO) is checked in AD_LOG alongside the existing compile-time LOGLEVEL guard. A message is suppressed if either gate rejects it.
  • RuntimeParameters integration: A new logLevel_ parameter (type LogLevelParameter) is added to RuntimeParameters. Its setOnUpdateAction wires changes directly to the atomic, so the level can be changed at runtime via HTTP (?cmd=set-parameter&log-level=DEBUG) or at startup via CLI.
  • CLI flag: --log-level is exposed in ServerMain.cpp with default INFO.
  • CMake default: The compile-time LOGLEVEL now always defaults to DEBUG (previously it was build-type-dependent). Since all messages are compiled in, the runtime level governs actual output.

Test plan

  • cmake --build build --target qlever-server builds without errors or warnings.
  • ./qlever-server --help shows --log-level arg (=INFO) with correct description.
  • Running with --log-level DEBUG produces debug output; --log-level WARN suppresses info messages.
  • Invalid --log-level foo produces a clear error message.
  • HTTP runtime change (?cmd=set-parameter&log-level=DEBUG) takes effect immediately.
  • ThreadSanitizer build shows no data races on the logging path.

🤖 Generated with Claude Code

joka921 and others added 5 commits April 21, 2026 20:35
- Add a global `std::mutex` (via a non-nodiscard `LogLock` wrapper) so that
  each `AD_LOG_*` statement is serialized: the lock is acquired before the
  first `<<` and released at the closing `;`, preventing interleaved output
  from concurrent threads.
- Add `std::atomic<LogLevel> runtimeLogLevel` (default `INFO`) and check it
  in the `AD_LOG` macro alongside the existing compile-time `LOGLEVEL` guard.
  Messages are suppressed when *either* gate rejects them.
- Add free functions `logLevelFromString` / `logLevelToString` in `Log.h`.
- Add a `LogLevelParameter` type (with string serializers) to
  `RuntimeParameters`, wired via `setOnUpdateAction` so any change to the
  parameter (HTTP, CLI) is immediately reflected in the atomic.
- Change the CMake default compile-time `LOGLEVEL` from build-type-dependent
  (`DEBUG` / `INFO`) to always `DEBUG`, so all messages are compiled in; the
  runtime level (default `INFO`) handles the actual filtering.
- Expose `--log-level` as a CLI flag in `ServerMain.cpp`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LogLevel now inherits from EnumWithStrings<LogLevel, detail::LogLevelEnum>,
which provides fromString(), toString(), JSON serialization, and a generic
boost::program_options validate() via ADL. This removes the hand-written
logLevelFromString()/logLevelToString() free functions, the LogLevel-specific
validate() in ProgramOptionsHelpers.h, and simplifies the wrapper structs in
RuntimeParameters.h.

EnumWithStrings.h is updated to include <nlohmann/json.hpp> directly instead
of util/json.h to avoid a circular include chain through util/File.h.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove the redundant `static const LogLevel FATAL/ERROR/...` members
  and their inline definitions; the EnumWithStrings base provides all
  necessary conversions without them.
- `setRuntimeLogLevel` now throws `std::runtime_error` when the requested
  level is more verbose than the compile-time LOGLEVEL, since such messages
  are compiled out and can never appear at runtime.
- Add `ScopedRuntimeLogLevel` RAII guard to temporarily set the runtime
  log level for a scope and restore it on exit.
- Update `SKIP_IF_LOGLEVEL_IS_LOWER` in GTestHelpers.h to install a
  `ScopedRuntimeLogLevel` guard, so tests that capture DEBUG log output
  work correctly even when the default runtime level is INFO.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AD_LOG macro: use fully-qualified ::ad_utility:: names so it works
  correctly from any namespace, including deeply nested ones.
- setRuntimeLogLevel: use absl::StrCat instead of manual std::string
  concatenation for the error message.
- Remove ScopedRuntimeLogLevel struct; GTestHelpers.h now uses an
  absl::Cleanup directly in SKIP_IF_LOGLEVEL_IS_LOWER.
- Default runtime log level (both in Log.h and RuntimeParameters.h) is
  now min(INFO, LOGLEVEL) so the runtime level is never initialized to a
  value the binary cannot actually log.
- CMakeLists.txt: extend LOGLEVEL description to mention that less verbose
  levels can be chosen at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member Author

@joka921 joka921 left a comment

Choose a reason for hiding this comment

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

Some comments from a self review, first wait for other reviews before working this in.

Comment on lines +188 to +189
LogLevel{LOGLEVEL < LogLevel::Enum::INFO ? LOGLEVEL
: LogLevel::Enum::INFO},
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.

use std::min

Comment thread src/util/Log.h
// 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{
LOGLEVEL < LogLevel::Enum::INFO ? LOGLEVEL : LogLevel::Enum::INFO};
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.

also use std::min

Comment thread src/util/Log.h
Comment on lines +114 to +117
"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.")};
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.

Maybe use backticks instead of quotes.

@joka921 joka921 requested review from RobinTF and ullingerc and removed request for RobinTF April 29, 2026 07:11
@sparql-conformance
Copy link
Copy Markdown

Overview

Number of Tests Passed ✅ Intended ✅ Failed ❌ Not tested
548 447 73 28 0

Conformance check passed ✅

No test result changes.

Details: https://qlever.dev/sparql-conformance-ui?cur=31727629b2816c4ca133b3336742cc170d768234&prev=e5f672431d5c70c406fd762854388d982b68a6e0

Copy link
Copy Markdown
Collaborator

@RobinTF RobinTF left a comment

Choose a reason for hiding this comment

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

I agree with your comments, I have a small question.

Comment thread src/util/Log.h
P(FATAL, "FATAL"),
}};
return map.at(LEVEL);
return LogLevel::descriptions_[static_cast<size_t>(LEVEL)].second;
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 there a reason why this can't be LEVEL.toString()?

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants