Skip to content

Commit 5fb66c3

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 5fb66c3

File tree

7 files changed

+411
-7
lines changed

7 files changed

+411
-7
lines changed

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: 43 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,26 @@ 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
873+
.as_ref()
874+
.map(|i| i.manifest_digest.as_ref())
875+
.unwrap_or("none");
876+
let msg = format!("Upgrading from digest {} to {}", old_digest, fetched_digest);
877+
crate::journal::journal_send(
878+
Priority::Info,
879+
&msg,
880+
[
881+
("MESSAGE_ID", UPGRADE_JOURNAL_ID),
882+
("BOOTC_OLD_MANIFEST_DIGEST", old_digest),
883+
("BOOTC_NEW_MANIFEST_DIGEST", fetched_digest.as_ref()),
884+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
885+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
886+
]
887+
.into_iter(),
888+
);
889+
869890
let osname = booted_deployment.osname();
870891
crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?;
871892
changed = true;
@@ -936,6 +957,28 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
936957
println!("Image specification is unchanged.");
937958
return Ok(());
938959
}
960+
961+
// Log the switch operation to systemd journal
962+
const SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
963+
let old_image = host
964+
.spec
965+
.image
966+
.as_ref()
967+
.map(|i| i.image.as_str())
968+
.unwrap_or("none");
969+
let msg = format!("Switching from image {} to {}", old_image, target.image);
970+
crate::journal::journal_send(
971+
Priority::Info,
972+
&msg,
973+
[
974+
("MESSAGE_ID", SWITCH_JOURNAL_ID),
975+
("BOOTC_OLD_IMAGE_REFERENCE", old_image),
976+
("BOOTC_NEW_IMAGE_REFERENCE", &target.image),
977+
("BOOTC_NEW_IMAGE_TRANSPORT", &target.transport),
978+
]
979+
.into_iter(),
980+
);
981+
939982
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
940983

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

crates/lib/src/deploy.rs

Lines changed: 130 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
@@ -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,39 @@ 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+
766862
let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest);
767-
libsystemd::logging::journal_send(
768-
libsystemd::logging::Priority::Info,
863+
crate::journal::journal_send(
864+
Priority::Info,
769865
&msg,
770866
[
771867
("MESSAGE_ID", ROLLBACK_JOURNAL_ID),
772868
(
773869
"BOOTC_MANIFEST_DIGEST",
774870
rollback_image.manifest_digest.as_ref(),
775871
),
872+
("BOOTC_OSTREE_COMMIT", &rollback_image.merge_commit),
873+
(
874+
"BOOTC_ROLLBACK_TYPE",
875+
if reverting { "revert" } else { "rollback" },
876+
),
877+
(
878+
"BOOTC_CURRENT_MANIFEST_DIGEST",
879+
current_image
880+
.as_ref()
881+
.map(|i| i.manifest_digest.as_ref())
882+
.unwrap_or("none"),
883+
),
776884
]
777885
.into_iter(),
778-
)?;
886+
);
779887
// SAFETY: If there's a rollback status, then there's a deployment
780888
let rollback_deployment = deployments.rollback.expect("rollback deployment");
781889
let new_deployments = if reverting {
@@ -823,6 +931,21 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
823931

824932
// Implementation of `bootc switch --in-place`
825933
pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Result<String> {
934+
// Log the in-place switch operation to systemd journal
935+
const SWITCH_INPLACE_JOURNAL_ID: &str = "3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7";
936+
let msg = format!("Performing in-place switch to image: {}", imgref);
937+
crate::journal::journal_send(
938+
Priority::Info,
939+
&msg,
940+
[
941+
("MESSAGE_ID", SWITCH_INPLACE_JOURNAL_ID),
942+
("BOOTC_IMAGE_REFERENCE", &imgref.image),
943+
("BOOTC_IMAGE_TRANSPORT", &imgref.transport),
944+
("BOOTC_SWITCH_TYPE", "in_place"),
945+
]
946+
.into_iter(),
947+
);
948+
826949
// First, just create the new origin file
827950
let origin = origin_from_imageref(imgref)?;
828951
let serialized_origin = origin.to_data();

0 commit comments

Comments
 (0)