logger.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*************************************************************************
  2. *
  3. * Copyright 2016 Realm Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. **************************************************************************/
  18. #ifndef REALM_UTIL_LOGGER_HPP
  19. #define REALM_UTIL_LOGGER_HPP
  20. #include <realm/util/features.h>
  21. #include <realm/util/thread.hpp>
  22. #include <realm/util/file.hpp>
  23. #include <cstring>
  24. #include <ostream>
  25. #include <string>
  26. #include <utility>
  27. namespace realm::util {
  28. /// All messages logged with a level that is lower than the current threshold
  29. /// will be dropped. For the sake of efficiency, this test happens before the
  30. /// message is formatted. This class allows for the log level threshold to be
  31. /// changed over time and any subclasses will share the same reference to a
  32. /// log level threshold instance. The default log level threshold is
  33. /// Logger::Level::info and is defined by Logger::default_log_level.
  34. ///
  35. /// The Logger threshold level is intrinsically thread safe since it uses an
  36. /// atomic to store the value. However, the do_log() operation is not, so it
  37. /// is up to the subclass to ensure thread safety of the output operation.
  38. ///
  39. /// Examples:
  40. ///
  41. /// logger.error("Overlong message from master coordinator");
  42. /// logger.info("Listening for peers on %1:%2", listen_address, listen_port);
  43. class Logger {
  44. public:
  45. template <class... Params>
  46. void trace(const char* message, Params&&...);
  47. template <class... Params>
  48. void debug(const char* message, Params&&...);
  49. template <class... Params>
  50. void detail(const char* message, Params&&...);
  51. template <class... Params>
  52. void info(const char* message, Params&&...);
  53. template <class... Params>
  54. void warn(const char* message, Params&&...);
  55. template <class... Params>
  56. void error(const char* message, Params&&...);
  57. template <class... Params>
  58. void fatal(const char* message, Params&&...);
  59. /// Specifies criticality when passed to log(). Functions as a criticality
  60. /// threshold when returned from LevelThreshold::get().
  61. ///
  62. /// error Be silent unless when there is an error.
  63. /// warn Be silent unless when there is an error or a warning.
  64. /// info Reveal information about what is going on, but in a
  65. /// minimalistic fashion to avoid general overhead from logging
  66. /// and to keep volume down.
  67. /// detail Same as 'info', but prioritize completeness over minimalism.
  68. /// debug Reveal information that can aid debugging, no longer paying
  69. /// attention to efficiency.
  70. /// trace A version of 'debug' that allows for very high volume
  71. /// output.
  72. // equivalent to realm_log_level_e in realm.h and must be kept in sync -
  73. // this is enforced in logging.cpp.
  74. enum class Level { all = 0, trace = 1, debug = 2, detail = 3, info = 4, warn = 5, error = 6, fatal = 7, off = 8 };
  75. template <class... Params>
  76. void log(Level, const char* message, Params&&...);
  77. virtual Level get_level_threshold() const noexcept
  78. {
  79. // Don't need strict ordering, mainly that the gets/sets are atomic
  80. return m_level_threshold.load(std::memory_order_relaxed);
  81. }
  82. virtual void set_level_threshold(Level level) noexcept
  83. {
  84. // Don't need strict ordering, mainly that the gets/sets are atomic
  85. m_level_threshold.store(level, std::memory_order_relaxed);
  86. }
  87. /// Shorthand for `int(level) >= int(m_level_threshold)`.
  88. inline bool would_log(Level level) const noexcept
  89. {
  90. return static_cast<int>(level) >= static_cast<int>(get_level_threshold());
  91. }
  92. virtual inline ~Logger() noexcept = default;
  93. static void set_default_logger(std::shared_ptr<util::Logger>) noexcept;
  94. static std::shared_ptr<util::Logger>& get_default_logger() noexcept;
  95. static void set_default_level_threshold(Level level) noexcept;
  96. static Level get_default_level_threshold() noexcept;
  97. protected:
  98. // Used by subclasses that link to a base logger
  99. std::shared_ptr<Logger> m_base_logger_ptr;
  100. // Shared level threshold for subclasses that link to a base logger
  101. // See PrefixLogger and ThreadSafeLogger
  102. std::atomic<Level>& m_level_threshold;
  103. Logger() noexcept
  104. : m_level_threshold{m_threshold_base}
  105. , m_threshold_base{get_default_level_threshold()}
  106. {
  107. }
  108. explicit Logger(Level level) noexcept
  109. : m_level_threshold{m_threshold_base}
  110. , m_threshold_base{level}
  111. {
  112. }
  113. explicit Logger(const std::shared_ptr<Logger>& base_logger) noexcept
  114. : m_base_logger_ptr{base_logger}
  115. , m_level_threshold{m_base_logger_ptr->m_level_threshold}
  116. {
  117. }
  118. static void do_log(Logger&, Level, const std::string& message);
  119. virtual void do_log(Level, const std::string& message) = 0;
  120. static const char* get_level_prefix(Level) noexcept;
  121. private:
  122. // Only used by the base Logger class
  123. std::atomic<Level> m_threshold_base;
  124. template <class... Params>
  125. REALM_NOINLINE void do_log(Level, const char* message, Params&&...);
  126. };
  127. template <class C, class T>
  128. std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>&, Logger::Level);
  129. template <class C, class T>
  130. std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>&, Logger::Level&);
  131. /// A logger that writes to STDERR, which is thread safe.
  132. /// Since this class is a subclass of Logger, it maintains its own modifiable log
  133. /// level threshold.
  134. class StderrLogger : public Logger {
  135. public:
  136. StderrLogger() noexcept = default;
  137. StderrLogger(Level level) noexcept
  138. : Logger{level}
  139. {
  140. }
  141. protected:
  142. void do_log(Level, const std::string&) final;
  143. };
  144. /// A logger that writes to a stream. This logger is not thread-safe.
  145. ///
  146. /// Since this class is a subclass of Logger, it maintains its own modifiable log
  147. /// level threshold.
  148. class StreamLogger : public Logger {
  149. public:
  150. explicit StreamLogger(std::ostream&) noexcept;
  151. protected:
  152. void do_log(Level, const std::string&) final;
  153. private:
  154. std::ostream& m_out;
  155. };
  156. /// A logger that writes to a new file. This logger is not thread-safe.
  157. ///
  158. /// Since this class is a subclass of Logger, it maintains its own thread safe log
  159. /// level threshold.
  160. class FileLogger : public StreamLogger {
  161. public:
  162. explicit FileLogger(std::string path);
  163. explicit FileLogger(util::File);
  164. private:
  165. util::File m_file;
  166. util::File::Streambuf m_streambuf;
  167. std::ostream m_out;
  168. };
  169. /// A logger that appends to a file. This logger is not thread-safe.
  170. ///
  171. /// Since this class is a subclass of Logger, it maintains its own thread safe log
  172. /// level threshold.
  173. class AppendToFileLogger : public StreamLogger {
  174. public:
  175. explicit AppendToFileLogger(std::string path);
  176. explicit AppendToFileLogger(util::File);
  177. private:
  178. util::File m_file;
  179. util::File::Streambuf m_streambuf;
  180. std::ostream m_out;
  181. };
  182. /// A thread-safe logger where do_log() is thread safe. The log level is already
  183. /// thread safe since Logger uses an atomic to store the log level threshold.
  184. class ThreadSafeLogger : public Logger {
  185. public:
  186. explicit ThreadSafeLogger(const std::shared_ptr<Logger>& base_logger) noexcept;
  187. protected:
  188. void do_log(Level, const std::string&) final;
  189. private:
  190. Mutex m_mutex;
  191. };
  192. /// A logger that adds a fixed prefix to each message.
  193. class PrefixLogger : public Logger {
  194. public:
  195. // A PrefixLogger must initially be created from a base Logger shared_ptr
  196. PrefixLogger(std::string prefix, const std::shared_ptr<Logger>& base_logger) noexcept;
  197. // Used for chaining a series of prefixes together for logging that combines prefix values
  198. PrefixLogger(std::string prefix, PrefixLogger& chained_logger) noexcept;
  199. protected:
  200. void do_log(Level, const std::string&) final;
  201. private:
  202. const std::string m_prefix;
  203. // The next logger in the chain for chained PrefixLoggers or the base_logger
  204. Logger& m_chained_logger;
  205. };
  206. // Logger with a local log level that is independent of the parent log level threshold
  207. // The LocalThresholdLogger will define its own atomic log level threshold and
  208. // will be unaffected by changes to the log level threshold of the parent.
  209. // In addition, any changes to the log level threshold of this class or any
  210. // subsequent linked loggers will not change the log level threshold of the
  211. // parent. The parent will only be used for outputting log messages.
  212. class LocalThresholdLogger : public Logger {
  213. public:
  214. // A shared_ptr parent must be provided for this class for log output
  215. // Local log level is initialized with the current value from the provided logger
  216. LocalThresholdLogger(const std::shared_ptr<Logger>&);
  217. // A shared_ptr parent must be provided for this class for log output
  218. LocalThresholdLogger(const std::shared_ptr<Logger>&, Level);
  219. void do_log(Logger::Level level, std::string const& message) override;
  220. protected:
  221. std::shared_ptr<Logger> m_chained_logger;
  222. };
  223. /// A logger that essentially performs a noop when logging functions are called
  224. /// The log level threshold for this logger is always Logger::Level::off and
  225. /// cannot be changed.
  226. class NullLogger : public Logger {
  227. public:
  228. NullLogger()
  229. : Logger{Level::off}
  230. {
  231. }
  232. Level get_level_threshold() const noexcept override
  233. {
  234. return Level::off;
  235. }
  236. void set_level_threshold(Level) noexcept override {}
  237. protected:
  238. // Since we don't want to log anything, do_log() does nothing
  239. void do_log(Level, const std::string&) override {}
  240. };
  241. // Implementation
  242. template <class... Params>
  243. inline void Logger::trace(const char* message, Params&&... params)
  244. {
  245. log(Level::trace, message, std::forward<Params>(params)...); // Throws
  246. }
  247. template <class... Params>
  248. inline void Logger::debug(const char* message, Params&&... params)
  249. {
  250. log(Level::debug, message, std::forward<Params>(params)...); // Throws
  251. }
  252. template <class... Params>
  253. inline void Logger::detail(const char* message, Params&&... params)
  254. {
  255. log(Level::detail, message, std::forward<Params>(params)...); // Throws
  256. }
  257. template <class... Params>
  258. inline void Logger::info(const char* message, Params&&... params)
  259. {
  260. log(Level::info, message, std::forward<Params>(params)...); // Throws
  261. }
  262. template <class... Params>
  263. inline void Logger::warn(const char* message, Params&&... params)
  264. {
  265. log(Level::warn, message, std::forward<Params>(params)...); // Throws
  266. }
  267. template <class... Params>
  268. inline void Logger::error(const char* message, Params&&... params)
  269. {
  270. log(Level::error, message, std::forward<Params>(params)...); // Throws
  271. }
  272. template <class... Params>
  273. inline void Logger::fatal(const char* message, Params&&... params)
  274. {
  275. log(Level::fatal, message, std::forward<Params>(params)...); // Throws
  276. }
  277. template <class... Params>
  278. inline void Logger::log(Level level, const char* message, Params&&... params)
  279. {
  280. if (would_log(level))
  281. do_log(level, message, std::forward<Params>(params)...); // Throws
  282. #if REALM_DEBUG
  283. else {
  284. // Do the string formatting even if it won't be logged to hopefully
  285. // catch invalid format strings
  286. static_cast<void>(format(message, std::forward<Params>(params)...)); // Throws
  287. }
  288. #endif
  289. }
  290. inline void Logger::do_log(Logger& logger, Level level, const std::string& message)
  291. {
  292. logger.do_log(level, std::move(message)); // Throws
  293. }
  294. template <class... Params>
  295. void Logger::do_log(Level level, const char* message, Params&&... params)
  296. {
  297. do_log(level, format(message, std::forward<Params>(params)...)); // Throws
  298. }
  299. template <class C, class T>
  300. std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& out, Logger::Level level)
  301. {
  302. switch (level) {
  303. case Logger::Level::all:
  304. out << "all";
  305. return out;
  306. case Logger::Level::trace:
  307. out << "trace";
  308. return out;
  309. case Logger::Level::debug:
  310. out << "debug";
  311. return out;
  312. case Logger::Level::detail:
  313. out << "detail";
  314. return out;
  315. case Logger::Level::info:
  316. out << "info";
  317. return out;
  318. case Logger::Level::warn:
  319. out << "warn";
  320. return out;
  321. case Logger::Level::error:
  322. out << "error";
  323. return out;
  324. case Logger::Level::fatal:
  325. out << "fatal";
  326. return out;
  327. case Logger::Level::off:
  328. out << "off";
  329. return out;
  330. }
  331. REALM_ASSERT(false);
  332. return out;
  333. }
  334. template <class C, class T>
  335. std::basic_istream<C, T>& operator>>(std::basic_istream<C, T>& in, Logger::Level& level)
  336. {
  337. std::basic_string<C, T> str;
  338. auto check = [&](const char* name) {
  339. size_t n = strlen(name);
  340. if (n != str.size())
  341. return false;
  342. for (size_t i = 0; i < n; ++i) {
  343. if (in.widen(name[i]) != str[i])
  344. return false;
  345. }
  346. return true;
  347. };
  348. if (in >> str) {
  349. if (check("all")) {
  350. level = Logger::Level::all;
  351. }
  352. else if (check("trace")) {
  353. level = Logger::Level::trace;
  354. }
  355. else if (check("debug")) {
  356. level = Logger::Level::debug;
  357. }
  358. else if (check("detail")) {
  359. level = Logger::Level::detail;
  360. }
  361. else if (check("info")) {
  362. level = Logger::Level::info;
  363. }
  364. else if (check("warn")) {
  365. level = Logger::Level::warn;
  366. }
  367. else if (check("error")) {
  368. level = Logger::Level::error;
  369. }
  370. else if (check("fatal")) {
  371. level = Logger::Level::fatal;
  372. }
  373. else if (check("off")) {
  374. level = Logger::Level::off;
  375. }
  376. else {
  377. in.setstate(std::ios_base::failbit);
  378. }
  379. }
  380. return in;
  381. }
  382. inline StreamLogger::StreamLogger(std::ostream& out) noexcept
  383. : m_out(out)
  384. {
  385. }
  386. inline FileLogger::FileLogger(std::string path)
  387. : StreamLogger(m_out)
  388. , m_file(path, util::File::mode_Write) // Throws
  389. , m_streambuf(&m_file) // Throws
  390. , m_out(&m_streambuf) // Throws
  391. {
  392. }
  393. inline FileLogger::FileLogger(util::File file)
  394. : StreamLogger(m_out)
  395. , m_file(std::move(file))
  396. , m_streambuf(&m_file) // Throws
  397. , m_out(&m_streambuf) // Throws
  398. {
  399. }
  400. inline AppendToFileLogger::AppendToFileLogger(std::string path)
  401. : StreamLogger(m_out)
  402. , m_file(path, util::File::mode_Append) // Throws
  403. , m_streambuf(&m_file) // Throws
  404. , m_out(&m_streambuf) // Throws
  405. {
  406. }
  407. inline AppendToFileLogger::AppendToFileLogger(util::File file)
  408. : StreamLogger(m_out)
  409. , m_file(std::move(file))
  410. , m_streambuf(&m_file) // Throws
  411. , m_out(&m_streambuf) // Throws
  412. {
  413. }
  414. inline ThreadSafeLogger::ThreadSafeLogger(const std::shared_ptr<Logger>& base_logger) noexcept
  415. : Logger(base_logger)
  416. {
  417. }
  418. // Construct a PrefixLogger from another PrefixLogger object for chaining the prefixes on log output
  419. inline PrefixLogger::PrefixLogger(std::string prefix, PrefixLogger& prefix_logger) noexcept
  420. // Save an alias of the base_logger shared_ptr from the passed in PrefixLogger
  421. : Logger(prefix_logger.m_base_logger_ptr)
  422. , m_prefix{std::move(prefix)}
  423. , m_chained_logger{prefix_logger} // do_log() writes to the chained logger
  424. {
  425. }
  426. // Construct a PrefixLogger from any Logger shared_ptr (PrefixLogger, StdErrLogger, etc.)
  427. // The first PrefixLogger must always be created from a Logger shared_ptr, subsequent PrefixLoggers
  428. // created, will point back to this logger shared_ptr for referencing the level_threshold when
  429. // logging output.
  430. inline PrefixLogger::PrefixLogger(std::string prefix, const std::shared_ptr<Logger>& base_logger) noexcept
  431. : Logger(base_logger) // Save an alias of the passed in base_logger shared_ptr
  432. , m_prefix{std::move(prefix)}
  433. , m_chained_logger{*base_logger} // do_log() writes to the chained logger
  434. {
  435. }
  436. // Construct a LocalThresholdLogger using the current log level value from the parent
  437. inline LocalThresholdLogger::LocalThresholdLogger(const std::shared_ptr<Logger>& base_logger)
  438. : Logger(base_logger->get_level_threshold())
  439. , m_chained_logger{base_logger}
  440. {
  441. }
  442. // Construct a LocalThresholdLogger using the provided log level threshold value
  443. inline LocalThresholdLogger::LocalThresholdLogger(const std::shared_ptr<Logger>& base_logger, Level threshold)
  444. : Logger(threshold)
  445. , m_chained_logger{base_logger}
  446. {
  447. // Verify the passed in shared ptr is not null
  448. REALM_ASSERT(m_chained_logger);
  449. }
  450. } // namespace realm::util
  451. #endif // REALM_UTIL_LOGGER_HPP