logman 0.1.0
Modern C++23 header-only logging manager wrapping spdlog with channels, listeners, and structured events
Loading...
Searching...
No Matches
listener_sink.hpp
Go to the documentation of this file.
1#pragma once
2
6
8
9#if defined(_WIN32)
10#include <process.h>
11#elif defined(__unix__) || defined(__APPLE__)
12#include <unistd.h>
13#endif
14
15#include <spdlog/details/log_msg.h>
16#include <spdlog/sinks/base_sink.h>
17#include <spdlog/spdlog.h>
18
19#include <atomic>
20#include <cstdint>
21#include <exception>
22#include <functional>
23#include <iostream>
24#include <mutex>
25#include <ranges>
26#include <typeinfo>
27#include <unordered_map>
28#include <vector>
29
30namespace logman {
31
32namespace detail {
33
36inline int current_pid() noexcept {
37#if defined(_WIN32)
38 return _getpid();
39#elif defined(__unix__) || defined(__APPLE__)
40 return static_cast<int>(::getpid());
41#else
42 return -1;
43#endif
44}
45
49inline LogEvent make_log_event(const spdlog::details::log_msg& msg,
50 const std::string_view formatted_message) {
51 LogEvent event{};
52 event.timestamp = msg.time;
53 const auto level_sv = spdlog::level::to_string_view(msg.level);
54 event.level.assign(level_sv.data(), level_sv.size());
55 event.channel.assign(msg.logger_name.data(), msg.logger_name.size());
56 event.thread_id = static_cast<std::uint64_t>(msg.thread_id);
57 if (const int pid = current_pid(); pid >= 0) {
58 event.process_id = pid;
59 }
60 if (msg.source.filename != nullptr && msg.source.filename[0] != '\0') {
61 event.file = msg.source.filename;
62 event.line = static_cast<int>(msg.source.line);
63 if (msg.source.funcname != nullptr) {
64 event.function = msg.source.funcname;
65 }
66 }
67 event.message.assign(formatted_message.data(), formatted_message.size());
68 return event;
69}
70
71} // namespace detail
72
77class ListenerSink : public spdlog::sinks::base_sink<std::mutex> {
78public:
79 using listener_t = std::function<void(const LogEvent&)>;
80 using listener_id = std::uint64_t;
81
82 ~ListenerSink() override = default;
83
84 listener_id add_listener(listener_t listener) {
85 const std::scoped_lock lock(listeners_mutex_);
86 const auto id = ++listener_id_gen_;
87 listeners_.emplace(id, std::move(listener));
88 return id;
89 }
90
91 bool remove_listener(const listener_id id) {
92 const std::scoped_lock lock(listeners_mutex_);
93 return listeners_.erase(id) > 0;
94 }
95
96 void clear_listeners() {
97 const std::scoped_lock lock(listeners_mutex_);
98 listeners_.clear();
99 }
100
102 std::size_t listener_count() const {
103 const std::scoped_lock lock(listeners_mutex_);
104 return listeners_.size();
105 }
106
107protected:
108 void sink_it_(const spdlog::details::log_msg& msg) override {
109 spdlog::memory_buf_t formatted;
110 this->formatter_->format(msg, formatted);
111 const LogEvent event =
112 detail::make_log_event(msg, std::string_view(formatted.data(), formatted.size()));
113
114 std::vector<listener_t> snapshot;
115 {
116 const std::scoped_lock lock(listeners_mutex_);
117 snapshot.reserve(listeners_.size());
118 for (const auto& l : listeners_ | std::views::values) {
119 snapshot.push_back(l);
120 }
121 }
122
123 for (auto& listener : snapshot) {
124 try {
125 listener(event);
126 } catch (const std::exception& e) {
127#ifndef NDEBUG
128 std::cerr << "logman: listener threw " << typeid(e).name() << ": " << e.what()
129 << '\n';
130#else
131 (void)e;
132#endif
133 } catch (...) {
134#ifndef NDEBUG
135 std::cerr << "logman: listener threw non-std::exception\n";
136#endif
137 }
138 }
139 }
140
141 void flush_() override {}
142
143private:
144 std::atomic<listener_id> listener_id_gen_{0};
145 std::unordered_map<listener_id, listener_t> listeners_;
146 mutable std::mutex listeners_mutex_;
147};
148
149} // namespace logman
spdlog sink that dispatches every record to a list of user-registered listener callbacks as LogEvent ...
Definition listener_sink.hpp:77
std::size_t listener_count() const
Snapshot of current listener count — primarily for tests.
Definition listener_sink.hpp:102
int current_pid() noexcept
Platform-portable PID accessor.
Definition listener_sink.hpp:36
LogEvent make_log_event(const spdlog::details::log_msg &msg, const std::string_view formatted_message)
Build a LogEvent from a spdlog log_msg.
Definition listener_sink.hpp:49
Plain-old-data structure describing one log record.
One log record, captured by ListenerSink and (optionally) emitted as JSON by JsonFormatter.
Definition log_event.hpp:16