logging.rs 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. use std::path::PathBuf;
  2. use std::time::SystemTime;
  3. use rand::Rng;
  4. /// Initialize a thread local logger for a test.
  5. ///
  6. /// A log file will be created in [`LOG_DIR`]. The name of the log file will
  7. /// be derived from the fully qualified test method name, plus a timestamp and
  8. /// 10 random characters, e.g. `mod-test_method-1600000000-abcdefghij.log`.
  9. ///
  10. /// The test logger is backed by `env_logger`. By default the test module will
  11. /// have `trace` level logging. Other modules have `info` level logging. See the
  12. /// documentation of `env_logger` on how to configure log levels.
  13. #[macro_export]
  14. macro_rules! init_test_log {
  15. () => {
  16. $crate::init_log(stdext::function_name!())
  17. .expect("Initializing test log should never fail")
  18. };
  19. }
  20. pub const LOG_DIR: &str = "/tmp/ruaft-test-logs/";
  21. /// Initialize a thread local logger for `module`.
  22. ///
  23. /// See [`init_test_log`] for more details.
  24. pub fn init_log(module: &str) -> std::io::Result<PathBuf> {
  25. let module_file = module.replace("::", "-");
  26. let timestamp = SystemTime::now()
  27. .duration_since(SystemTime::UNIX_EPOCH)
  28. .map(|d| d.as_secs())
  29. .unwrap_or(0);
  30. let suffix: String = rand::thread_rng()
  31. .sample_iter(&rand::distributions::Alphanumeric)
  32. .take(10)
  33. .map(char::from)
  34. .collect();
  35. let log_file_name = format!("{module_file}-{timestamp:010}-{suffix}.log");
  36. let log_dir = option_env!("LOG_DIR").unwrap_or(LOG_DIR);
  37. let mut path = PathBuf::from(log_dir);
  38. std::fs::create_dir_all(path.as_path())?;
  39. path.push(log_file_name);
  40. let log_file = std::fs::File::create(path.as_path())?;
  41. {
  42. let mut latest_path = path.clone();
  43. latest_path.pop();
  44. latest_path.push(format!("{module_file}-latest"));
  45. let _ = std::fs::remove_file(latest_path.as_path());
  46. #[cfg(unix)]
  47. let _ = std::os::unix::fs::symlink(path.as_path(), latest_path);
  48. #[cfg(windows)]
  49. let _ = std::os::windows::fs::symlink_file(path.as_path(), latest_path);
  50. }
  51. let module = match module.rfind("::") {
  52. Some(pos) => &module[..pos],
  53. None => module,
  54. };
  55. let env = env_logger::Env::default().default_filter_or("info");
  56. let logger = env_logger::Builder::from_env(env)
  57. .target(env_logger::Target::Pipe(Box::new(log_file)))
  58. .filter(Some(module), log::LevelFilter::Trace)
  59. .format_timestamp_millis()
  60. .is_test(true)
  61. .build();
  62. crate::thread_local_logger::thread_init(logger);
  63. Ok(path)
  64. }