commit 7fb16206b66e2aab72609ff21cb4d3294baafebf
parent 3f811b7b0e31ebbfabaa773274a4ded27a70873c
Author: triesap <tyson@radroots.org>
Date: Mon, 23 Mar 2026 02:03:04 +0000
logging: add myc runtime log smoke coverage
- add dated log path coverage for myc logging options
- add a myc run smoke test that waits for a non-empty dated log file
- generate isolated temp env and identity fixtures for runtime logging verification
- keep coverage aligned with the canonical daily service log layout
Diffstat:
| M | Cargo.lock | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/logging.rs | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++-- |
| A | tests/logging_run.rs | | | 109 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 259 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -51,6 +51,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -313,6 +322,19 @@ dependencies = [
]
[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -418,6 +440,12 @@ dependencies = [
]
[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -779,6 +807,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1395,6 +1447,7 @@ dependencies = [
name = "radroots-log"
version = "0.1.0-alpha.1"
dependencies = [
+ "chrono",
"thiserror 1.0.69",
"tracing",
"tracing-appender",
@@ -2432,12 +2485,65 @@ dependencies = [
]
[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/logging.rs b/src/logging.rs
@@ -1,6 +1,6 @@
-use radroots_log::{LogFileLayout, LoggingOptions};
use crate::config::MycLoggingConfig;
use crate::error::MycError;
+use radroots_log::{LogFileLayout, LoggingOptions};
pub fn init_logging(config: &MycLoggingConfig) -> Result<(), MycError> {
radroots_log::init_logging(LoggingOptions {
@@ -15,6 +15,7 @@ pub fn init_logging(config: &MycLoggingConfig) -> Result<(), MycError> {
#[cfg(test)]
mod tests {
+ use radroots_log::{LogFileLayout, LoggingOptions};
use std::path::PathBuf;
use crate::config::MycConfig;
@@ -36,7 +37,48 @@ MYC_TRANSPORT_CONNECT_TIMEOUT_SECS=10
)
.expect("config");
- assert_eq!(config.logging.output_dir, Some(PathBuf::from("/tmp/myc-logs")));
+ assert_eq!(
+ config.logging.output_dir,
+ Some(PathBuf::from("/tmp/myc-logs"))
+ );
assert!(!config.logging.stdout);
}
+
+ #[test]
+ fn logging_options_resolve_real_dated_file_path() {
+ let config = MycConfig::from_env_str(
+ r#"
+MYC_LOGGING_FILTER=info,myc=debug
+MYC_LOGGING_OUTPUT_DIR=/tmp/myc-logs
+MYC_LOGGING_STDOUT=false
+MYC_PATHS_STATE_DIR=/tmp/myc
+MYC_PATHS_SIGNER_IDENTITY_PATH=/tmp/signer.json
+MYC_PATHS_USER_IDENTITY_PATH=/tmp/user.json
+MYC_DISCOVERY_ENABLED=false
+MYC_TRANSPORT_ENABLED=false
+MYC_TRANSPORT_CONNECT_TIMEOUT_SECS=10
+ "#,
+ )
+ .expect("config");
+
+ let path = LoggingOptions {
+ dir: config.logging.output_dir.clone(),
+ file_name: "log".to_owned(),
+ stdout: config.logging.stdout,
+ default_level: Some(config.logging.filter.clone()),
+ file_layout: LogFileLayout::DatedFileName,
+ }
+ .resolved_current_log_file_path()
+ .expect("resolved log path");
+
+ assert_eq!(
+ path.parent(),
+ Some(PathBuf::from("/tmp/myc-logs").as_path())
+ );
+ assert!(
+ path.file_name()
+ .and_then(|value| value.to_str())
+ .is_some_and(|value| value.ends_with(".log"))
+ );
+ }
}
diff --git a/tests/logging_run.rs b/tests/logging_run.rs
@@ -0,0 +1,109 @@
+use radroots_identity::RadrootsIdentity;
+use radroots_log::{LogFileLayout, LoggingOptions};
+use std::path::Path;
+use std::process::{Child, Command, Stdio};
+use std::thread;
+use std::time::{Duration, Instant};
+
+fn write_test_identity(path: &Path, secret_key: &str) {
+ RadrootsIdentity::from_secret_key_str(secret_key)
+ .expect("identity from secret")
+ .save_json(path)
+ .expect("write identity");
+}
+
+fn wait_for_non_empty_log(path: &Path, timeout: Duration) -> Result<String, String> {
+ let deadline = Instant::now() + timeout;
+ while Instant::now() < deadline {
+ match std::fs::read_to_string(path) {
+ Ok(contents) if !contents.trim().is_empty() => return Ok(contents),
+ Ok(_) => {}
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
+ Err(error) => return Err(format!("failed to read log file: {error}")),
+ }
+ thread::sleep(Duration::from_millis(100));
+ }
+ Err(format!(
+ "timed out waiting for non-empty log file at {}",
+ path.display()
+ ))
+}
+
+fn kill_child(child: &mut Child) {
+ let _ = child.kill();
+ let _ = child.wait();
+}
+
+#[test]
+fn myc_run_writes_non_empty_dated_log_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let state_dir = temp.path().join("state");
+ let logs_dir = temp.path().join("logs");
+ let signer_path = temp.path().join("signer.json");
+ let user_path = temp.path().join("user.json");
+ let env_path = temp.path().join("myc.env");
+
+ write_test_identity(
+ signer_path.as_path(),
+ "1111111111111111111111111111111111111111111111111111111111111111",
+ );
+ write_test_identity(
+ user_path.as_path(),
+ "2222222222222222222222222222222222222222222222222222222222222222",
+ );
+
+ std::fs::write(
+ &env_path,
+ format!(
+ "MYC_SERVICE_INSTANCE_NAME=myc-test\n\
+MYC_LOGGING_FILTER=info,myc=info\n\
+MYC_LOGGING_OUTPUT_DIR={}\n\
+MYC_LOGGING_STDOUT=false\n\
+MYC_PATHS_STATE_DIR={}\n\
+MYC_PATHS_SIGNER_IDENTITY_PATH={}\n\
+MYC_PATHS_USER_IDENTITY_PATH={}\n\
+MYC_DISCOVERY_ENABLED=false\n\
+MYC_TRANSPORT_ENABLED=false\n\
+MYC_TRANSPORT_CONNECT_TIMEOUT_SECS=10\n",
+ logs_dir.display(),
+ state_dir.display(),
+ signer_path.display(),
+ user_path.display(),
+ ),
+ )
+ .expect("write env");
+
+ let expected_log_path = LoggingOptions {
+ dir: Some(logs_dir.clone()),
+ file_name: "log".to_owned(),
+ stdout: false,
+ default_level: Some("info,myc=info".to_owned()),
+ file_layout: LogFileLayout::DatedFileName,
+ }
+ .resolved_current_log_file_path()
+ .expect("resolved current log path");
+
+ let mut child = Command::new(env!("CARGO_BIN_EXE_myc"))
+ .arg("--env-file")
+ .arg(&env_path)
+ .arg("run")
+ .stdout(Stdio::null())
+ .stderr(Stdio::piped())
+ .spawn()
+ .expect("spawn myc");
+
+ let contents = match wait_for_non_empty_log(expected_log_path.as_path(), Duration::from_secs(5))
+ {
+ Ok(contents) => contents,
+ Err(error) => {
+ kill_child(&mut child);
+ panic!("{error}");
+ }
+ };
+
+ kill_child(&mut child);
+
+ assert!(expected_log_path.exists());
+ assert!(contents.contains("logging initialized"));
+ assert!(contents.contains("myc runtime bootstrapped"));
+}