Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
log.hpp
Go to the documentation of this file.
1#pragma once
2
3// Logging.
4//
5// `log_info(...)` and friends write to the platform log (os_log, the debugger
6// output window, stderr) and then hand the message to the registered sink, if
7// any. Header-only: nothing here needs a runtime translation unit, so modules
8// that include it without linking pulp-runtime keep working.
9//
10// Logging is NOT real-time safe. Formatting allocates, the platform writers and
11// sinks take locks and perform I/O, and a sink is arbitrary user code. Never
12// log from `Processor::process()` or any other audio-thread path. Hand the
13// event to a non-realtime thread (an SPSC queue drained by a worker) and log
14// there.
15//
16// Sinks: `set_log_sink` installs one process-wide callback that receives
17// (level, category, message) for every record. It is the hook for file logs
18// (`FileLogSink`), test capture (`CaptureLogSink`) and diagnostics surfaces; see
19// <pulp/runtime/log_sink.hpp>. The registry is per loaded binary image: a
20// plugin and its host each have their own, which keeps one plugin's sink from
21// receiving another's records.
22
25#include <atomic>
26#include <cstdio>
27#include <format>
28#include <functional>
29#include <memory>
30#include <mutex>
31#include <string>
32#include <string_view>
33
34#if defined(__APPLE__)
35 #include <os/log.h>
36#elif defined(_WIN32)
38#endif
39
40namespace pulp::runtime {
41
42enum class LogLevel { Debug, Info, Warning, Error };
43
49using LogSink =
50 std::function<void(LogLevel level, std::string_view category, std::string_view message)>;
51
53inline constexpr std::string_view kDefaultLogCategory = "pulp";
54
55namespace detail {
56
58 std::mutex mutex;
59 std::shared_ptr<const LogSink> sink;
60 std::atomic<bool> active{false}; // relaxed fast path: skip the lock when no sink is set
61};
62
63// Intentionally leaked so logging during static destruction stays valid.
65 static LogSinkRegistry* registry = new LogSinkRegistry;
66 return *registry;
67}
68
69inline bool& in_log_sink_dispatch() {
70 static thread_local bool flag = false;
71 return flag;
72}
73
74inline void dispatch_log_sink(LogLevel level, std::string_view category, std::string_view message) {
75 auto& registry = log_sink_registry();
76 if (!registry.active.load(std::memory_order_relaxed))
77 return;
78 bool& busy = in_log_sink_dispatch();
79 if (busy)
80 return; // a sink that logs must not recurse into itself
81
82 std::shared_ptr<const LogSink> sink;
83 {
84 std::lock_guard<std::mutex> lock(registry.mutex);
85 sink = registry.sink;
86 }
87 if (!sink || !*sink)
88 return;
89
90 busy = true;
91 PULP_TRY {
92 (*sink)(level, category, message);
93 }
95 // A misbehaving sink must never take the logging caller down.
96 }
97 busy = false;
98}
99
100inline void log_impl(LogLevel level, std::string_view category, std::string_view message) {
101 const char* prefix = "";
102 switch (level) {
103 case LogLevel::Debug: prefix = "[pulp:debug] "; break;
104 case LogLevel::Info: prefix = "[pulp:info] "; break;
105 case LogLevel::Warning: prefix = "[pulp:warn] "; break;
106 case LogLevel::Error: prefix = "[pulp:error] "; break;
107 }
108
109#if defined(__APPLE__)
110 os_log_type_t type = OS_LOG_TYPE_DEFAULT;
111 switch (level) {
112 case LogLevel::Debug: type = OS_LOG_TYPE_DEBUG; break;
113 case LogLevel::Info: type = OS_LOG_TYPE_INFO; break;
114 case LogLevel::Warning: type = OS_LOG_TYPE_DEFAULT; break;
115 case LogLevel::Error: type = OS_LOG_TYPE_ERROR; break;
116 }
117 // Named subsystem so out-of-process logs (notably an AUv3 extension's)
118 // are capturable via `log stream --predicate 'subsystem ==
119 // "dev.pulp.runtime"'` and Console.app. OS_LOG_DEFAULT routes info/
120 // debug records that are not persisted for extension processes, so
121 // `simctl log show/stream` saw nothing during the #3217 cube
122 // investigation. A function-local static initializes the logger once.
123 static os_log_t pulp_log = os_log_create("dev.pulp.runtime", "runtime");
124 os_log_with_type(pulp_log, type, "%{public}s%{public}s",
125 prefix, std::string(message).c_str());
126#elif defined(_WIN32)
127 auto full = std::string(prefix) + std::string(message) + "\n";
128 OutputDebugStringA(full.c_str());
129#endif
130 // Always also write to stderr
131 std::fprintf(stderr, "%s%.*s\n", prefix,
132 static_cast<int>(message.size()), message.data());
133
134 dispatch_log_sink(level, category, message);
135}
136
137inline void log_impl(LogLevel level, std::string_view message) {
138 log_impl(level, kDefaultLogCategory, message);
139}
140
141} // namespace detail
142
148 auto& registry = detail::log_sink_registry();
149 std::shared_ptr<const LogSink> incoming;
150 if (sink)
151 incoming = std::make_shared<const LogSink>(std::move(sink));
152
153 std::shared_ptr<const LogSink> previous;
154 {
155 std::lock_guard<std::mutex> lock(registry.mutex);
156 previous = std::move(registry.sink);
157 registry.sink = std::move(incoming);
158 registry.active.store(registry.sink != nullptr, std::memory_order_relaxed);
159 }
160 return previous ? *previous : LogSink{};
161}
162
164template <typename... Args>
165void log_with_category(std::string_view category, LogLevel level, std::format_string<Args...> fmt,
166 Args&&... args) {
167 auto msg = std::format(fmt, std::forward<Args>(args)...);
168 detail::log_impl(level, category, msg);
169}
170
171template<typename... Args>
172void log(LogLevel level, std::format_string<Args...> fmt, Args&&... args) {
173 auto msg = std::format(fmt, std::forward<Args>(args)...);
174 detail::log_impl(level, msg);
175}
176
177template<typename... Args>
178void log_debug(std::format_string<Args...> fmt, Args&&... args) {
179 if constexpr (pulp::platform::is_debug) {
180 log(LogLevel::Debug, fmt, std::forward<Args>(args)...);
181 }
182}
183
184template<typename... Args>
185void log_info(std::format_string<Args...> fmt, Args&&... args) {
186 log(LogLevel::Info, fmt, std::forward<Args>(args)...);
187}
188
189template<typename... Args>
190void log_warn(std::format_string<Args...> fmt, Args&&... args) {
191 log(LogLevel::Warning, fmt, std::forward<Args>(args)...);
192}
193
194template<typename... Args>
195void log_error(std::format_string<Args...> fmt, Args&&... args) {
196 log(LogLevel::Error, fmt, std::forward<Args>(args)...);
197}
198
199} // namespace pulp::runtime
Portable try/catch macros for source that must also compile with C++ exceptions disabled (-fno-except...
#define PULP_TRY
Definition exceptions.hpp:42
#define PULP_CATCH_ALL
Definition exceptions.hpp:43
constexpr bool is_debug
Definition detect.hpp:97
Definition assert.hpp:29
bool & in_log_sink_dispatch()
Definition log.hpp:69
void log_impl(LogLevel level, std::string_view category, std::string_view message)
Definition log.hpp:100
LogSinkRegistry & log_sink_registry()
Definition log.hpp:64
void dispatch_log_sink(LogLevel level, std::string_view category, std::string_view message)
Definition log.hpp:74
Definition generated_event_source.hpp:33
LogLevel
Definition log.hpp:42
@ Warning
Definition log.hpp:42
@ Info
Definition log.hpp:42
@ Error
Definition log.hpp:42
@ Debug
Definition log.hpp:42
void log_with_category(std::string_view category, LogLevel level, std::format_string< Args... > fmt, Args &&... args)
Log with an explicit category (a subsystem tag such as "audio" or "net").
Definition log.hpp:165
constexpr std::string_view kDefaultLogCategory
Category used by log_info and the other level helpers.
Definition log.hpp:53
std::function< void(LogLevel level, std::string_view category, std::string_view message)> LogSink
Receives every record after the platform writers.
Definition log.hpp:49
void log_warn(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:190
LogSink set_log_sink(LogSink sink)
Install sink as the process-wide log sink and return the previous one (empty if none).
Definition log.hpp:147
void log_error(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:195
void log(LogLevel level, std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:172
void log_info(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:185
void log_debug(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:178
std::mutex mutex
Definition log.hpp:58
std::atomic< bool > active
Definition log.hpp:60
std::shared_ptr< const LogSink > sink
Definition log.hpp:59