thread_local_logger.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /// A logger that is local to each thread.
  2. ///
  3. /// Logger as defined by the `log` crate is a global construct. A logger is
  4. /// shared by the whole process and all of its threads. This makes sense for an
  5. /// application.
  6. ///
  7. /// However, a global logger is rarely useful for tests. `cargo test` creates
  8. /// one process for each test source file, and runs each test method in a
  9. /// separate thread. By default, logs of different tests will be mixed together,
  10. /// which is less useful.
  11. ///
  12. /// A similar problem exists in tests for `stdout` and `stderr`. Therefore,
  13. /// `libtest` implemented something called `output capture`. Internally in crate
  14. /// `std::io`, output `stdout` and `stderr` of a thread can be configured to
  15. /// redirected to a sink of type `Arc<Mutex<Vec<u8>>>`. The sink is stored in a
  16. /// `thread_local` variable. For each test method, `libtest` creates a sink
  17. /// specific to the test and stores it in the main test thread. The output of
  18. /// the main test thread will be redirected to the sink by `std::io`. Later in
  19. /// `std::thread::spawn()`, when a new thread is created, the sink is copied to
  20. /// the new thread's local variable. Thus, the output of the new thread is also
  21. /// redirected to the same sink. That is how tests output are captured.
  22. ///
  23. /// We took a similar approach by storing a logger in each thread, and copy the
  24. /// logger to all sub-threads. Unfortunately we do not have access to methods
  25. /// like `std::thread::spawn()`, thus the copying can only be done manually.
  26. use std::cell::RefCell;
  27. use std::sync::{Arc, Once};
  28. use log::{Log, Metadata, Record};
  29. use std::fmt::Formatter;
  30. use std::ops::Deref;
  31. struct GlobalLogger;
  32. #[cfg(not(feature = "must-log"))]
  33. #[derive(Clone)]
  34. pub struct LocalLogger(Arc<dyn Log>);
  35. #[cfg(feature = "must-log")]
  36. #[derive(Clone)]
  37. pub struct LocalLogger(Option<Arc<dyn Log>>);
  38. thread_local!(static LOCAL_LOGGER: RefCell<LocalLogger> = Default::default());
  39. /// Initialized the global logger used by the `log` crate.
  40. ///
  41. /// This method can be called multiple times by different tests. The logger is
  42. /// only initialized once and it works for all tests and all threads.
  43. pub fn global_init_once() {
  44. static GLOBAL_LOGGER: GlobalLogger = GlobalLogger;
  45. static INIT_LOG_ONCE: Once = Once::new();
  46. INIT_LOG_ONCE.call_once(|| {
  47. log::set_logger(&GLOBAL_LOGGER)
  48. .expect("Set global logger should never fail");
  49. // Enable all logging globally. Each local logger should have its own
  50. // filtering, which could be less efficient.
  51. log::set_max_level(log::LevelFilter::max());
  52. });
  53. }
  54. /// Set the logger for the main test thread.
  55. ///
  56. /// This method should only be called once at the beginning of each test.
  57. ///
  58. /// Before creating a child thread, use [`LocalLogger::inherit()`] to copy the
  59. /// logger from the parent thread. Move the copied logger to the child thread
  60. /// and use [`LocalLogger::attach()`] to set the logger for the child thread.
  61. pub fn thread_init<T: 'static + Log>(logger: T) {
  62. global_init_once();
  63. #[cfg(not(feature = "must-log"))]
  64. self::set(LocalLogger(Arc::new(logger)));
  65. #[cfg(feature = "must-log")]
  66. self::set(LocalLogger(Some(Arc::new(logger))));
  67. }
  68. #[doc(hidden)]
  69. pub fn get() -> LocalLogger {
  70. let result = LOCAL_LOGGER.with(|inner| inner.borrow().clone());
  71. let _ = result.deref();
  72. result
  73. }
  74. #[doc(hidden)]
  75. pub fn set(logger: LocalLogger) {
  76. LOCAL_LOGGER.with(|inner| inner.replace(logger));
  77. }
  78. #[doc(hidden)]
  79. pub fn reset() {
  80. self::set(LocalLogger::default())
  81. }
  82. impl LocalLogger {
  83. /// Inherit the logger from the current thread.
  84. pub fn inherit() -> Self {
  85. get()
  86. }
  87. /// Set the logger of this thread to `self`.
  88. pub fn attach(self) {
  89. set(self)
  90. }
  91. }
  92. impl Default for LocalLogger {
  93. fn default() -> Self {
  94. #[cfg(not(feature = "must-log"))]
  95. {
  96. Self(Arc::new(NopLogger))
  97. }
  98. #[cfg(feature = "must-log")]
  99. Self(None)
  100. }
  101. }
  102. impl std::ops::Deref for LocalLogger {
  103. type Target = dyn Log;
  104. fn deref(&self) -> &Self::Target {
  105. #[cfg(not(feature = "must-log"))]
  106. {
  107. self.0.deref()
  108. }
  109. #[cfg(feature = "must-log")]
  110. self.0
  111. .as_ref()
  112. .expect("Local logger must be set before use")
  113. .as_ref()
  114. }
  115. }
  116. impl std::fmt::Debug for LocalLogger {
  117. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  118. f.write_str("thread local logger")
  119. }
  120. }
  121. impl Log for GlobalLogger {
  122. fn enabled(&self, metadata: &Metadata) -> bool {
  123. LOCAL_LOGGER.with(|inner| inner.borrow().enabled(metadata))
  124. }
  125. fn log(&self, record: &Record) {
  126. LOCAL_LOGGER.with(|inner| inner.borrow().log(record))
  127. }
  128. fn flush(&self) {
  129. LOCAL_LOGGER.with(|inner| inner.borrow().flush())
  130. }
  131. }
  132. struct NopLogger;
  133. impl Log for NopLogger {
  134. fn enabled(&self, _: &Metadata) -> bool {
  135. false
  136. }
  137. fn log(&self, _: &Record) {}
  138. fn flush(&self) {}
  139. }