Skip to content

Commit ec01edb

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 pattern.
1 parent 1537946 commit ec01edb

File tree

10 files changed

+337
-8
lines changed

10 files changed

+337
-8
lines changed

Cargo.lock

Lines changed: 12 additions & 0 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: 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: 109 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,30 @@ 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 IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
465+
tracing::debug!(
466+
message_id = IMAGE_ALREADY_PRESENT_ID,
467+
bootc.image.reference = &imgref.image,
468+
bootc.image.transport = &imgref.transport,
469+
bootc.status = "already_present",
470+
"Image already present: {}",
471+
imgref
472+
);
473+
Ok(existing)
474+
}
446475
PreparedPullResult::Ready(prepared_image_meta) => {
476+
// Log that we're pulling a new image
477+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
478+
tracing::info!(
479+
message_id = PULLING_NEW_IMAGE_ID,
480+
bootc.image.reference = &imgref.image,
481+
bootc.image.transport = &imgref.transport,
482+
bootc.status = "pulling_new",
483+
"Pulling new image: {}",
484+
imgref
485+
);
447486
Ok(pull_from_prepared(imgref, quiet, prog, prepared_image_meta).await?)
448487
}
449488
}
@@ -461,6 +500,15 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
461500
}
462501

463502
pub(crate) async fn cleanup(sysroot: &Storage) -> Result<()> {
503+
// Log the cleanup operation to systemd journal
504+
const CLEANUP_JOURNAL_ID: &str = "2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6";
505+
let msg = "Starting cleanup of old images and deployments";
506+
crate::journal::journal_send(
507+
Priority::Info,
508+
msg,
509+
[("MESSAGE_ID", CLEANUP_JOURNAL_ID)].into_iter(),
510+
);
511+
464512
let bound_prune = prune_container_store(sysroot);
465513

466514
// We create clones (just atomic reference bumps) here to move to the thread.
@@ -610,6 +658,25 @@ pub(crate) async fn stage(
610658
spec: &RequiredHostSpec<'_>,
611659
prog: ProgressWriter,
612660
) -> Result<()> {
661+
// Log the staging operation to systemd journal with comprehensive upgrade information
662+
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
663+
let msg = format!(
664+
"Staging image for deployment: {} (digest: {})",
665+
spec.image, image.manifest_digest
666+
);
667+
crate::journal::journal_send(
668+
Priority::Info,
669+
&msg,
670+
[
671+
("MESSAGE_ID", STAGE_JOURNAL_ID),
672+
("BOOTC_IMAGE_REFERENCE", &spec.image.image),
673+
("BOOTC_IMAGE_TRANSPORT", &spec.image.transport),
674+
("BOOTC_MANIFEST_DIGEST", image.manifest_digest.as_ref()),
675+
("BOOTC_STATEROOT", stateroot),
676+
]
677+
.into_iter(),
678+
);
679+
613680
let ostree = sysroot.get_ostree()?;
614681
let mut subtask = SubTaskStep {
615682
subtask: "merging".into(),
@@ -768,19 +835,39 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
768835
let rollback_image = rollback_status
769836
.query_image(repo)?
770837
.ok_or_else(|| anyhow!("Rollback is not container image based"))?;
838+
839+
// Get current booted image for comparison
840+
let current_image = host
841+
.status
842+
.booted
843+
.as_ref()
844+
.and_then(|b| b.query_image(repo).ok()?);
845+
771846
let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest);
772-
libsystemd::logging::journal_send(
773-
libsystemd::logging::Priority::Info,
847+
crate::journal::journal_send(
848+
Priority::Info,
774849
&msg,
775850
[
776851
("MESSAGE_ID", ROLLBACK_JOURNAL_ID),
777852
(
778853
"BOOTC_MANIFEST_DIGEST",
779854
rollback_image.manifest_digest.as_ref(),
780855
),
856+
("BOOTC_OSTREE_COMMIT", &rollback_image.merge_commit),
857+
(
858+
"BOOTC_ROLLBACK_TYPE",
859+
if reverting { "revert" } else { "rollback" },
860+
),
861+
(
862+
"BOOTC_CURRENT_MANIFEST_DIGEST",
863+
current_image
864+
.as_ref()
865+
.map(|i| i.manifest_digest.as_ref())
866+
.unwrap_or("none"),
867+
),
781868
]
782869
.into_iter(),
783-
)?;
870+
);
784871
// SAFETY: If there's a rollback status, then there's a deployment
785872
let rollback_deployment = deployments.rollback.expect("rollback deployment");
786873
let new_deployments = if reverting {
@@ -828,6 +915,21 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
828915

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

0 commit comments

Comments
 (0)