Skip to content

Commit 843e8fc

Browse files
committed
Add systemd structured logging for bootc state changes
Log key operations (deploy, upgrade, switch, install, etc.) to systemd journal with structured fields including image references, digests, and operation types. Uses existing journal infrastructure and follows ostree logging patterns.
1 parent 1537946 commit 843e8fc

File tree

6 files changed

+342
-8
lines changed

6 files changed

+342
-8
lines changed

crates/lib/src/boundimage.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use camino::Utf8Path;
1010
use cap_std_ext::cap_std::fs::Dir;
1111
use cap_std_ext::dirext::CapStdExtDirExt;
1212
use fn_error_context::context;
13+
use libsystemd::logging::Priority;
1314
use ostree_ext::containers_image_proxy;
1415
use ostree_ext::ostree::Deployment;
1516

@@ -39,7 +40,32 @@ pub(crate) struct ResolvedBoundImage {
3940

4041
/// Given a deployment, pull all container images it references.
4142
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)?;
43+
// Log the bound images operation to systemd journal
44+
const BOUND_IMAGES_JOURNAL_ID: &str = "1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5";
45+
let msg = "Starting pull of bound images for deployment";
46+
crate::journal::journal_send(
47+
Priority::Info,
48+
msg,
49+
[
50+
("MESSAGE_ID", BOUND_IMAGES_JOURNAL_ID),
51+
("BOOTC_DEPLOYMENT_OSNAME", &deployment.osname()),
52+
("BOOTC_DEPLOYMENT_CHECKSUM", &deployment.csum()),
53+
]
54+
.into_iter(),
55+
);
56+
57+
let ostree = sysroot.get_ostree()?;
58+
let bound_images = query_bound_images_for_deployment(ostree, deployment)?;
59+
let msg = format!("Found {} bound images to pull", bound_images.len());
60+
crate::journal::journal_send(
61+
Priority::Info,
62+
&msg,
63+
[
64+
("MESSAGE_ID", BOUND_IMAGES_JOURNAL_ID),
65+
("BOOTC_BOUND_IMAGES_COUNT", &bound_images.len().to_string()),
66+
]
67+
.into_iter(),
68+
);
4369
pull_images(sysroot, bound_images).await
4470
}
4571

crates/lib/src/cli.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use clap::Parser;
1515
use clap::ValueEnum;
1616
use fn_error_context::context;
1717
use indoc::indoc;
18+
use libsystemd::logging::Priority;
1819
use ostree::gio;
1920
use ostree_container::store::PrepareResult;
2021
use ostree_ext::composefs::fsverity;
@@ -992,6 +993,9 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
992993
} else if booted_unchanged {
993994
println!("No update available.")
994995
} else {
996+
// Note: Individual operation logging (pull, stage, deploy) will provide detailed information
997+
// No need for a separate "upgrading" message here since we're not adding new information
998+
995999
let osname = booted_deployment.osname();
9961000
crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?;
9971001
changed = true;
@@ -1069,6 +1073,28 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10691073
println!("Image specification is unchanged.");
10701074
return Ok(());
10711075
}
1076+
1077+
// Log the switch operation to systemd journal
1078+
const SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
1079+
let old_image = host
1080+
.spec
1081+
.image
1082+
.as_ref()
1083+
.map(|i| i.image.as_str())
1084+
.unwrap_or("none");
1085+
let msg = format!("Switching from image {} to {}", old_image, target.image);
1086+
crate::journal::journal_send(
1087+
Priority::Info,
1088+
&msg,
1089+
[
1090+
("MESSAGE_ID", SWITCH_JOURNAL_ID),
1091+
("BOOTC_OLD_IMAGE_REFERENCE", old_image),
1092+
("BOOTC_NEW_IMAGE_REFERENCE", &target.image),
1093+
("BOOTC_NEW_IMAGE_TRANSPORT", &target.transport),
1094+
]
1095+
.into_iter(),
1096+
);
1097+
10721098
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
10731099

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

crates/lib/src/deploy.rs

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use cap_std::fs::{Dir, MetadataExt};
1111
use cap_std_ext::cap_std;
1212
use cap_std_ext::dirext::CapStdExtDirExt;
1313
use fn_error_context::context;
14+
use libsystemd::logging::Priority;
1415
use ostree::{gio, glib};
1516
use ostree_container::OstreeImageReference;
1617
use ostree_ext::container as ostree_container;
@@ -99,7 +100,7 @@ pub(crate) fn check_bootc_label(config: &ostree_ext::oci_spec::image::ImageConfi
99100
match label.as_str() {
100101
crate::metadata::COMPAT_LABEL_V1 => {}
101102
o => crate::journal::journal_print(
102-
libsystemd::logging::Priority::Warning,
103+
Priority::Warning,
103104
&format!(
104105
"notice: Unknown {} value {}",
105106
crate::metadata::BOOTC_COMPAT_LABEL,
@@ -109,7 +110,7 @@ pub(crate) fn check_bootc_label(config: &ostree_ext::oci_spec::image::ImageConfi
109110
}
110111
} else {
111112
crate::journal::journal_print(
112-
libsystemd::logging::Priority::Warning,
113+
Priority::Warning,
113114
&format!(
114115
"notice: Image is missing label: {}",
115116
crate::metadata::BOOTC_COMPAT_LABEL
@@ -424,11 +425,27 @@ pub(crate) async fn pull_from_prepared(
424425
let imgref_canonicalized = imgref.clone().canonicalize()?;
425426
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
426427

428+
// Log successful import completion
429+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
430+
let msg = format!("Successfully imported image: {}", imgref);
431+
crate::journal::journal_send(
432+
Priority::Info,
433+
&msg,
434+
[
435+
("MESSAGE_ID", IMPORT_COMPLETE_JOURNAL_ID),
436+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
437+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
438+
("BOOTC_MANIFEST_DIGEST", import.manifest_digest.as_ref()),
439+
("BOOTC_OSTREE_COMMIT", &import.merge_commit),
440+
]
441+
.into_iter(),
442+
);
443+
427444
if let Some(msg) =
428445
ostree_container::store::image_filtered_content_warning(&import.filtered_files)
429446
.context("Image content warning")?
430447
{
431-
crate::journal::journal_print(libsystemd::logging::Priority::Notice, &msg);
448+
crate::journal::journal_print(Priority::Notice, &msg);
432449
}
433450
Ok(Box::new((*import).into()))
434451
}
@@ -442,8 +459,38 @@ pub(crate) async fn pull(
442459
prog: ProgressWriter,
443460
) -> Result<Box<ImageState>> {
444461
match prepare_for_pull(repo, imgref, target_imgref).await? {
445-
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
462+
PreparedPullResult::AlreadyPresent(existing) => {
463+
// Log that the image was already present (Debug level since it's not actionable)
464+
const PULL_JOURNAL_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
465+
let msg = format!("Image already present: {}", imgref);
466+
crate::journal::journal_send(
467+
Priority::Debug,
468+
&msg,
469+
[
470+
("MESSAGE_ID", PULL_JOURNAL_ID),
471+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
472+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
473+
("BOOTC_STATUS", "already_present"),
474+
]
475+
.into_iter(),
476+
);
477+
Ok(existing)
478+
}
446479
PreparedPullResult::Ready(prepared_image_meta) => {
480+
// Log that we're pulling a new image
481+
const PULL_JOURNAL_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
482+
let msg = format!("Pulling new image: {}", imgref);
483+
crate::journal::journal_send(
484+
Priority::Info,
485+
&msg,
486+
[
487+
("MESSAGE_ID", PULL_JOURNAL_ID),
488+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
489+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
490+
("BOOTC_STATUS", "pulling_new"),
491+
]
492+
.into_iter(),
493+
);
447494
Ok(pull_from_prepared(imgref, quiet, prog, prepared_image_meta).await?)
448495
}
449496
}
@@ -461,6 +508,15 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
461508
}
462509

463510
pub(crate) async fn cleanup(sysroot: &Storage) -> Result<()> {
511+
// Log the cleanup operation to systemd journal
512+
const CLEANUP_JOURNAL_ID: &str = "2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6";
513+
let msg = "Starting cleanup of old images and deployments";
514+
crate::journal::journal_send(
515+
Priority::Info,
516+
msg,
517+
[("MESSAGE_ID", CLEANUP_JOURNAL_ID)].into_iter(),
518+
);
519+
464520
let bound_prune = prune_container_store(sysroot);
465521

466522
// We create clones (just atomic reference bumps) here to move to the thread.
@@ -610,6 +666,25 @@ pub(crate) async fn stage(
610666
spec: &RequiredHostSpec<'_>,
611667
prog: ProgressWriter,
612668
) -> Result<()> {
669+
// Log the staging operation to systemd journal with comprehensive upgrade information
670+
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
671+
let msg = format!(
672+
"Staging image for deployment: {} (digest: {})",
673+
spec.image, image.manifest_digest
674+
);
675+
crate::journal::journal_send(
676+
Priority::Info,
677+
&msg,
678+
[
679+
("MESSAGE_ID", STAGE_JOURNAL_ID),
680+
("BOOTC_IMAGE_REFERENCE", &spec.image.image),
681+
("BOOTC_IMAGE_TRANSPORT", &spec.image.transport),
682+
("BOOTC_MANIFEST_DIGEST", image.manifest_digest.as_ref()),
683+
("BOOTC_STATEROOT", stateroot),
684+
]
685+
.into_iter(),
686+
);
687+
613688
let ostree = sysroot.get_ostree()?;
614689
let mut subtask = SubTaskStep {
615690
subtask: "merging".into(),
@@ -768,19 +843,39 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
768843
let rollback_image = rollback_status
769844
.query_image(repo)?
770845
.ok_or_else(|| anyhow!("Rollback is not container image based"))?;
846+
847+
// Get current booted image for comparison
848+
let current_image = host
849+
.status
850+
.booted
851+
.as_ref()
852+
.and_then(|b| b.query_image(repo).ok()?);
853+
771854
let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest);
772-
libsystemd::logging::journal_send(
773-
libsystemd::logging::Priority::Info,
855+
crate::journal::journal_send(
856+
Priority::Info,
774857
&msg,
775858
[
776859
("MESSAGE_ID", ROLLBACK_JOURNAL_ID),
777860
(
778861
"BOOTC_MANIFEST_DIGEST",
779862
rollback_image.manifest_digest.as_ref(),
780863
),
864+
("BOOTC_OSTREE_COMMIT", &rollback_image.merge_commit),
865+
(
866+
"BOOTC_ROLLBACK_TYPE",
867+
if reverting { "revert" } else { "rollback" },
868+
),
869+
(
870+
"BOOTC_CURRENT_MANIFEST_DIGEST",
871+
current_image
872+
.as_ref()
873+
.map(|i| i.manifest_digest.as_ref())
874+
.unwrap_or("none"),
875+
),
781876
]
782877
.into_iter(),
783-
)?;
878+
);
784879
// SAFETY: If there's a rollback status, then there's a deployment
785880
let rollback_deployment = deployments.rollback.expect("rollback deployment");
786881
let new_deployments = if reverting {
@@ -828,6 +923,21 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
828923

829924
// Implementation of `bootc switch --in-place`
830925
pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Result<String> {
926+
// Log the in-place switch operation to systemd journal
927+
const SWITCH_INPLACE_JOURNAL_ID: &str = "3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7";
928+
let msg = format!("Performing in-place switch to image: {}", imgref);
929+
crate::journal::journal_send(
930+
Priority::Info,
931+
&msg,
932+
[
933+
("MESSAGE_ID", SWITCH_INPLACE_JOURNAL_ID),
934+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
935+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
936+
("BOOTC_SWITCH_TYPE", "in_place"),
937+
]
938+
.into_iter(),
939+
);
940+
831941
// First, just create the new origin file
832942
let origin = origin_from_imageref(imgref)?;
833943
let serialized_origin = origin.to_data();

0 commit comments

Comments
 (0)