16enum class LogLevel : std::uint8_t {
32constexpr std::string_view levelName(LogLevel level)
noexcept {
50using Logger = std::function<void(LogLevel, std::string_view)>;
65inline std::string sanitizeLogLine(std::string_view msg) {
66 auto needsEscape = [](
unsigned char chr) {
return chr < 0x20 || chr == 0x7f; };
67 std::size_t firstBad = std::string_view::npos;
68 for (std::size_t i = 0; i < msg.size(); ++i) {
69 if (needsEscape(
static_cast<unsigned char>(msg[i]))) {
74 if (firstBad == std::string_view::npos) {
75 return std::string{msg};
78 out.reserve(msg.size() + 8);
79 out.append(msg.substr(0, firstBad));
80 static constexpr std::string_view kHex =
"0123456789abcdef";
81 for (std::size_t i = firstBad; i < msg.size(); ++i) {
82 const auto chr =
static_cast<unsigned char>(msg[i]);
94 if (needsEscape(chr)) {
96 out.push_back(kHex[chr >> 4]);
97 out.push_back(kHex[chr & 0x0f]);
99 out.push_back(
static_cast<char>(chr));
108 Logger sink = [](LogLevel lvl, std::string_view msg) {
109 std::println(stderr,
"[{}] {}", levelName(lvl), sanitizeLogLine(msg));
124 std::atomic<LogLevel> minLevel{LogLevel::warn};
129 std::atomic<std::uint64_t> dropped{0};
132inline LogState& logState() {
133 static LogState state;
143inline void log(LogLevel level, std::string_view msg)
noexcept {
144 auto& state = logState();
145 if (level < state.minLevel.load(std::memory_order_relaxed)) {
155 std::scoped_lock
const lock{state.mtx};
157 state.sink(level, msg);
160 state.dropped.fetch_add(1, std::memory_order_relaxed);
173inline void setLogger(std::function<
void(LogLevel, std::string_view)> logger) {
174 std::scoped_lock
const lock{detail::logState().mtx};
175 detail::logState().sink = std::move(logger);
182inline void setLogLevel(LogLevel level) { detail::logState().minLevel.store(level, std::memory_order_relaxed); }
186inline LogLevel getLogLevel() {
return detail::logState().minLevel.load(std::memory_order_relaxed); }
201[[nodiscard]]
inline std::uint64_t droppedLogRecords() noexcept {
202 return detail::logState().dropped.load(std::memory_order_relaxed);
213template <
typename... Args>
214void logFormat(LogLevel level, std::format_string<Args...> fmt, Args&&... args)
noexcept {
215 if (level < logState().minLevel.load(std::memory_order_relaxed)) {
222 log(level, std::format(fmt, std::forward<Args>(args)...));
224 logState().dropped.fetch_add(1, std::memory_order_relaxed);
231inline void logDebug(std::string_view msg)
noexcept { detail::log(LogLevel::debug, msg); }
233inline void logInfo(std::string_view msg)
noexcept { detail::log(LogLevel::info, msg); }
235inline void logWarn(std::string_view msg)
noexcept { detail::log(LogLevel::warn, msg); }
237inline void logError(std::string_view msg)
noexcept { detail::log(LogLevel::error, msg); }
240template <
typename... Args>
241void logDebug(std::format_string<Args...> fmt, Args&&... args)
noexcept {
242 detail::logFormat(LogLevel::debug, fmt, std::forward<Args>(args)...);
245template <
typename... Args>
246void logInfo(std::format_string<Args...> fmt, Args&&... args)
noexcept {
247 detail::logFormat(LogLevel::info, fmt, std::forward<Args>(args)...);
250template <
typename... Args>
251void logWarn(std::format_string<Args...> fmt, Args&&... args)
noexcept {
252 detail::logFormat(LogLevel::warn, fmt, std::forward<Args>(args)...);
255template <
typename... Args>
256void logError(std::format_string<Args...> fmt, Args&&... args)
noexcept {
257 detail::logFormat(LogLevel::error, fmt, std::forward<Args>(args)...);
294 std::scoped_lock
const lock{detail::logState().mtx};
295 _savedSink = detail::logState().sink;
296 _savedLevel = detail::logState().minLevel;
305 LogLevel level = LogLevel::debug) {
309 std::scoped_lock
const lock{detail::logState().mtx};
310 _savedSink = std::move(detail::logState().sink);
311 _savedLevel = detail::logState().minLevel;
312 detail::logState().sink = std::move(sink);
313 detail::logState().minLevel = level;
319 std::scoped_lock
const lock{detail::logState().mtx};
320 detail::logState().sink = std::move(_savedSink);
321 detail::logState().minLevel = _savedLevel;
330 std::function<void(LogLevel, std::string_view)> _savedSink;
331 LogLevel _savedLevel{LogLevel::debug};
RAII helper that swaps the global logger and level for the lifetime of the object,...
ScopedLoggerOverride(std::function< void(LogLevel, std::string_view)> sink, LogLevel level=LogLevel::debug)
Installs sink and level; saves whatever was there before.
~ScopedLoggerOverride()
Restores the saved sink and level.
ScopedLoggerOverride()
Snapshots the current sink + level so they can be restored.