util/log.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/util/log.h" | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <array> | ||
| 5 | #include <atomic> | ||
| 6 | #include <cctype> | ||
| 7 | #include <cstdio> | ||
| 8 | #include <iostream> | ||
| 9 | #include <mutex> | ||
| 10 | #include <utility> | ||
| 11 | |||
| 12 | namespace terraingen { | ||
| 13 | |||
| 14 | namespace { | ||
| 15 | |||
| 16 | struct LogState { | ||
| 17 | std::mutex mutex; | ||
| 18 | LogSink sink = StandardErrorLogSink(); | ||
| 19 | std::atomic<LogLevel> level{LogLevel::Warning}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | 1551878 | LogState& State() { | |
| 23 | 1551878 | static LogState state; | |
| 24 | 1551878 | return state; | |
| 25 | } | ||
| 26 | |||
| 27 | thread_local bool inSink = false; | ||
| 28 | |||
| 29 | /// Marks the current thread as inside the sink, so records the sink logs are dropped. | ||
| 30 | class SinkGuard { | ||
| 31 | public: | ||
| 32 | 8093 | SinkGuard() { | |
| 33 | 8093 | inSink = true; | |
| 34 | 8093 | } | |
| 35 | 8093 | ~SinkGuard() { | |
| 36 | 8093 | inSink = false; | |
| 37 | 8093 | } | |
| 38 | SinkGuard(const SinkGuard&) = delete; | ||
| 39 | SinkGuard& operator=(const SinkGuard&) = delete; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace | ||
| 43 | |||
| 44 | 420 | std::string_view LogLevelName(LogLevel level) { | |
| 45 | 420 | switch (level) { | |
| 46 | 98 | case LogLevel::Trace: | |
| 47 | 98 | return "trace"; | |
| 48 | 98 | case LogLevel::Debug: | |
| 49 | 98 | return "debug"; | |
| 50 | 112 | case LogLevel::Info: | |
| 51 | 112 | return "info"; | |
| 52 | 94 | case LogLevel::Warning: | |
| 53 | 94 | return "warning"; | |
| 54 | 9 | case LogLevel::Error: | |
| 55 | 9 | return "error"; | |
| 56 | 8 | case LogLevel::Off: | |
| 57 | 8 | return "off"; | |
| 58 | } | ||
| 59 | 1 | return "unknown"; | |
| 60 | } | ||
| 61 | |||
| 62 | 98 | std::optional<LogLevel> ParseLogLevel(std::string_view name) { | |
| 63 | 98 | std::string lower(name); | |
| 64 | 98 | std::transform(lower.begin(), lower.end(), lower.begin(), | |
| 65 | 642 | [](unsigned char c) { return static_cast<char>(std::tolower(c)); }); | |
| 66 | 98 | if (lower == "warn") { | |
| 67 | 1 | return LogLevel::Warning; | |
| 68 | } | ||
| 69 | 97 | constexpr std::array kLevels{LogLevel::Trace, LogLevel::Debug, LogLevel::Info, | |
| 70 | LogLevel::Warning, LogLevel::Error, LogLevel::Off}; | ||
| 71 | 394 | for (const LogLevel level : kLevels) { | |
| 72 | 391 | if (lower == LogLevelName(level)) { | |
| 73 | 94 | return level; | |
| 74 | } | ||
| 75 | } | ||
| 76 | 3 | return std::nullopt; | |
| 77 | 98 | } | |
| 78 | |||
| 79 | 441 | LogSink StandardErrorLogSink() { | |
| 80 | 441 | return [](const LogRecord& record) { | |
| 81 | 36 | std::cerr << "[terraingen] " << LogLevelName(record.level) << ' ' << record.category << ": " | |
| 82 | 18 | << record.message << '\n'; | |
| 83 | 441 | }; | |
| 84 | } | ||
| 85 | |||
| 86 | 128 | void SetLogSink(LogSink sink) { | |
| 87 | 128 | LogState& state = State(); | |
| 88 | 128 | const std::lock_guard lock(state.mutex); | |
| 89 | 128 | state.sink = sink ? std::move(sink) : StandardErrorLogSink(); | |
| 90 | 128 | } | |
| 91 | |||
| 92 | 205 | void SetLogLevel(LogLevel level) { | |
| 93 | 205 | State().level.store(level, std::memory_order_relaxed); | |
| 94 | 205 | } | |
| 95 | |||
| 96 | 1543452 | LogLevel GetLogLevel() { | |
| 97 | 1543452 | return State().level.load(std::memory_order_relaxed); | |
| 98 | } | ||
| 99 | |||
| 100 | 1543367 | bool LogEnabled(LogLevel level) { | |
| 101 | 1543367 | return level != LogLevel::Off && level >= GetLogLevel(); | |
| 102 | } | ||
| 103 | |||
| 104 | 8405 | void Log(LogLevel level, std::string_view category, std::string message) { | |
| 105 | 8405 | if (!LogEnabled(level) || inSink) { | |
| 106 | 312 | return; | |
| 107 | } | ||
| 108 | 8093 | const LogRecord record{level, std::string(category), std::move(message), | |
| 109 | 16186 | std::chrono::system_clock::now()}; | |
| 110 | 8093 | LogState& state = State(); | |
| 111 | 8093 | const std::lock_guard lock(state.mutex); | |
| 112 | 8093 | const SinkGuard guard; | |
| 113 | 8093 | state.sink(record); | |
| 114 | 8093 | } | |
| 115 | |||
| 116 | 366526 | std::string FormatDuration(double milliseconds) { | |
| 117 | 366526 | std::array<char, 32> buffer{}; | |
| 118 | 366526 | if (milliseconds >= 1000.0) { | |
| 119 | 852 | std::snprintf(buffer.data(), buffer.size(), "%.2f s", milliseconds / 1000.0); | |
| 120 | 366242 | } else if (milliseconds >= 10.0) { | |
| 121 | 4858 | std::snprintf(buffer.data(), buffer.size(), "%.1f ms", milliseconds); | |
| 122 | } else { | ||
| 123 | 727626 | std::snprintf(buffer.data(), buffer.size(), "%.2f ms", milliseconds); | |
| 124 | } | ||
| 125 | 733052 | return buffer.data(); | |
| 126 | } | ||
| 127 | |||
| 128 | } // namespace terraingen | ||
| 129 |