Skip to content

Commit 2b95e42

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 1dd55c2 commit 2b95e42

File tree

10 files changed

+306
-43
lines changed

10 files changed

+306
-43
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: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use ostree_ext::container as ostree_container;
2424
use ostree_ext::container_utils::ostree_booted;
2525
use ostree_ext::keyfileext::KeyFileExt;
2626
use ostree_ext::ostree;
27-
use ostree_ext::sysroot::SysrootLock;
2827
use schemars::schema_for;
2928
use serde::{Deserialize, Serialize};
3029

@@ -785,7 +784,10 @@ fn has_soft_reboot_capability(deployment: Option<&crate::spec::BootEntry>) -> bo
785784

786785
/// Prepare a soft reboot for the given deployment
787786
#[context("Preparing soft reboot")]
788-
fn prepare_soft_reboot(sysroot: &SysrootLock, deployment: &ostree::Deployment) -> Result<()> {
787+
fn prepare_soft_reboot(
788+
sysroot: &ostree_ext::sysroot::SysrootLock,
789+
deployment: &ostree::Deployment,
790+
) -> Result<()> {
789791
let cancellable = ostree::gio::Cancellable::NONE;
790792
sysroot
791793
.deployment_set_soft_reboot(deployment, false, cancellable)
@@ -832,7 +834,7 @@ where
832834
/// Handle soft reboot for staged deployments (used by upgrade and switch)
833835
#[context("Handling staged soft reboot")]
834836
fn handle_staged_soft_reboot(
835-
sysroot: &SysrootLock,
837+
sysroot: &ostree_ext::sysroot::SysrootLock,
836838
soft_reboot_mode: Option<SoftRebootMode>,
837839
host: &crate::spec::Host,
838840
) -> Result<()> {
@@ -846,7 +848,7 @@ fn handle_staged_soft_reboot(
846848

847849
/// Perform a soft reboot for a staged deployment
848850
#[context("Soft reboot staged deployment")]
849-
fn soft_reboot_staged(sysroot: &SysrootLock) -> Result<()> {
851+
fn soft_reboot_staged(sysroot: &ostree_ext::sysroot::SysrootLock) -> Result<()> {
850852
println!("Staged deployment is soft-reboot capable, preparing for soft-reboot...");
851853

852854
let deployments_list = sysroot.deployments();
@@ -861,7 +863,7 @@ fn soft_reboot_staged(sysroot: &SysrootLock) -> Result<()> {
861863

862864
/// Perform a soft reboot for a rollback deployment
863865
#[context("Soft reboot rollback deployment")]
864-
fn soft_reboot_rollback(sysroot: &SysrootLock) -> Result<()> {
866+
fn soft_reboot_rollback(sysroot: &ostree_ext::sysroot::SysrootLock) -> Result<()> {
865867
println!("Rollback deployment is soft-reboot capable, preparing for soft-reboot...");
866868

867869
let deployments_list = sysroot.deployments();
@@ -990,6 +992,9 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
990992
} else if booted_unchanged {
991993
println!("No update available.")
992994
} else {
995+
// Note: Individual operation logging (pull, stage, deploy) will provide detailed information
996+
// No need for a separate "upgrading" message here since we're not adding new information
997+
993998
let osname = booted_deployment.osname();
994999
crate::deploy::stage(sysroot, &osname, &fetched, &spec, prog.clone()).await?;
9951000
changed = true;
@@ -1067,6 +1072,26 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
10671072
println!("Image specification is unchanged.");
10681073
return Ok(());
10691074
}
1075+
1076+
// Log the switch operation to systemd journal
1077+
const SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
1078+
let old_image = host
1079+
.spec
1080+
.image
1081+
.as_ref()
1082+
.map(|i| i.image.as_str())
1083+
.unwrap_or("none");
1084+
1085+
tracing::info!(
1086+
message_id = SWITCH_JOURNAL_ID,
1087+
bootc.old_image_reference = old_image,
1088+
bootc.new_image_reference = &target.image,
1089+
bootc.new_image_transport = &target.transport,
1090+
"Switching from image {} to {}",
1091+
old_image,
1092+
target.image
1093+
);
1094+
10701095
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
10711096

10721097
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet, prog.clone()).await?;
@@ -1364,7 +1389,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
13641389
Opt::ExecInHostMountNamespace { args } => {
13651390
crate::install::exec_in_host_mountns(args.as_slice())
13661391
}
1367-
Opt::Status(opts) => super::status::status(opts).await,
1392+
Opt::Status(opts) => crate::status::status(opts).await,
13681393
Opt::Internals(opts) => match opts {
13691394
InternalsOpts::SystemdGenerator {
13701395
normal_dir,

crates/lib/src/deploy.rs

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -98,22 +98,17 @@ pub(crate) fn check_bootc_label(config: &ostree_ext::oci_spec::image::ImageConfi
9898
{
9999
match label.as_str() {
100100
crate::metadata::COMPAT_LABEL_V1 => {}
101-
o => crate::journal::journal_print(
102-
libsystemd::logging::Priority::Warning,
103-
&format!(
101+
o => {
102+
tracing::warn!(
104103
"notice: Unknown {} value {}",
105-
crate::metadata::BOOTC_COMPAT_LABEL,
106-
o
107-
),
108-
),
104+
crate::metadata::BOOTC_COMPAT_LABEL, o
105+
);
106+
}
109107
}
110108
} else {
111-
crate::journal::journal_print(
112-
libsystemd::logging::Priority::Warning,
113-
&format!(
114-
"notice: Image is missing label: {}",
115-
crate::metadata::BOOTC_COMPAT_LABEL
116-
),
109+
tracing::warn!(
110+
"notice: Image is missing label: {}",
111+
crate::metadata::BOOTC_COMPAT_LABEL
117112
)
118113
}
119114
}
@@ -429,11 +424,24 @@ pub(crate) async fn pull_from_prepared(
429424
let imgref_canonicalized = imgref.clone().canonicalize()?;
430425
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
431426

427+
// Log successful import completion
428+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
429+
430+
tracing::info!(
431+
message_id = IMPORT_COMPLETE_JOURNAL_ID,
432+
bootc.image.reference = &imgref.image,
433+
bootc.image.transport = &imgref.transport,
434+
bootc.manifest_digest = import.manifest_digest.as_ref(),
435+
bootc.ostree_commit = &import.merge_commit,
436+
"Successfully imported image: {}",
437+
imgref
438+
);
439+
432440
if let Some(msg) =
433441
ostree_container::store::image_filtered_content_warning(&import.filtered_files)
434442
.context("Image content warning")?
435443
{
436-
crate::journal::journal_print(libsystemd::logging::Priority::Notice, &msg);
444+
tracing::info!("{}", msg);
437445
}
438446
Ok(Box::new((*import).into()))
439447
}
@@ -447,8 +455,30 @@ pub(crate) async fn pull(
447455
prog: ProgressWriter,
448456
) -> Result<Box<ImageState>> {
449457
match prepare_for_pull(repo, imgref, target_imgref).await? {
450-
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
458+
PreparedPullResult::AlreadyPresent(existing) => {
459+
// Log that the image was already present (Debug level since it's not actionable)
460+
const IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
461+
tracing::debug!(
462+
message_id = IMAGE_ALREADY_PRESENT_ID,
463+
bootc.image.reference = &imgref.image,
464+
bootc.image.transport = &imgref.transport,
465+
bootc.status = "already_present",
466+
"Image already present: {}",
467+
imgref
468+
);
469+
Ok(existing)
470+
}
451471
PreparedPullResult::Ready(prepared_image_meta) => {
472+
// Log that we're pulling a new image
473+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
474+
tracing::info!(
475+
message_id = PULLING_NEW_IMAGE_ID,
476+
bootc.image.reference = &imgref.image,
477+
bootc.image.transport = &imgref.transport,
478+
bootc.status = "pulling_new",
479+
"Pulling new image: {}",
480+
imgref
481+
);
452482
Ok(pull_from_prepared(imgref, quiet, prog, *prepared_image_meta).await?)
453483
}
454484
}
@@ -466,6 +496,14 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
466496
}
467497

468498
pub(crate) async fn cleanup(sysroot: &Storage) -> Result<()> {
499+
// Log the cleanup operation to systemd journal
500+
const CLEANUP_JOURNAL_ID: &str = "2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6";
501+
502+
tracing::info!(
503+
message_id = CLEANUP_JOURNAL_ID,
504+
"Starting cleanup of old images and deployments"
505+
);
506+
469507
let bound_prune = prune_container_store(sysroot);
470508

471509
// We create clones (just atomic reference bumps) here to move to the thread.
@@ -615,6 +653,20 @@ pub(crate) async fn stage(
615653
spec: &RequiredHostSpec<'_>,
616654
prog: ProgressWriter,
617655
) -> Result<()> {
656+
// Log the staging operation to systemd journal with comprehensive upgrade information
657+
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
658+
659+
tracing::info!(
660+
message_id = STAGE_JOURNAL_ID,
661+
bootc.image.reference = &spec.image.image,
662+
bootc.image.transport = &spec.image.transport,
663+
bootc.manifest_digest = image.manifest_digest.as_ref(),
664+
bootc.stateroot = stateroot,
665+
"Staging image for deployment: {} (digest: {})",
666+
spec.image,
667+
image.manifest_digest
668+
);
669+
618670
let ostree = sysroot.get_ostree()?;
619671
let mut subtask = SubTaskStep {
620672
subtask: "merging".into(),
@@ -773,19 +825,26 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
773825
let rollback_image = rollback_status
774826
.query_image(repo)?
775827
.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-
)?;
828+
829+
// Get current booted image for comparison
830+
let current_image = host
831+
.status
832+
.booted
833+
.as_ref()
834+
.and_then(|b| b.query_image(repo).ok()?);
835+
836+
tracing::info!(
837+
message_id = ROLLBACK_JOURNAL_ID,
838+
bootc.manifest_digest = rollback_image.manifest_digest.as_ref(),
839+
bootc.ostree_commit = &rollback_image.merge_commit,
840+
bootc.rollback_type = if reverting { "revert" } else { "rollback" },
841+
bootc.current_manifest_digest = current_image
842+
.as_ref()
843+
.map(|i| i.manifest_digest.as_ref())
844+
.unwrap_or("none"),
845+
"Rolling back to image: {}",
846+
rollback_image.manifest_digest
847+
);
789848
// SAFETY: If there's a rollback status, then there's a deployment
790849
let rollback_deployment = deployments.rollback.expect("rollback deployment");
791850
let new_deployments = if reverting {
@@ -833,6 +892,18 @@ fn find_newest_deployment_name(deploysdir: &Dir) -> Result<String> {
833892

834893
// Implementation of `bootc switch --in-place`
835894
pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Result<String> {
895+
// Log the in-place switch operation to systemd journal
896+
const SWITCH_INPLACE_JOURNAL_ID: &str = "3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7";
897+
898+
tracing::info!(
899+
message_id = SWITCH_INPLACE_JOURNAL_ID,
900+
bootc.image.reference = &imgref.image,
901+
bootc.image.transport = &imgref.transport,
902+
bootc.switch_type = "in_place",
903+
"Performing in-place switch to image: {}",
904+
imgref
905+
);
906+
836907
// First, just create the new origin file
837908
let origin = origin_from_imageref(imgref)?;
838909
let serialized_origin = origin.to_data();

0 commit comments

Comments
 (0)