quill
LoggerManager.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/Common.h"
11 #include "quill/core/LogLevel.h"
12 #include "quill/core/LoggerBase.h"
13 #include "quill/core/PatternFormatterOptions.h"
14 #include "quill/core/QuillError.h"
15 #include "quill/core/Spinlock.h"
16 
17 #include <algorithm>
18 #include <atomic>
19 #include <cstdlib>
20 #include <initializer_list>
21 #include <memory>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <vector>
26 
27 QUILL_BEGIN_NAMESPACE
28 
30 QUILL_BEGIN_EXPORT
31 
32 class Sink;
33 class UserClockSource;
34 
35 QUILL_END_EXPORT
36 
37 namespace detail
38 {
40 {
41 public:
42  LoggerManager(LoggerManager const&) = delete;
43  LoggerManager& operator=(LoggerManager const&) = delete;
44 
45  /***/
46  QUILL_EXPORT static LoggerManager& instance() noexcept
47  {
48  static LoggerManager instance;
49  return instance;
50  }
51 
52  /***/
53  QUILL_NODISCARD LoggerBase* get_logger(std::string const& logger_name) const
54  {
55  LockGuard const lock{_spinlock};
56  LoggerBase* logger = _find_logger(logger_name);
57  return logger && logger->is_valid_logger() ? logger : nullptr;
58  }
59 
60  /***/
61  QUILL_NODISCARD std::vector<LoggerBase*> get_all_loggers() const
62  {
63  LockGuard const lock{_spinlock};
64 
65  std::vector<LoggerBase*> loggers;
66 
67  for (auto const& elem : _loggers)
68  {
69  // we can not add invalidated loggers as they can be removed at any time
70  if (elem->is_valid_logger())
71  {
72  loggers.push_back(elem.get());
73  }
74  }
75 
76  return loggers;
77  }
78 
79  /***/
80  QUILL_NODISCARD LoggerBase* get_valid_logger(std::string_view exclude_logger_substr = {}) const
81  {
82  // Retrieves any valid logger without the need for constructing a vector
83  LockGuard const lock{_spinlock};
84 
85  for (auto const& elem : _loggers)
86  {
87  // we can not add invalidated loggers as they can be removed at any time
88  if (elem->is_valid_logger())
89  {
90  // Return the logger only if it does not match the exclude_logger_substr
91  if (exclude_logger_substr.empty() ||
92  elem->get_logger_name().find(exclude_logger_substr) == std::string::npos)
93  {
94  // Return this logger if it's valid and not excluded
95  return elem.get();
96  }
97  }
98  }
99 
100  return nullptr;
101  }
102 
103  /***/
104  QUILL_NODISCARD LoggerBase* get_valid_logger(std::vector<std::string> const& exclude_logger_substrs) const
105  {
106  LockGuard const lock{_spinlock};
107  for (auto const& elem : _loggers)
108  {
109  if (elem->is_valid_logger())
110  {
111  bool excluded = false;
112  for (auto const& exclude_substr : exclude_logger_substrs)
113  {
114  if (!exclude_substr.empty() && elem->get_logger_name().find(exclude_substr) != std::string::npos)
115  {
116  excluded = true;
117  break;
118  }
119  }
120 
121  if (!excluded)
122  {
123  return elem.get();
124  }
125  }
126  }
127  return nullptr;
128  }
129 
130  /***/
131  QUILL_NODISCARD size_t get_number_of_loggers() const noexcept
132  {
133  LockGuard const lock{_spinlock};
134  return _loggers.size();
135  }
136 
140  template <typename TCallback>
141  void for_each_logger(TCallback cb) const
142  {
143  LockGuard const lock{_spinlock};
144 
145  for (auto const& elem : _loggers)
146  {
147  // Here we do not check for valid_logger() like in get_all_loggers() because this
148  // function is only called by the backend
149  if (cb(elem.get()))
150  {
151  // When the callback returns true stop the loop early
152  break;
153  }
154  }
155  }
156 
161  template <typename TLogger>
162  LoggerBase* create_logger(std::string const& logger_name, std::vector<std::shared_ptr<Sink>> sinks,
163  PatternFormatterOptions const& pattern_formatter_options,
164  ClockSourceType clock_source, UserClockSource* user_clock)
165  {
166  LockGuard const lock{_spinlock};
167 
168  LoggerBase* logger_ptr = _find_logger(logger_name);
169 
170  if (logger_ptr && !logger_ptr->is_valid_logger())
171  {
172  QUILL_THROW(QuillError{"Logger with name \"" + logger_name +
173  "\" is pending removal and cannot be recreated until the backend "
174  "completes logger cleanup. Use remove_logger_blocking() if you need "
175  "to recreate the logger synchronously."});
176  }
177 
178  if (logger_ptr)
179  {
180  QUILL_THROW(
181  QuillError{"Logger with name \"" + logger_name +
182  "\" already exists. "
183  "Use create_or_get_logger() if you want to retrieve the existing logger, "
184  "or choose a different name."});
185  }
186 
187  std::unique_ptr<LoggerBase> new_logger{
188  new TLogger{logger_name, static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
189  pattern_formatter_options, clock_source, user_clock}};
190 
191  _insert_logger(static_cast<std::unique_ptr<LoggerBase>&&>(new_logger));
192 
193  logger_ptr = _find_logger(logger_name);
194 
195  if (logger_ptr && _env_log_level)
196  {
197  logger_ptr->set_log_level(*_env_log_level);
198  }
199 
200  QUILL_ASSERT(logger_ptr, "logger_ptr is nullptr in LoggerManager::create_logger()");
201  QUILL_ASSERT(logger_ptr->is_valid_logger(),
202  "logger is not valid in LoggerManager::create_logger()");
203  return logger_ptr;
204  }
205 
211  template <typename TLogger>
212  LoggerBase* create_or_get_logger(std::string const& logger_name, std::vector<std::shared_ptr<Sink>> sinks,
213  PatternFormatterOptions const& pattern_formatter_options,
214  ClockSourceType clock_source, UserClockSource* user_clock)
215  {
216  LockGuard const lock{_spinlock};
217 
218  LoggerBase* logger_ptr = _find_logger(logger_name);
219 
220  if (logger_ptr && !logger_ptr->is_valid_logger())
221  {
222  QUILL_THROW(QuillError{"Logger with name \"" + logger_name +
223  "\" is pending removal and cannot be recreated until the backend "
224  "completes logger cleanup. Use remove_logger_blocking() if you need "
225  "to recreate the logger synchronously."});
226  }
227 
228  if (!logger_ptr)
229  {
230  // If logger pointer is null, create a new logger instance.
231  std::unique_ptr<LoggerBase> new_logger{
232  new TLogger{logger_name, static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
233  pattern_formatter_options, clock_source, user_clock}};
234 
235  _insert_logger(static_cast<std::unique_ptr<LoggerBase>&&>(new_logger));
236 
237  // Although we could directly return .get() from the new_logger here,
238  // we retain this portion of code for additional safety in case of potential re-lookup of
239  // the logger. This section is not performance-critical.
240  logger_ptr = _find_logger(logger_name);
241 
242  if (logger_ptr && _env_log_level)
243  {
244  logger_ptr->set_log_level(*_env_log_level);
245  }
246  }
247 
248  QUILL_ASSERT(logger_ptr, "logger_ptr is nullptr in LoggerManager::get_logger()");
249  QUILL_ASSERT(logger_ptr->is_valid_logger(),
250  "logger is not valid in LoggerManager::get_logger()");
251  return logger_ptr;
252  }
253 
254  /***/
255  template <typename TLogger>
256  LoggerBase* create_or_get_logger(std::string const& logger_name, LoggerBase* source_logger)
257  {
258  if (!source_logger)
259  {
260  return get_logger(logger_name);
261  }
262 
263  return create_or_get_logger<TLogger>(logger_name, source_logger->_sinks, source_logger->_pattern_formatter_options,
264  source_logger->_clock_source, source_logger->_user_clock);
265  }
266 
267  /***/
268  void remove_logger(LoggerBase* logger)
269  {
270  logger->mark_invalid();
271  _has_invalidated_loggers.store(true, std::memory_order_release);
272  }
273 
274  /***/
275  template <typename TCheckQueuesEmpty>
276  void cleanup_invalidated_loggers(TCheckQueuesEmpty check_queues_empty, std::vector<std::string>& removed_loggers)
277  {
278  if (_has_invalidated_loggers.exchange(false, std::memory_order_acq_rel))
279  {
280  // Defer logger destruction until after _spinlock is released. Destroying a logger drops
281  // its sink refcounts, and the last owner runs the sink destructor here, which may invoke
282  // user-provided file-event callbacks (e.g. before_close/after_close). Running arbitrary
283  // user code under _spinlock could stall frontend logger lookups or deadlock.
284  std::vector<std::unique_ptr<LoggerBase>> loggers_to_destroy;
285 
286  {
287  LockGuard const lock{_spinlock};
288  for (auto it = _loggers.begin(); it != _loggers.end();)
289  {
290  if (!it->get()->is_valid_logger())
291  {
292  // invalid logger, check if the logger has any pending records in the queue
293  if (!check_queues_empty())
294  {
295  // we have pending records in the queue, we can not remove the logger yet
296  ++it;
297  _has_invalidated_loggers.store(true, std::memory_order_release);
298  }
299  else
300  {
301  removed_loggers.push_back(it->get()->get_logger_name());
302  loggers_to_destroy.push_back(static_cast<std::unique_ptr<LoggerBase>&&>(*it));
303  it = _loggers.erase(it);
304  }
305  }
306  else
307  {
308  ++it;
309  }
310  }
311  }
312  }
313  }
314 
315  /***/
316  QUILL_NODISCARD bool has_invalidated_loggers() const noexcept
317  {
318  return _has_invalidated_loggers.load(std::memory_order_acquire);
319  }
320 
321  QUILL_ATTRIBUTE_COLD void parse_log_level_from_env()
322  {
323  constexpr char const* field = "QUILL_LOG_LEVEL";
324 
325  std::string log_level;
326 
327 #if defined(_MSC_VER)
328  size_t len = 0;
329  char buf[128]{};
330  bool const ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0;
331  if (ok && len > 0)
332  {
333  log_level = buf;
334  }
335 #else // revert to getenv
336  char* buf = ::getenv(field);
337  if (buf)
338  {
339  log_level = buf;
340  }
341 #endif
342 
343  if (!log_level.empty())
344  {
345  QUILL_TRY { _env_log_level = loglevel_from_string(log_level); }
346 #if !defined(QUILL_NO_EXCEPTIONS)
347  QUILL_CATCH(QuillError const& e)
348  {
349  // Add the environment variable name to the error, otherwise the failure surfaces from
350  // the first logger creation with no hint about where the invalid value came from
351  QUILL_THROW(QuillError{
352  std::string{"invalid \"QUILL_LOG_LEVEL\" environment variable value - "} + e.what()});
353  }
354 #endif
355  }
356  }
357 
358 private:
359  LoggerManager() { parse_log_level_from_env(); }
360 
361  ~LoggerManager() = default;
362 
363  /***/
364  void _insert_logger(std::unique_ptr<LoggerBase> logger)
365  {
366  auto search_it = std::lower_bound(_loggers.begin(), _loggers.end(), logger->get_logger_name(),
367  [](std::unique_ptr<LoggerBase> const& a, std::string const& b)
368  { return a->get_logger_name() < b; });
369 
370  _loggers.insert(search_it, static_cast<std::unique_ptr<LoggerBase>&&>(logger));
371  }
372 
373  /***/
374  QUILL_NODISCARD LoggerBase* _find_logger(std::string const& target) const noexcept
375  {
376  auto search_it = std::lower_bound(_loggers.begin(), _loggers.end(), target,
377  [](std::unique_ptr<LoggerBase> const& a, std::string const& b)
378  { return a->get_logger_name() < b; });
379 
380  return (search_it != std::end(_loggers) && search_it->get()->get_logger_name() == target)
381  ? search_it->get()
382  : nullptr;
383  }
384 
385 private:
386  std::vector<std::unique_ptr<LoggerBase>> _loggers;
387  std::optional<LogLevel> _env_log_level;
388  mutable Spinlock _spinlock;
389  std::atomic<bool> _has_invalidated_loggers{false};
390 };
391 } // namespace detail
392 
393 QUILL_END_NAMESPACE
Base class for sinks.
Definition: Sink.h:46
QUILL_NODISCARD bool is_valid_logger() const noexcept
Checks if the logger is valid.
Definition: LoggerBase.h:115
void for_each_logger(TCallback cb) const
For backend use only.
Definition: LoggerManager.h:141
Configuration options for the PatternFormatter.
Definition: PatternFormatterOptions.h:24
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
Definition: Spinlock.h:18
Definition: LoggerBase.h:39
custom exception
Definition: QuillError.h:47
void set_log_level(LogLevel new_log_level)
Set the log level of the logger.
Definition: LoggerBase.h:140
LoggerBase * create_logger(std::string const &logger_name, std::vector< std::shared_ptr< Sink >> sinks, PatternFormatterOptions const &pattern_formatter_options, ClockSourceType clock_source, UserClockSource *user_clock)
Creates a new logger with the given name.
Definition: LoggerManager.h:162
Definition: Spinlock.h:58
LoggerBase * create_or_get_logger(std::string const &logger_name, std::vector< std::shared_ptr< Sink >> sinks, PatternFormatterOptions const &pattern_formatter_options, ClockSourceType clock_source, UserClockSource *user_clock)
Creates a new logger or returns an existing one with the given name.
Definition: LoggerManager.h:212
Base class that provides a timestamp for log statements based on a user-provided clock source...
Definition: UserClockSource.h:27
Definition: LoggerManager.h:39
void mark_invalid()
This function sets the logger&#39;s validity flag to false, indicating that the logger is no longer valid...
Definition: LoggerBase.h:109