Skip to content

Commit 1e180a8

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 00dea6e commit 1e180a8

7 files changed

Lines changed: 349 additions & 11 deletions

File tree

crates/lib/src/boundimage.rs

Lines changed: 30 additions & 0 deletions
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,36 @@ 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<()> {
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_OPERATION", "pull_bound_images"),
52+
("BOOTC_DEPLOYMENT_OSNAME", deployment.osname()),
53+
("BOOTC_DEPLOYMENT_CHECKSUM", deployment.csum()),
54+
]
55+
.into_iter(),
56+
);
57+
4258
let bound_images = query_bound_images_for_deployment(sysroot, deployment)?;
59+
60+
if !bound_images.is_empty() {
61+
let msg = format!("Found {} bound images to pull", bound_images.len());
62+
crate::journal::journal_send(
63+
Priority::Info,
64+
&msg,
65+
[
66+
("MESSAGE_ID", BOUND_IMAGES_JOURNAL_ID),
67+
("BOOTC_BOUND_IMAGES_COUNT", &bound_images.len().to_string()),
68+
]
69+
.into_iter(),
70+
);
71+
}
72+
4373
pull_images(sysroot, bound_images).await
4474
}
4575

crates/lib/src/cli.rs

Lines changed: 35 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;
@@ -866,6 +867,23 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
866867
} else if booted_unchanged {
867868
println!("No update available.")
868869
} else {
870+
// Log the upgrade operation to systemd journal
871+
const UPGRADE_JOURNAL_ID: &str = "6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0";
872+
let old_digest = booted_image.as_ref().map(|i| i.manifest_digest.as_ref()).unwrap_or("none");
873+
let msg = format!("Upgrading from digest {} to {}", old_digest, fetched_digest);
874+
crate::journal::journal_send(
875+
Priority::Info,
876+
&msg,
877+
[
878+
("MESSAGE_ID", UPGRADE_JOURNAL_ID),
879+
("BOOTC_OLD_MANIFEST_DIGEST", old_digest),
880+
("BOOTC_NEW_MANIFEST_DIGEST", fetched_digest.as_ref()),
881+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
882+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
883+
]
884+
.into_iter(),
885+
);
886+
869887
let osname = booted_deployment.osname();
870888
crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?;
871889
changed = true;
@@ -936,6 +954,23 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
936954
println!("Image specification is unchanged.");
937955
return Ok(());
938956
}
957+
958+
// Log the switch operation to systemd journal
959+
const SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
960+
let old_image = host.spec.image.as_ref().map(|i| i.image.as_str()).unwrap_or("none");
961+
let msg = format!("Switching from image {} to {}", old_image, target.image);
962+
crate::journal::journal_send(
963+
Priority::Info,
964+
&msg,
965+
[
966+
("MESSAGE_ID", SWITCH_JOURNAL_ID),
967+
("BOOTC_OLD_IMAGE_REFERENCE", old_image),
968+
("BOOTC_NEW_IMAGE_REFERENCE", &target.image),
969+
("BOOTC_NEW_IMAGE_TRANSPORT", &target.transport),
970+
]
971+
.into_iter(),
972+
);
973+
939974
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
940975

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

crates/lib/src/deploy.rs

Lines changed: 123 additions & 11 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
@@ -423,11 +424,27 @@ pub(crate) async fn pull_from_prepared(
423424
let imgref_canonicalized = imgref.clone().canonicalize()?;
424425
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
425426

427+
// Log successful import completion
428+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
429+
let msg = format!("Successfully imported image: {}", imgref);
430+
crate::journal::journal_send(
431+
Priority::Info,
432+
&msg,
433+
[
434+
("MESSAGE_ID", IMPORT_COMPLETE_JOURNAL_ID),
435+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
436+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
437+
("BOOTC_MANIFEST_DIGEST", import.manifest_digest.as_ref()),
438+
("BOOTC_OSTREE_COMMIT", &import.merge_commit),
439+
]
440+
.into_iter(),
441+
);
442+
426443
if let Some(msg) =
427444
ostree_container::store::image_filtered_content_warning(&import.filtered_files)
428445
.context("Image content warning")?
429446
{
430-
crate::journal::journal_print(libsystemd::logging::Priority::Notice, &msg);
447+
crate::journal::journal_print(Priority::Notice, &msg);
431448
}
432449
Ok(Box::new((*import).into()))
433450
}
@@ -440,9 +457,51 @@ pub(crate) async fn pull(
440457
quiet: bool,
441458
prog: ProgressWriter,
442459
) -> Result<Box<ImageState>> {
460+
// Log the pull operation to systemd journal
461+
const PULL_JOURNAL_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
462+
let msg = format!("Pulling container image: {}", imgref);
463+
crate::journal::journal_send(
464+
Priority::Info,
465+
&msg,
466+
[
467+
("MESSAGE_ID", PULL_JOURNAL_ID),
468+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
469+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
470+
]
471+
.into_iter(),
472+
);
473+
443474
match prepare_for_pull(repo, imgref, target_imgref).await? {
444-
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
475+
PreparedPullResult::AlreadyPresent(existing) => {
476+
// Log that the image was already present
477+
let msg = format!("Image already present: {}", imgref);
478+
crate::journal::journal_send(
479+
Priority::Debug,
480+
&msg,
481+
[
482+
("MESSAGE_ID", PULL_JOURNAL_ID),
483+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
484+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
485+
("BOOTC_STATUS", "already_present"),
486+
]
487+
.into_iter(),
488+
);
489+
Ok(existing)
490+
}
445491
PreparedPullResult::Ready(prepared_image_meta) => {
492+
// Log that we're pulling a new image
493+
let msg = format!("Pulling new image: {}", imgref);
494+
crate::journal::journal_send(
495+
Priority::Info,
496+
&msg,
497+
[
498+
("MESSAGE_ID", PULL_JOURNAL_ID),
499+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
500+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
501+
("BOOTC_STATUS", "pulling_new"),
502+
]
503+
.into_iter(),
504+
);
446505
Ok(pull_from_prepared(imgref, quiet, prog, prepared_image_meta).await?)
447506
}
448507
}
@@ -460,6 +519,19 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
460519
}
461520

462521
pub(crate) async fn cleanup(sysroot: &Storage) -> Result<()> {
522+
// Log the cleanup operation to systemd journal
523+
const CLEANUP_JOURNAL_ID: &str = "2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6";
524+
let msg = "Starting cleanup of old images and deployments";
525+
crate::journal::journal_send(
526+
Priority::Info,
527+
msg,
528+
[
529+
("MESSAGE_ID", CLEANUP_JOURNAL_ID),
530+
("BOOTC_OPERATION", "cleanup"),
531+
]
532+
.into_iter(),
533+
);
534+
463535
let bound_prune = prune_container_store(sysroot);
464536

465537
// We create clones (just atomic reference bumps) here to move to the thread.
@@ -608,6 +680,22 @@ pub(crate) async fn stage(
608680
spec: &RequiredHostSpec<'_>,
609681
prog: ProgressWriter,
610682
) -> Result<()> {
683+
// Log the staging operation to systemd journal
684+
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
685+
let msg = format!("Staging image for deployment: {}", spec.image);
686+
crate::journal::journal_send(
687+
Priority::Info,
688+
&msg,
689+
[
690+
("MESSAGE_ID", STAGE_JOURNAL_ID),
691+
("BOOTC_IMAGE_REFERENCE", &spec.image.image),
692+
("BOOTC_IMAGE_TRANSPORT", &spec.image.transport),
693+
("BOOTC_MANIFEST_DIGEST", image.manifest_digest.as_ref()),
694+
("BOOTC_STATEROOT", stateroot),
695+
]
696+
.into_iter(),
697+
);
698+
611699
let mut subtask = SubTaskStep {
612700
subtask: "merging".into(),
613701
description: "Merging Image".into(),
@@ -763,19 +851,28 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
763851
let rollback_image = rollback_status
764852
.query_image(repo)?
765853
.ok_or_else(|| anyhow!("Rollback is not container image based"))?;
854+
855+
// Get current booted image for comparison
856+
let current_image = host
857+
.status
858+
.booted
859+
.as_ref()
860+
.and_then(|b| b.query_image(repo).ok()?)
861+
.flatten();
862+
766863
let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest);
767-
libsystemd::logging::journal_send(
768-
libsystemd::logging::Priority::Info,
864+
crate::journal::journal_send(
865+
Priority::Info,
769866
&msg,
770867
[
771868
("MESSAGE_ID", ROLLBACK_JOURNAL_ID),
772-
(
773-
"BOOTC_MANIFEST_DIGEST",
774-
rollback_image.manifest_digest.as_ref(),
775-
),
869+
("BOOTC_MANIFEST_DIGEST", rollback_image.manifest_digest.as_ref()),
870+
("BOOTC_OSTREE_COMMIT", &rollback_image.ostree_commit),
871+
("BOOTC_ROLLBACK_TYPE", if reverting { "revert" } else { "rollback" }),
872+
("BOOTC_CURRENT_MANIFEST_DIGEST", current_image.as_ref().map(|i| i.manifest_digest.as_ref()).unwrap_or("none")),
776873
]
777874
.into_iter(),
778-
)?;
875+
);
779876
// SAFETY: If there's a rollback status, then there's a deployment
780877
let rollback_deployment = deployments.rollback.expect("rollback deployment");
781878
let new_deployments = if reverting {
@@ -823,6 +920,21 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
823920

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

0 commit comments

Comments
 (0)