diff --git a/src/anolisa/crates/anolisa-core/src/telemetry.rs b/src/anolisa/crates/anolisa-core/src/telemetry.rs index fd284e8bb..f60d5515a 100644 --- a/src/anolisa/crates/anolisa-core/src/telemetry.rs +++ b/src/anolisa/crates/anolisa-core/src/telemetry.rs @@ -59,6 +59,8 @@ pub struct TelemetryConfig { pub ops_user_defined_ids: Vec, /// Ops directory for component .jsonl files pub ops_dir: PathBuf, + /// logrotate config path for ops .jsonl files + pub logrotate_config_path: PathBuf, /// Instance ID cache path pub instance_id_cache_path: PathBuf, /// Path to `/etc/machine-id` (used as instance ID fallback) @@ -108,6 +110,7 @@ impl Default for TelemetryConfig { ops_sls_account_id: default_ops_id, ops_user_defined_ids: vec!["anolisa-livetrace".into()], ops_dir: PathBuf::from("/var/log/anolisa/sls/ops"), + logrotate_config_path: PathBuf::from("/etc/logrotate.d/anolisa"), instance_id_cache_path: PathBuf::from("/var/lib/anolisa/instance-id.cache"), machine_id_path: PathBuf::from("/etc/machine-id"), release_path: PathBuf::from("/etc/anolisa-release"), @@ -213,10 +216,11 @@ impl TelemetryStarter { installer.configure_ops_user_defined_ids(), )?; - // 7-10. Ops telemetry setup + // 7-11. Ops telemetry setup let ops = OpsTelemetrySetup::new(&self.config); Self::run_step("create ops directory", ops.create_ops_dir())?; Self::run_step("create ops jsonl files", ops.create_ops_jsonl_files())?; + Self::run_step("setup logrotate", ops.setup_logrotate())?; Self::run_step("enable sls log marker", ops.enable_sls_log_marker())?; let instance_info = Self::run_step( "write instance snapshot", @@ -248,9 +252,11 @@ impl TelemetryStarter { /// /// Called after `anolisa unregister` successfully writes register.json. /// Note: does not uninstall ilogtail itself, only revokes upload configuration. - /// Ops directory and .jsonl files are preserved (components still write locally). + /// Ops directory, .jsonl files and logrotate config are preserved + /// (components still write locally, disk size still needs to be bounded). pub fn stop(&self) -> Result<(), TelemetryError> { // 0. Remove agentsight SLS log marker file + // (logrotate config is preserved — components still write to .jsonl files) let ops = OpsTelemetrySetup::new(&self.config); ops.remove_sls_log_marker()?; @@ -283,6 +289,7 @@ pub(crate) fn test_config(dir: &tempfile::TempDir) -> TelemetryConfig { ops_sls_account_id: "987654321".into(), ops_user_defined_ids: vec!["anolisa-livetrace".into()], ops_dir: dir.path().join("ops"), + logrotate_config_path: dir.path().join("logrotate-anolisa"), instance_id_cache_path: dir.path().join("instance-id.cache"), machine_id_path: dir.path().join("machine-id"), release_path: dir.path().join("anolisa-release"), diff --git a/src/anolisa/crates/anolisa-core/src/telemetry/ilogtail.rs b/src/anolisa/crates/anolisa-core/src/telemetry/ilogtail.rs index 34a580dc4..886736459 100644 --- a/src/anolisa/crates/anolisa-core/src/telemetry/ilogtail.rs +++ b/src/anolisa/crates/anolisa-core/src/telemetry/ilogtail.rs @@ -220,12 +220,12 @@ impl<'a> IlogtailInstaller<'a> { .map_err(|e| TelemetryError::Command(format!("chmod failed: {e}")))?; let install = if region_info.use_internal { - Command::new("sh") + Command::new("bash") .args([&tmp_script, "install", region_id.as_str()]) .output() } else { let public_region = format!("{region_id}-internet"); - Command::new("sh") + Command::new("bash") .args([tmp_script.as_str(), "install", &public_region]) .output() }; diff --git a/src/anolisa/crates/anolisa-core/src/telemetry/ops_telemetry.rs b/src/anolisa/crates/anolisa-core/src/telemetry/ops_telemetry.rs index 508b6e9f2..15143f3af 100644 --- a/src/anolisa/crates/anolisa-core/src/telemetry/ops_telemetry.rs +++ b/src/anolisa/crates/anolisa-core/src/telemetry/ops_telemetry.rs @@ -3,6 +3,7 @@ //! Responsibilities: //! - Create `/var/log/anolisa/sls/ops/` directory //! - Pre-create component `.jsonl` files +//! - Configure logrotate for ops `.jsonl` files //! - Enable / remove agentsight SLS log marker //! - Write `instance.jsonl` snapshot @@ -66,6 +67,41 @@ impl<'a> OpsTelemetrySetup<'a> { Ok(()) } + /// Write logrotate config for ops `.jsonl` files. + /// + /// Creates `/etc/logrotate.d/anolisa` with a `size 30M / rotate 1` policy + /// using rename-mode rotation so ilogtail inode offsets are preserved. + pub fn setup_logrotate(&self) -> Result<(), TelemetryError> { + let config_path = &self.config.logrotate_config_path; + if let Some(parent) = config_path.parent() { + fs::create_dir_all(parent)?; + } + + let glob = self.config.ops_dir.join("*.jsonl"); + let content = format!( + "{glob} {{\n size 30M\n rotate 1\n missingok\n notifempty\n create 0666 root root\n}}\n", + glob = glob.display() + ); + fs::write(config_path, content)?; + + #[cfg(target_os = "linux")] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(config_path, fs::Permissions::from_mode(0o644))?; + } + + Ok(()) + } + + /// Remove logrotate config for ops `.jsonl` files. + pub fn remove_logrotate(&self) -> Result<(), TelemetryError> { + let config_path = &self.config.logrotate_config_path; + if config_path.exists() { + fs::remove_file(config_path)?; + } + Ok(()) + } + /// Enable agentsight SLS log marker file pub fn enable_sls_log_marker(&self) -> Result<(), TelemetryError> { let marker = &self.config.sls_log_marker; @@ -164,6 +200,52 @@ mod tests { assert!(instance_file.exists()); } + #[test] + fn test_setup_logrotate_creates_config() { + let dir = TempDir::new().unwrap(); + let cfg = test_config(&dir); + let ops = OpsTelemetrySetup::new(&cfg); + ops.setup_logrotate().unwrap(); + + let content = fs::read_to_string(&cfg.logrotate_config_path).unwrap(); + assert!(content.contains("size 30M")); + assert!(content.contains("rotate 1")); + assert!(content.contains("create 0666 root root")); + assert!(content.contains("*.jsonl")); + } + + #[test] + fn test_setup_logrotate_idempotent() { + let dir = TempDir::new().unwrap(); + let cfg = test_config(&dir); + let ops = OpsTelemetrySetup::new(&cfg); + ops.setup_logrotate().unwrap(); + ops.setup_logrotate().unwrap(); + + let content = fs::read_to_string(&cfg.logrotate_config_path).unwrap(); + assert_eq!(content.matches("size 30M").count(), 1); + } + + #[test] + fn test_remove_logrotate_noop_when_absent() { + let dir = TempDir::new().unwrap(); + let cfg = test_config(&dir); + let ops = OpsTelemetrySetup::new(&cfg); + assert!(ops.remove_logrotate().is_ok()); + } + + #[test] + fn test_remove_logrotate_deletes_config() { + let dir = TempDir::new().unwrap(); + let cfg = test_config(&dir); + let ops = OpsTelemetrySetup::new(&cfg); + ops.setup_logrotate().unwrap(); + assert!(cfg.logrotate_config_path.exists()); + + ops.remove_logrotate().unwrap(); + assert!(!cfg.logrotate_config_path.exists()); + } + #[test] fn test_enable_and_remove_sls_log_marker() { let dir = TempDir::new().unwrap();