163 using Listener = std::function<void(
const LogEvent&)>;
164 using ListenerId = std::uint64_t;
166 static void initialize(
const spdlog::level::level_enum default_level = spdlog::level::info) {
169 instance().init(cfg);
172 static void initialize(
const InitConfig& cfg) {
173 instance().init(cfg);
176 static void shutdown() {
177 instance().shutdown_impl();
180 static std::shared_ptr<spdlog::logger> get(
const std::string_view channel) {
181 return instance().get_channel(channel);
184 static std::shared_ptr<spdlog::logger> get_or_null(
const std::string_view channel) {
185 auto& self = instance();
186 const std::shared_lock lock(self.mutex_);
187 if (
const auto it = self.loggers_.find(std::string(channel)); it != self.loggers_.end()) {
193 static std::unordered_map<std::string, spdlog::level::level_enum> channels() {
194 auto& self = instance();
195 const std::shared_lock lock(self.mutex_);
196 std::unordered_map<std::string, spdlog::level::level_enum> out;
197 out.reserve(self.loggers_.size());
198 for (
const auto& [name, logger] : self.loggers_) {
199 out.emplace(name, logger->level());
204 static bool set_level(
const std::string_view channel,
const spdlog::level::level_enum lvl) {
205 auto& self = instance();
206 const std::shared_lock lock(self.mutex_);
207 const auto it = self.loggers_.find(std::string(channel));
208 if (it == self.loggers_.end()) {
211 it->second->set_level(lvl);
215 static void set_levels_by_prefix(
const std::string_view prefix,
216 const spdlog::level::level_enum lvl) {
217 auto& self = instance();
218 const std::unique_lock lock(self.mutex_);
219 for (
auto& [name, lg] : self.loggers_) {
220 if (name.starts_with(prefix)) {
224 self.prefixed_levels_[std::string(prefix)] = lvl;
227 static void set_all_levels(
const spdlog::level::level_enum lvl) {
228 auto& self = instance();
229 const std::unique_lock lock(self.mutex_);
230 for (
const auto& lg : self.loggers_ | std::views::values) {
233 self.default_level_ = lvl;
234 spdlog::set_level(lvl);
237 static void set_default_level(
const spdlog::level::level_enum lvl) {
238 auto& self = instance();
239 const std::unique_lock lock(self.mutex_);
240 self.default_level_ = lvl;
241 spdlog::set_level(lvl);
244 static spdlog::level::level_enum default_level() {
245 const auto& self = instance();
246 const std::shared_lock lock(self.mutex_);
247 return self.default_level_;
250 static void set_pattern(std::string pattern) {
251 auto& self = instance();
252 const std::unique_lock lock(self.mutex_);
253 self.pattern_ = std::move(pattern);
254 const auto formatter = self.make_console_formatter();
255 if (self.console_sink_) {
256 self.console_sink_->set_formatter(formatter->clone());
258 for (
const auto& lg : self.loggers_ | std::views::values) {
259 lg->set_pattern(self.pattern_);
263 static void flush() {
264 auto& self = instance();
265 const std::shared_lock lock(self.mutex_);
266 for (
const auto& lg : self.loggers_ | std::views::values) {
271 static void set_flush_on(spdlog::level::level_enum lvl) {
272 auto& self = instance();
273 const std::shared_lock lock(self.mutex_);
274 for (
const auto& lg : self.loggers_ | std::views::values) {
277 self.flush_on_ = lvl;
280 static void add_sink(
const spdlog::sink_ptr& sink) {
281 auto& self = instance();
282 const std::unique_lock lock(self.mutex_);
283 self.sinks_.push_back(sink);
284 for (
const auto& lg : self.loggers_ | std::views::values) {
285 lg->sinks().push_back(sink);
289 static bool remove_sink(
const spdlog::sink_ptr& sink) {
290 auto& self = instance();
291 const std::unique_lock lock(self.mutex_);
292 const auto before = self.sinks_.size();
293 std::erase(self.sinks_, sink);
294 if (self.sinks_.size() == before) {
297 for (
const auto& lg : self.loggers_ | std::views::values) {
298 auto& s = lg->sinks();
304 static ListenerId add_listener(Listener l) {
305 const auto& self = instance();
306 return self.listener_sink_->add_listener(std::move(l));
309 static bool remove_listener(
const ListenerId
id) {
310 return instance().listener_sink_->remove_listener(
id);
313 static void clear_listeners() {
314 instance().listener_sink_->clear_listeners();
317 static std::shared_ptr<ListenerSink> listener_sink() {
318 return instance().listener_sink_;
329 friend void detail_reset_for_testing();
331 std::unique_ptr<spdlog::pattern_formatter> make_console_formatter()
const {
332 auto formatter = std::make_unique<spdlog::pattern_formatter>();
335 formatter->set_pattern(pattern_);
344 if (prefixes.empty()) {
346 prefixes.emplace_back(p);
349 for (
const auto& prefix : prefixes) {
350 detail::enumerate_env([&](
const std::string_view name,
const std::string_view value) {
351 if (!name.starts_with(prefix)) {
354 if (
const std::string_view rest = name.substr(prefix.size()); rest ==
"LEVEL") {
358 std::cerr <<
"logman: ignoring " << name <<
'=' << value
359 <<
" (unknown level)\n";
361 }
else if (rest.starts_with(
"LEVEL_")) {
362 const std::string_view ns = rest.substr(6);
366 std::cerr <<
"logman: ignoring " << name <<
'=' << value
367 <<
" (unknown level)\n";
369 }
else if (rest ==
"FORMAT") {
370 if (
const std::string lower = detail::ascii_lower(value); lower ==
"text") {
372 }
else if (lower ==
"json") {
375 std::cerr <<
"logman: ignoring " << name <<
'=' << value
376 <<
" (unknown format; want text|json)\n";
384 std::call_once(init_flag_, [&] {
389 listener_sink_ = std::make_shared<ListenerSink>();
390 listener_sink_->set_level(spdlog::level::trace);
391 listener_sink_->set_formatter(std::make_unique<spdlog::pattern_formatter>(
392 "%v", spdlog::pattern_time_type::local, std::string{}));
395 console_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
396 console_sink_->set_level(spdlog::level::trace);
400 console_sink_->set_formatter(std::make_unique<JsonFormatter>());
402 std::cerr <<
"logman: structured_json requested but library compiled "
403 "without <nlohmann/json.hpp>; falling back to text\n";
404 console_sink_->set_formatter(make_console_formatter());
407 console_sink_->set_formatter(make_console_formatter());
409 sinks_.push_back(console_sink_);
411 sinks_.push_back(listener_sink_);
413 for (
const auto& [pfx, lvl] : env_prefix_overrides_) {
414 prefixed_levels_[pfx] = lvl;
417 auto main = std::make_shared<spdlog::logger>(
"main", sinks_.begin(), sinks_.end());
418 main->set_level(default_level_);
419 for (
const auto& [pfx, lvl] : prefixed_levels_) {
420 if (std::string_view(
"main").starts_with(pfx)) {
421 main->set_level(lvl);
424 spdlog::register_logger(main);
425 loggers_.emplace(
"main", main);
427 spdlog::set_default_logger(main);
429 spdlog::set_level(default_level_);
435 void shutdown_impl() {
436 const std::unique_lock lock(mutex_);
439 prefixed_levels_.clear();
440 env_prefix_overrides_.clear();
441 listener_sink_.reset();
442 console_sink_.reset();
443 initialized_ =
false;
447 std::shared_ptr<spdlog::logger> get_channel(
const std::string_view channel) {
452 const std::string name(channel);
454 const std::shared_lock lock(mutex_);
455 if (
const auto it = loggers_.find(name); it != loggers_.end()) {
460 const std::unique_lock lock(mutex_);
461 if (
const auto it = loggers_.find(name); it != loggers_.end()) {
465 auto logger = std::make_shared<spdlog::logger>(name, sinks_.begin(), sinks_.end());
466 logger->set_level(default_level_);
468 logger->flush_on(*flush_on_);
470 for (
const auto& [pfx, lvl] : prefixed_levels_) {
471 if (name.starts_with(pfx)) {
472 logger->set_level(lvl);
475 spdlog::register_logger(logger);
476 loggers_.emplace(name, logger);
480 mutable std::shared_mutex mutex_;
481 std::once_flag init_flag_;
482 bool initialized_ =
false;
483 std::string pattern_;
484 std::shared_ptr<ListenerSink> listener_sink_;
485 std::shared_ptr<spdlog::sinks::stdout_color_sink_mt> console_sink_;
486 std::vector<spdlog::sink_ptr> sinks_;
487 std::unordered_map<std::string, std::shared_ptr<spdlog::logger>> loggers_;
488 std::unordered_map<std::string, spdlog::level::level_enum> prefixed_levels_;
489 std::unordered_map<std::string, spdlog::level::level_enum> env_prefix_overrides_;
490 std::optional<spdlog::level::level_enum> flush_on_;
491 spdlog::level::level_enum default_level_ = spdlog::level::info;