Skip to content

Commit 137b7d8

Browse files
committed
Add systemd structured logging for bootc state changes
1 parent 6bb4c71 commit 137b7d8

File tree

10 files changed

+469
-213
lines changed

10 files changed

+469
-213
lines changed

Cargo.lock

Lines changed: 183 additions & 183 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ tokio-util = { features = ["io-util"], version = "0.7.10" }
6565
toml = "0.9.5"
6666
tracing = "0.1.40"
6767
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
68+
tracing-journald = "0.3.1"
6869
uzers = "0.12"
6970
xshell = "0.2.6"
7071

crates/lib/src/boundimage.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,23 @@ pub(crate) struct ResolvedBoundImage {
3939

4040
/// Given a deployment, pull all container images it references.
4141
pub(crate) async fn pull_bound_images(sysroot: &Storage, deployment: &Deployment) -> Result<()> {
42-
let bound_images = query_bound_images_for_deployment(sysroot.get_ostree()?, deployment)?;
42+
// Log the bound images operation to systemd journal
43+
const BOUND_IMAGES_JOURNAL_ID: &str = "1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5";
44+
tracing::info!(
45+
message_id = BOUND_IMAGES_JOURNAL_ID,
46+
bootc.deployment.osname = deployment.osname().as_str(),
47+
bootc.deployment.checksum = deployment.csum().as_str(),
48+
"Starting pull of bound images for deployment"
49+
);
50+
51+
let ostree = sysroot.get_ostree()?;
52+
let bound_images = query_bound_images_for_deployment(ostree, deployment)?;
53+
tracing::info!(
54+
message_id = BOUND_IMAGES_JOURNAL_ID,
55+
bootc.bound_images_count = bound_images.len(),
56+
"Found {} bound images to pull",
57+
bound_images.len()
58+
);
4359
pull_images(sysroot, bound_images).await
4460
}
4561

crates/lib/src/cli.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,26 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10671067
println!("Image specification is unchanged.");
10681068
return Ok(());
10691069
}
1070+
1071+
// Log the switch operation to systemd journal
1072+
const SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
1073+
let old_image = host
1074+
.spec
1075+
.image
1076+
.as_ref()
1077+
.map(|i| i.image.as_str())
1078+
.unwrap_or("none");
1079+
1080+
tracing::info!(
1081+
message_id = SWITCH_JOURNAL_ID,
1082+
bootc.old_image_reference = old_image,
1083+
bootc.new_image_reference = &target.image,
1084+
bootc.new_image_transport = &target.transport,
1085+
"Switching from image {} to {}",
1086+
old_image,
1087+
target.image
1088+
);
1089+
10701090
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
10711091

10721092
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?;

crates/lib/src/deploy.rs

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,24 @@ pub(crate) async fn pull_from_prepared(
429429
let imgref_canonicalized = imgref.clone().canonicalize()?;
430430
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
431431

432+
// Log successful import completion
433+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
434+
435+
tracing::info!(
436+
message_id = IMPORT_COMPLETE_JOURNAL_ID,
437+
bootc.image.reference = &imgref.image,
438+
bootc.image.transport = &imgref.transport,
439+
bootc.manifest_digest = import.manifest_digest.as_ref(),
440+
bootc.ostree_commit = &import.merge_commit,
441+
"Successfully imported image: {}",
442+
imgref
443+
);
444+
432445
if let Some(msg) =
433446
ostree_container::store::image_filtered_content_warning(&import.filtered_files)
434447
.context("Image content warning")?
435448
{
436-
crate::journal::journal_print(libsystemd::logging::Priority::Notice, &msg);
449+
tracing::info!("{}", msg);
437450
}
438451
Ok(Box::new((*import).into()))
439452
}
@@ -447,8 +460,30 @@ pub(crate) async fn pull(
447460
prog: ProgressWriter,
448461
) -> Result<Box<ImageState>> {
449462
match prepare_for_pull(repo, imgref, target_imgref).await? {
450-
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
463+
PreparedPullResult::AlreadyPresent(existing) => {
464+
// Log that the image was already present (Debug level since it's not actionable)
465+
const IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
466+
tracing::debug!(
467+
message_id = IMAGE_ALREADY_PRESENT_ID,
468+
bootc.image.reference = &imgref.image,
469+
bootc.image.transport = &imgref.transport,
470+
bootc.status = "already_present",
471+
"Image already present: {}",
472+
imgref
473+
);
474+
Ok(existing)
475+
}
451476
PreparedPullResult::Ready(prepared_image_meta) => {
477+
// Log that we're pulling a new image
478+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
479+
tracing::info!(
480+
message_id = PULLING_NEW_IMAGE_ID,
481+
bootc.image.reference = &imgref.image,
482+
bootc.image.transport = &imgref.transport,
483+
bootc.status = "pulling_new",
484+
"Pulling new image: {}",
485+
imgref
486+
);
452487
Ok(pull_from_prepared(imgref, quiet, prog, *prepared_image_meta).await?)
453488
}
454489
}
@@ -466,6 +501,14 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
466501
}
467502

468503
pub(crate) async fn cleanup(sysroot: &Storage) -> Result<()> {
504+
// Log the cleanup operation to systemd journal
505+
const CLEANUP_JOURNAL_ID: &str = "2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6";
506+
507+
tracing::info!(
508+
message_id = CLEANUP_JOURNAL_ID,
509+
"Starting cleanup of old images and deployments"
510+
);
511+
469512
let bound_prune = prune_container_store(sysroot);
470513

471514
// We create clones (just atomic reference bumps) here to move to the thread.
@@ -615,6 +658,20 @@ pub(crate) async fn stage(
615658
spec: &RequiredHostSpec<'_>,
616659
prog: ProgressWriter,
617660
) -> Result<()> {
661+
// Log the staging operation to systemd journal with comprehensive upgrade information
662+
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
663+
664+
tracing::info!(
665+
message_id = STAGE_JOURNAL_ID,
666+
bootc.image.reference = &spec.image.image,
667+
bootc.image.transport = &spec.image.transport,
668+
bootc.manifest_digest = image.manifest_digest.as_ref(),
669+
bootc.stateroot = stateroot,
670+
"Staging image for deployment: {} (digest: {})",
671+
spec.image,
672+
image.manifest_digest
673+
);
674+
618675
let ostree = sysroot.get_ostree()?;
619676
let mut subtask = SubTaskStep {
620677
subtask: "merging".into(),
@@ -773,19 +830,26 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
773830
let rollback_image = rollback_status
774831
.query_image(repo)?
775832
.ok_or_else(|| anyhow!("Rollback is not container image based"))?;
776-
let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest);
777-
libsystemd::logging::journal_send(
778-
libsystemd::logging::Priority::Info,
779-
&msg,
780-
[
781-
("MESSAGE_ID", ROLLBACK_JOURNAL_ID),
782-
(
783-
"BOOTC_MANIFEST_DIGEST",
784-
rollback_image.manifest_digest.as_ref(),
785-
),
786-
]
787-
.into_iter(),
788-
)?;
833+
834+
// Get current booted image for comparison
835+
let current_image = host
836+
.status
837+
.booted
838+
.as_ref()
839+
.and_then(|b| b.query_image(repo).ok()?);
840+
841+
tracing::info!(
842+
message_id = ROLLBACK_JOURNAL_ID,
843+
bootc.manifest_digest = rollback_image.manifest_digest.as_ref(),
844+
bootc.ostree_commit = &rollback_image.merge_commit,
845+
bootc.rollback_type = if reverting { "revert" } else { "rollback" },
846+
bootc.current_manifest_digest = current_image
847+
.as_ref()
848+
.map(|i| i.manifest_digest.as_ref())
849+
.unwrap_or("none"),
850+
"Rolling back to image: {}",
851+
rollback_image.manifest_digest
852+
);
789853
// SAFETY: If there's a rollback status, then there's a deployment
790854
let rollback_deployment = deployments.rollback.expect("rollback deployment");
791855
let new_deployments = if reverting {
@@ -833,6 +897,18 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
833897

834898
// Implementation of `bootc switch --in-place`
835899
pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Result<String> {
900+
// Log the in-place switch operation to systemd journal
901+
const SWITCH_INPLACE_JOURNAL_ID: &str = "3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7";
902+
903+
tracing::info!(
904+
message_id = SWITCH_INPLACE_JOURNAL_ID,
905+
bootc.image.reference = &imgref.image,
906+
bootc.image.transport = &imgref.transport,
907+
bootc.switch_type = "in_place",
908+
"Performing in-place switch to image: {}",
909+
imgref
910+
);
911+
836912
// First, just create the new origin file
837913
let origin = origin_from_imageref(imgref)?;
838914
let serialized_origin = origin.to_data();

crates/lib/src/install.rs

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,26 @@ fn installation_complete() {
14761476
#[context("Installing to disk")]
14771477
#[cfg(feature = "install-to-disk")]
14781478
pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
1479+
// Log the disk installation operation to systemd journal
1480+
const INSTALL_DISK_JOURNAL_ID: &str = "8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2";
1481+
let source_image = opts
1482+
.source_opts
1483+
.source_imgref
1484+
.as_ref()
1485+
.map(|s| s.as_str())
1486+
.unwrap_or("none");
1487+
let target_device = opts.block_opts.device.as_str();
1488+
1489+
tracing::info!(
1490+
message_id = INSTALL_DISK_JOURNAL_ID,
1491+
bootc.source_image = source_image,
1492+
bootc.target_device = target_device,
1493+
bootc.via_loopback = if opts.via_loopback { "true" } else { "false" },
1494+
"Starting disk installation from {} to {}",
1495+
source_image,
1496+
target_device
1497+
);
1498+
14791499
let mut block_opts = opts.block_opts;
14801500
let target_blockdev_meta = block_opts
14811501
.device
@@ -1701,6 +1721,26 @@ pub(crate) async fn install_to_filesystem(
17011721
targeting_host_root: bool,
17021722
cleanup: Cleanup,
17031723
) -> Result<()> {
1724+
// Log the installation operation to systemd journal
1725+
const INSTALL_FILESYSTEM_JOURNAL_ID: &str = "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3";
1726+
let source_image = opts
1727+
.source_opts
1728+
.source_imgref
1729+
.as_ref()
1730+
.map(|s| s.as_str())
1731+
.unwrap_or("none");
1732+
let target_path = opts.filesystem_opts.root_path.as_str();
1733+
1734+
tracing::info!(
1735+
message_id = INSTALL_FILESYSTEM_JOURNAL_ID,
1736+
bootc.source_image = source_image,
1737+
bootc.target_path = target_path,
1738+
bootc.targeting_host_root = if targeting_host_root { "true" } else { "false" },
1739+
"Starting filesystem installation from {} to {}",
1740+
source_image,
1741+
target_path
1742+
);
1743+
17041744
// Gather global state, destructuring the provided options.
17051745
// IMPORTANT: We might re-execute the current process in this function (for SELinux among other things)
17061746
// IMPORTANT: and hence anything that is done before MUST BE IDEMPOTENT.
@@ -1933,6 +1973,30 @@ pub(crate) async fn install_to_filesystem(
19331973
}
19341974

19351975
pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) -> Result<()> {
1976+
// Log the existing root installation operation to systemd journal
1977+
const INSTALL_EXISTING_ROOT_JOURNAL_ID: &str = "7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1";
1978+
let source_image = opts
1979+
.source_opts
1980+
.source_imgref
1981+
.as_ref()
1982+
.map(|s| s.as_str())
1983+
.unwrap_or("none");
1984+
let target_path = opts.root_path.as_str();
1985+
1986+
tracing::info!(
1987+
message_id = INSTALL_EXISTING_ROOT_JOURNAL_ID,
1988+
bootc.source_image = source_image,
1989+
bootc.target_path = target_path,
1990+
bootc.cleanup = if opts.cleanup {
1991+
"trigger_on_next_boot"
1992+
} else {
1993+
"skip"
1994+
},
1995+
"Starting installation to existing root from {} to {}",
1996+
source_image,
1997+
target_path
1998+
);
1999+
19362000
let cleanup = match opts.cleanup {
19372001
true => Cleanup::TriggerOnNextBoot,
19382002
false => Cleanup::Skip,
@@ -1957,14 +2021,33 @@ pub(crate) async fn install_to_existing_root(opts: InstallToExistingRootOpts) ->
19572021

19582022
/// Implementation of `bootc install finalize`.
19592023
pub(crate) async fn install_finalize(target: &Utf8Path) -> Result<()> {
2024+
// Log the installation finalization operation to systemd journal
2025+
const INSTALL_FINALIZE_JOURNAL_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
2026+
2027+
tracing::info!(
2028+
message_id = INSTALL_FINALIZE_JOURNAL_ID,
2029+
bootc.target_path = target.as_str(),
2030+
"Starting installation finalization for target: {}",
2031+
target
2032+
);
2033+
19602034
crate::cli::require_root(false)?;
19612035
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(target)));
19622036
sysroot.load(gio::Cancellable::NONE)?;
1963-
let deployments = sysroot.deployments();
1964-
// Verify we find a deployment
1965-
if deployments.is_empty() {
1966-
anyhow::bail!("Failed to find deployment in {target}");
1967-
}
2037+
2038+
let deployment = sysroot
2039+
.deployments()
2040+
.into_iter()
2041+
.next()
2042+
.ok_or_else(|| anyhow!("Failed to find deployment in {target}"))?;
2043+
2044+
// Log successful finalization
2045+
tracing::info!(
2046+
message_id = INSTALL_FINALIZE_JOURNAL_ID,
2047+
bootc.target_path = target.as_str(),
2048+
"Successfully finalized installation for target: {}",
2049+
target
2050+
);
19682051

19692052
// For now that's it! We expect to add more validation/postprocessing
19702053
// later, such as munging `etc/fstab` if needed. See

crates/lib/src/install/completion.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,17 @@ pub(crate) async fn impl_completion(
276276
sysroot: &ostree::Sysroot,
277277
stateroot: Option<&str>,
278278
) -> Result<()> {
279+
// Log the completion operation to systemd journal
280+
const COMPLETION_JOURNAL_ID: &str = "0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4";
281+
tracing::info!(
282+
message_id = COMPLETION_JOURNAL_ID,
283+
bootc.stateroot = stateroot.unwrap_or("default"),
284+
"Starting bootc installation completion"
285+
);
286+
279287
let deployment = &sysroot
280288
.merge_deployment(stateroot)
281-
.ok_or_else(|| anyhow::anyhow!("Failed to find deployment (stateroot={stateroot:?}"))?;
289+
.ok_or_else(|| anyhow::anyhow!("Failed to find deployment (stateroot={stateroot:?})"))?;
282290
let sysroot_dir = crate::utils::sysroot_dir(&sysroot)?;
283291

284292
// Create a subdir in /run
@@ -291,7 +299,16 @@ pub(crate) async fn impl_completion(
291299

292300
// ostree-ext doesn't do logically bound images
293301
let bound_images = crate::boundimage::query_bound_images_for_deployment(sysroot, deployment)?;
302+
294303
if !bound_images.is_empty() {
304+
// Log bound images found
305+
tracing::info!(
306+
message_id = COMPLETION_JOURNAL_ID,
307+
bootc.bound_images_count = bound_images.len(),
308+
"Found {} bound images for completion",
309+
bound_images.len()
310+
);
311+
295312
// load the selinux policy from the target ostree deployment
296313
let deployment_fd = deployment_fd(sysroot, deployment)?;
297314
let sepolicy = crate::lsm::new_sepolicy_at(deployment_fd)?;
@@ -304,5 +321,12 @@ pub(crate) async fn impl_completion(
304321
.context("pulling bound images")?;
305322
}
306323

324+
// Log completion success
325+
tracing::info!(
326+
message_id = COMPLETION_JOURNAL_ID,
327+
bootc.stateroot = stateroot.unwrap_or("default"),
328+
"Successfully completed bootc installation"
329+
);
330+
307331
Ok(())
308332
}

0 commit comments

Comments
 (0)