logging.rs 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. use std::fs::File;
  2. use std::io::{BufRead, BufReader};
  3. use std::path::{Path, PathBuf};
  4. use tracing_appender::non_blocking::WorkerGuard;
  5. use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
  6. const MAX_LOG_AGE_DAYS: u64 = 7;
  7. const TAIL_LINES: usize = 1000;
  8. static LOG_PATH: std::sync::OnceLock<PathBuf> = std::sync::OnceLock::new();
  9. pub fn init(log_dir: &Path) -> WorkerGuard {
  10. std::fs::create_dir_all(log_dir).expect("failed to create log directory");
  11. cleanup(log_dir);
  12. let timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S");
  13. let filename = format!("opencode-desktop_{timestamp}.log");
  14. let log_path = log_dir.join(&filename);
  15. LOG_PATH
  16. .set(log_path.clone())
  17. .expect("logging already initialized");
  18. let file = File::create(&log_path).expect("failed to create log file");
  19. let (non_blocking, guard) = tracing_appender::non_blocking(file);
  20. let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
  21. if cfg!(debug_assertions) {
  22. EnvFilter::new("opencode_lib=debug,opencode_desktop=debug,sidecar=debug")
  23. } else {
  24. EnvFilter::new("opencode_lib=info,opencode_desktop=info,sidecar=info")
  25. }
  26. });
  27. tracing_subscriber::registry()
  28. .with(filter)
  29. .with(fmt::layer().with_writer(std::io::stderr))
  30. .with(
  31. fmt::layer()
  32. .with_writer(non_blocking)
  33. .with_ansi(false),
  34. )
  35. .init();
  36. guard
  37. }
  38. pub fn tail() -> String {
  39. let Some(path) = LOG_PATH.get() else {
  40. return String::new();
  41. };
  42. let Ok(file) = File::open(path) else {
  43. return String::new();
  44. };
  45. let lines: Vec<String> = BufReader::new(file)
  46. .lines()
  47. .map_while(Result::ok)
  48. .collect();
  49. let start = lines.len().saturating_sub(TAIL_LINES);
  50. lines[start..].join("\n")
  51. }
  52. fn cleanup(log_dir: &Path) {
  53. let cutoff = std::time::SystemTime::now()
  54. - std::time::Duration::from_secs(MAX_LOG_AGE_DAYS * 24 * 60 * 60);
  55. let Ok(entries) = std::fs::read_dir(log_dir) else {
  56. return;
  57. };
  58. for entry in entries.flatten() {
  59. if let Ok(meta) = entry.metadata()
  60. && let Ok(modified) = meta.modified()
  61. && modified < cutoff
  62. {
  63. let _ = std::fs::remove_file(entry.path());
  64. }
  65. }
  66. }