Skip to content

Commit c060bad

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 c060bad

File tree

10 files changed

+305
-41
lines changed

10 files changed

+305
-41
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: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,22 +98,18 @@ 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 {}",
105104
crate::metadata::BOOTC_COMPAT_LABEL,
106105
o
107-
),
108-
),
106+
);
107+
}
109108
}
110109
} 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-
),
110+
tracing::warn!(
111+
"notice: Image is missing label: {}",
112+
crate::metadata::BOOTC_COMPAT_LABEL
117113
)
118114
}
119115
}
@@ -429,11 +425,24 @@ pub(crate) async fn pull_from_prepared(
429425
let imgref_canonicalized = imgref.clone().canonicalize()?;
430426
tracing::debug!("Canonicalized image reference: {imgref_canonicalized:#}");
431427

428+
// Log successful import completion
429+
const IMPORT_COMPLETE_JOURNAL_ID: &str = "4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8";
430+
431+
tracing::info!(
432+
message_id = IMPORT_COMPLETE_JOURNAL_ID,
433+
bootc.image.reference = &imgref.image,
434+
bootc.image.transport = &imgref.transport,
435+
bootc.manifest_digest = import.manifest_digest.as_ref(),
436+
bootc.ostree_commit = &import.merge_commit,
437+
"Successfully imported image: {}",
438+
imgref
439+
);
440+
432441
if let Some(msg) =
433442
ostree_container::store::image_filtered_content_warning(&import.filtered_files)
434443
.context("Image content warning")?
435444
{
436-
crate::journal::journal_print(libsystemd::logging::Priority::Notice, &msg);
445+
tracing::info!("{}", msg);
437446
}
438447
Ok(Box::new((*import).into()))
439448
}
@@ -447,8 +456,30 @@ pub(crate) async fn pull(
447456
prog: ProgressWriter,
448457
) -> Result<Box<ImageState>> {
449458
match prepare_for_pull(repo, imgref, target_imgref).await? {
450-
PreparedPullResult::AlreadyPresent(existing) => Ok(existing),
459+
PreparedPullResult::AlreadyPresent(existing) => {
460+
// Log that the image was already present (Debug level since it's not actionable)
461+
const IMAGE_ALREADY_PRESENT_ID: &str = "5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9";
462+
tracing::debug!(
463+
message_id = IMAGE_ALREADY_PRESENT_ID,
464+
bootc.image.reference = &imgref.image,
465+
bootc.image.transport = &imgref.transport,
466+
bootc.status = "already_present",
467+
"Image already present: {}",
468+
imgref
469+
);
470+
Ok(existing)
471+
}
451472
PreparedPullResult::Ready(prepared_image_meta) => {
473+
// Log that we're pulling a new image
474+
const PULLING_NEW_IMAGE_ID: &str = "6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0";
475+
tracing::info!(
476+
message_id = PULLING_NEW_IMAGE_ID,
477+
bootc.image.reference = &imgref.image,
478+
bootc.image.transport = &imgref.transport,
479+
bootc.status = "pulling_new",
480+
"Pulling new image: {}",
481+
imgref
482+
);
452483
Ok(pull_from_prepared(imgref, quiet, prog, *prepared_image_meta).await?)
453484
}
454485
}
@@ -466,6 +497,14 @@ pub(crate) async fn wipe_ostree(sysroot: Sysroot) -> Result<()> {
466497
}
467498

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

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

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

0 commit comments

Comments
 (0)