quill
SinkManager.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/Filesystem.h"
11 #include "quill/core/QuillError.h"
12 #include "quill/core/Spinlock.h"
13 #include "quill/core/ThreadPrimitives.h"
14 
15 #include <algorithm>
16 #include <cstdint>
17 #include <memory>
18 #include <string>
19 #include <type_traits>
20 #include <vector>
21 
22 QUILL_BEGIN_NAMESPACE
23 
24 QUILL_BEGIN_EXPORT
25 
27 class FileSink;
28 class Sink;
29 
30 QUILL_END_EXPORT
31 
32 namespace detail
33 {
35 {
36 private:
37  struct SinkInfo
38  {
39  explicit SinkInfo() = default;
40  SinkInfo(std::string sid, std::weak_ptr<Sink> sptr)
41  : sink_id(static_cast<std::string&&>(sid)), sink_ptr(static_cast<std::weak_ptr<Sink>&&>(sptr)) {};
42 
43  std::string sink_id;
44  std::weak_ptr<Sink> sink_ptr;
45  };
46 
47 public:
48  SinkManager(SinkManager const&) = delete;
49  SinkManager& operator=(SinkManager const&) = delete;
50 
51  /***/
52  QUILL_EXPORT static SinkManager& instance() noexcept
53  {
54  static SinkManager instance;
55  return instance;
56  }
57 
58  /***/
59  QUILL_NODISCARD std::shared_ptr<Sink> get_sink(std::string const& sink_name) const
60  {
61  // Normalize before taking the lock to avoid blocking filesystem calls under the spinlock
62  std::string normalized_sink_name;
63  QUILL_TRY
64  {
65  normalized_sink_name = detail::normalize_file_sink_path(fs::path{sink_name}, false).string();
66  }
67  QUILL_CATCH(QuillError const&)
68  {
69  // Fallback normalization is best-effort for file-path lookups only.
70  }
71 
72  // The sinks are used by the backend thread, so after their creation we want to avoid mutating their member variables.
73  LockGuard const lock{_spinlock};
74 
75  std::shared_ptr<Sink> sink = _find_sink(sink_name);
76 
77  if (!sink && !normalized_sink_name.empty() && normalized_sink_name != sink_name)
78  {
79  sink = _find_sink(normalized_sink_name);
80  }
81 
82  if (QUILL_UNLIKELY(!sink))
83  {
84  QUILL_THROW(QuillError{"Sink with name \"" + sink_name + "\" does not exist"});
85  }
86 
87  return sink;
88  }
89 
94  template <typename TSink, typename... Args>
95  std::shared_ptr<Sink> create_sink(std::string const& sink_name, Args&&... args)
96  {
97  static_assert(std::is_base_of_v<Sink, TSink>, "TSink must derive from Sink");
98 
99  std::string const sink_id = _normalized_sink_name<TSink>(sink_name);
100 
101  (void)_reserve_sink_id_for_creation(sink_name, sink_id, false);
102  return _create_reserved_sink<TSink>(sink_id, static_cast<Args&&>(args)...);
103  }
104 
110  template <typename TSink, typename... Args>
111  std::shared_ptr<Sink> create_or_get_sink(std::string const& sink_name, Args&&... args)
112  {
113  static_assert(std::is_base_of_v<Sink, TSink>, "TSink must derive from Sink");
114 
115  std::string const sink_id = _normalized_sink_name<TSink>(sink_name);
116 
117  std::shared_ptr<Sink> sink = _reserve_sink_id_for_creation(sink_name, sink_id, true);
118  return sink ? sink : _create_reserved_sink<TSink>(sink_id, static_cast<Args&&>(args)...);
119  }
120 
121  /***/
122  uint32_t cleanup_unused_sinks()
123  {
124  // this needs to take a lock each time. The backend logging thread should be carefully call
125  // it only when needed
126  LockGuard const lock{_spinlock};
127 
128  uint32_t cnt{0};
129  for (auto it = _sinks.begin(); it != _sinks.end();)
130  {
131  if (it->sink_ptr.expired())
132  {
133  it = _sinks.erase(it);
134  ++cnt;
135  }
136  else
137  {
138  ++it;
139  }
140  }
141 
142  return cnt;
143  }
144 
145 private:
146  template <typename TSink>
147  static std::string _normalized_sink_name(std::string const& sink_name)
148  {
149  if constexpr (std::disjunction_v<std::is_same<FileSink, TSink>, std::is_base_of<FileSink, TSink>>)
150  {
151  return detail::normalize_file_sink_path(fs::path{sink_name}).string();
152  }
153  else
154  {
155  return sink_name;
156  }
157  }
158 
159  SinkManager() = default;
160  ~SinkManager() = default;
161 
162  QUILL_NODISCARD std::shared_ptr<Sink> _reserve_sink_id_for_creation(std::string const& sink_name,
163  std::string const& sink_id, bool return_existing)
164  {
165  while (true)
166  {
167  {
168  LockGuard const lock{_spinlock};
169 
170  std::shared_ptr<Sink> sink = _find_sink(sink_id);
171  if (sink)
172  {
173  if (return_existing)
174  {
175  return sink;
176  }
177 
178  QUILL_THROW(
179  QuillError{"Sink with name \"" + sink_name +
180  "\" already exists. "
181  "Use create_or_get_sink() if you want to retrieve the existing sink, "
182  "or choose a different name."});
183  }
184 
185  // Reserve the id, then construct outside the lock because file sinks can run user callbacks.
186  if (_try_mark_sink_pending(sink_id))
187  {
188  return nullptr;
189  }
190  }
191 
193  }
194  }
195 
196  template <typename TSink, typename... Args>
197  std::shared_ptr<Sink> _create_reserved_sink(std::string const& sink_id, Args&&... args)
198  {
199  std::shared_ptr<Sink> sink;
200  QUILL_TRY { sink = _create_sink_instance<TSink>(sink_id, static_cast<Args&&>(args)...); }
201 #if !defined(QUILL_NO_EXCEPTIONS)
202  QUILL_CATCH_ALL()
203  {
204  _remove_pending_sink(sink_id);
205  throw;
206  }
207 #endif
208 
209  _publish_created_sink(sink_id, sink);
210  return sink;
211  }
212 
213  template <typename TSink, typename... Args>
214  static std::shared_ptr<Sink> _create_sink_instance(std::string const& sink_id, Args&&... args)
215  {
216  if constexpr (std::disjunction_v<std::is_same<FileSink, TSink>, std::is_base_of<FileSink, TSink>>)
217  {
218  return std::make_shared<TSink>(sink_id, static_cast<Args&&>(args)...);
219  }
220  else
221  {
222  return std::make_shared<TSink>(static_cast<Args&&>(args)...);
223  }
224  }
225 
226  void _publish_created_sink(std::string const& sink_id, std::shared_ptr<Sink> const& sink)
227  {
228  LockGuard const lock{_spinlock};
229 
230  QUILL_TRY
231  {
232  _insert_sink(sink_id, sink);
233  _erase_pending_sink(sink_id);
234  }
235 #if !defined(QUILL_NO_EXCEPTIONS)
236  QUILL_CATCH_ALL()
237  {
238  _erase_pending_sink(sink_id);
239  throw;
240  }
241 #endif
242  }
243 
244  void _remove_pending_sink(std::string const& sink_id) noexcept
245  {
246  LockGuard const lock{_spinlock};
247  _erase_pending_sink(sink_id);
248  }
249 
250  /***/
251  void _insert_sink(std::string const& sink_name, std::shared_ptr<Sink> const& sink)
252  {
253  auto search_it =
254  std::lower_bound(_sinks.begin(), _sinks.end(), sink_name,
255  [](SinkInfo const& elem, std::string const& b) { return elem.sink_id < b; });
256 
257  if (search_it != _sinks.end() && search_it->sink_id == sink_name && search_it->sink_ptr.expired())
258  {
259  search_it->sink_ptr = sink;
260  return;
261  }
262 
263  _sinks.insert(search_it, SinkInfo{sink_name, sink});
264  }
265 
266  QUILL_NODISCARD bool _try_mark_sink_pending(std::string const& sink_name)
267  {
268  auto search_it = std::lower_bound(_pending_sink_ids.begin(), _pending_sink_ids.end(), sink_name);
269 
270  if (search_it != _pending_sink_ids.end() && *search_it == sink_name)
271  {
272  return false;
273  }
274 
275  _pending_sink_ids.insert(search_it, sink_name);
276  return true;
277  }
278 
279  void _erase_pending_sink(std::string const& sink_name) noexcept
280  {
281  auto search_it = std::lower_bound(_pending_sink_ids.begin(), _pending_sink_ids.end(), sink_name);
282 
283  if (search_it != _pending_sink_ids.end() && *search_it == sink_name)
284  {
285  _pending_sink_ids.erase(search_it);
286  }
287  }
288 
289  /***/
290  QUILL_NODISCARD std::shared_ptr<Sink> _find_sink(std::string const& target) const noexcept
291  {
292  std::shared_ptr<Sink> sink;
293 
294  auto search_it =
295  std::lower_bound(_sinks.begin(), _sinks.end(), target,
296  [](SinkInfo const& elem, std::string const& b) { return elem.sink_id < b; });
297 
298  if (search_it != std::end(_sinks) && search_it->sink_id == target)
299  {
300  sink = search_it->sink_ptr.lock();
301  }
302 
303  return sink;
304  }
305 
306 private:
307  std::vector<SinkInfo> _sinks;
308  std::vector<std::string> _pending_sink_ids;
309  mutable Spinlock _spinlock;
310 };
311 } // namespace detail
312 
313 QUILL_END_NAMESPACE
QUILL_ATTRIBUTE_HOT void sleep_for_ns(uint64_t ns) noexcept
Mirrors std::this_thread::sleep_for(std::chrono::nanoseconds{ns}).
Definition: ThreadPrimitives.h:43
Keep the Windows-specific FileSink split isolated under _WIN32.
Definition: FileSink.h:645
std::shared_ptr< Sink > create_or_get_sink(std::string const &sink_name, Args &&... args)
Creates a new sink or returns an existing one with the given name.
Definition: SinkManager.h:111
Base class for sinks.
Definition: Sink.h:46
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
Definition: Spinlock.h:18
custom exception
Definition: QuillError.h:47
Definition: Spinlock.h:58
Definition: SinkManager.h:34
std::shared_ptr< Sink > create_sink(std::string const &sink_name, Args &&... args)
Creates a new sink with the given name.
Definition: SinkManager.h:95