From 85ae59c849748e6b427efea222084064792c6b9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:17:26 +0000 Subject: [PATCH 1/3] Add JSON formatted log output support to all CLI tools --- Cargo.lock | 13 ++++++++++++ apt2aptly/Cargo.toml | 2 +- apt2aptly/src/main.rs | 32 +++++++++++++++++++++++++----- aptly-latest-snapshots/Cargo.toml | 2 +- aptly-latest-snapshots/src/main.rs | 32 +++++++++++++++++++++++++----- aptlyctl/Cargo.toml | 2 +- aptlyctl/src/main.rs | 30 ++++++++++++++++++++++++---- obs2aptly/Cargo.toml | 2 +- obs2aptly/src/main.rs | 32 +++++++++++++++++++++++++----- 9 files changed, 124 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1df5f02..88844d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4977,6 +4977,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -4984,11 +4994,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "nu-ansi-term", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] diff --git a/apt2aptly/Cargo.toml b/apt2aptly/Cargo.toml index 6f1b671..3d0c2ad 100644 --- a/apt2aptly/Cargo.toml +++ b/apt2aptly/Cargo.toml @@ -19,5 +19,5 @@ sync2aptly = { path = "../sync2aptly" } tokio = { version = "1.45.1", features = ["full"] } tracing = "0.1.41" tracing-error = "0.2.1" -tracing-subscriber = "0.3.20" +tracing-subscriber = { version = "0.3.20", features = ["json"] } url = "2.5.4" diff --git a/apt2aptly/src/main.rs b/apt2aptly/src/main.rs index 41ee524..7ade2bb 100644 --- a/apt2aptly/src/main.rs +++ b/apt2aptly/src/main.rs @@ -9,7 +9,7 @@ use aptly_rest::{ api::{publish, repos, snapshots::DeleteOptions}, AptlyRest, AptlyRestError, }; -use clap::{builder::ArgPredicate, Parser}; +use clap::{builder::ArgPredicate, Parser, ValueEnum}; use color_eyre::{ eyre::{bail, ensure, Context}, Result, @@ -23,6 +23,13 @@ use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; use url::Url; +#[derive(ValueEnum, Clone, Copy, Debug, Default)] +enum LogFormat { + #[default] + Pretty, + Json, +} + #[derive(Parser, Debug)] struct Opts { /// Url for the aptly rest api endpoint @@ -86,6 +93,9 @@ struct Opts { /// Only show changes, don't apply them #[clap(short = 'n', long, default_value_t = false)] dry_run: bool, + /// Log output format + #[clap(long, value_enum, default_value_t = LogFormat::Pretty)] + log_format: LogFormat, } const TEMPLATE_VAR_COMPONENT: &str = "component"; @@ -474,12 +484,24 @@ async fn sync_dist( #[tokio::main] async fn main() -> Result<()> { - tracing_subscriber::registry() - .with(ErrorLayer::default()) - .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) - .init(); color_eyre::install().unwrap(); let opts = Opts::parse(); + + match opts.log_format { + LogFormat::Pretty => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) + .init(), + LogFormat::Json => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_filter(LevelFilter::INFO), + ) + .init(), + } + let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? } else { diff --git a/aptly-latest-snapshots/Cargo.toml b/aptly-latest-snapshots/Cargo.toml index 2714c08..3e19fec 100644 --- a/aptly-latest-snapshots/Cargo.toml +++ b/aptly-latest-snapshots/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "2.0.12" tokio = { version = "1.45.1", features = ["full"] } tracing = "0.1.41" tracing-error = "0.2.1" -tracing-subscriber = "0.3.20" +tracing-subscriber = { version = "0.3.20", features = ["json"] } url = "2.5.4" [dev-dependencies] diff --git a/aptly-latest-snapshots/src/main.rs b/aptly-latest-snapshots/src/main.rs index e9f5ce6..6b2211e 100644 --- a/aptly-latest-snapshots/src/main.rs +++ b/aptly-latest-snapshots/src/main.rs @@ -2,13 +2,20 @@ use std::{net::SocketAddr, time::Duration}; use aptly_latest_snapshots::{create_app, periodic_snapshot_refresh, AppState}; use aptly_rest::AptlyRest; -use clap::Parser; +use clap::{Parser, ValueEnum}; use color_eyre::{eyre::WrapErr, Result}; use tracing::info; use tracing::metadata::LevelFilter; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; +#[derive(ValueEnum, Clone, Copy, Debug, Default)] +enum LogFormat { + #[default] + Pretty, + Json, +} + #[derive(Parser, Debug)] struct Opts { /// Url for the aptly rest api endpoint @@ -31,17 +38,32 @@ struct Opts { default_value_t = 600, value_parser = clap::value_parser!(u16).range(1..))] refresh_interval_sec: u16, + /// Log output format + #[clap(long, value_enum, default_value_t = LogFormat::Pretty)] + log_format: LogFormat, } #[tokio::main] async fn main() -> Result<()> { - tracing_subscriber::registry() - .with(ErrorLayer::default()) - .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) - .init(); color_eyre::install().unwrap(); let opts = Opts::parse(); + + match opts.log_format { + LogFormat::Pretty => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) + .init(), + LogFormat::Json => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_filter(LevelFilter::INFO), + ) + .init(), + } + let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? } else { diff --git a/aptlyctl/Cargo.toml b/aptlyctl/Cargo.toml index 95569e7..b1003f0 100644 --- a/aptlyctl/Cargo.toml +++ b/aptlyctl/Cargo.toml @@ -17,5 +17,5 @@ serde-saphyr = "0.0.27" tokio = { version = "1.45.1", features = ["full"] } tracing = "0.1.41" tracing-error = "0.2.1" -tracing-subscriber = "0.3.20" +tracing-subscriber = { version = "0.3.20", features = ["json"] } url = "2.5.4" diff --git a/aptlyctl/src/main.rs b/aptlyctl/src/main.rs index e37b8de..6fa60e0 100644 --- a/aptlyctl/src/main.rs +++ b/aptlyctl/src/main.rs @@ -24,6 +24,13 @@ enum OutputFormat { Yaml, } +#[derive(ValueEnum, Clone, Copy, Debug, Default)] +enum LogFormat { + #[default] + Pretty, + Json, +} + #[derive(Subcommand, Debug)] enum Command { Repo { @@ -60,16 +67,31 @@ struct Opts { /// Authentication token for the API #[clap(long, env = "APTLY_API_TOKEN")] api_token: Option, + /// Log output format + #[clap(long, value_enum, default_value_t = LogFormat::Pretty)] + log_format: LogFormat, } #[tokio::main] async fn main() -> Result { - tracing_subscriber::registry() - .with(ErrorLayer::default()) - .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) - .init(); color_eyre::install().unwrap(); let opts = Opts::parse(); + + match opts.log_format { + LogFormat::Pretty => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) + .init(), + LogFormat::Json => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_filter(LevelFilter::INFO), + ) + .init(), + } + let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)? } else { diff --git a/obs2aptly/Cargo.toml b/obs2aptly/Cargo.toml index 1619a6c..1c5cd4b 100644 --- a/obs2aptly/Cargo.toml +++ b/obs2aptly/Cargo.toml @@ -18,7 +18,7 @@ sync2aptly = { path = "../sync2aptly" } tokio = { version = "1.45.1", features = ["full"] } tracing = "0.1.41" tracing-error = "0.2.1" -tracing-subscriber = "0.3.20" +tracing-subscriber = { version = "0.3.20", features = ["json"] } url = "2.5.4" [dev-dependencies] diff --git a/obs2aptly/src/main.rs b/obs2aptly/src/main.rs index 8444c77..63ba2fc 100644 --- a/obs2aptly/src/main.rs +++ b/obs2aptly/src/main.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use aptly_rest::AptlyRest; -use clap::Parser; +use clap::{Parser, ValueEnum}; use color_eyre::Result; use sync2aptly::{AptlyContent, PoolPackagesCache, UploadOptions}; use tracing::metadata::LevelFilter; @@ -14,6 +14,13 @@ enum FilterKind { Binaries, } +#[derive(ValueEnum, Clone, Copy, Debug, Default)] +enum LogFormat { + #[default] + Pretty, + Json, +} + #[derive(Parser, Debug)] struct Opts { /// Url for the aptly rest api endpoint @@ -40,16 +47,31 @@ struct Opts { /// Only show changes, don't apply them #[clap(short = 'n', long, default_value_t = false)] dry_run: bool, + /// Log output format + #[clap(long, value_enum, default_value_t = LogFormat::Pretty)] + log_format: LogFormat, } #[tokio::main] async fn main() -> Result<()> { - tracing_subscriber::registry() - .with(ErrorLayer::default()) - .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) - .init(); color_eyre::install().unwrap(); let opts = Opts::parse(); + + match opts.log_format { + LogFormat::Pretty => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) + .init(), + LogFormat::Json => tracing_subscriber::registry() + .with(ErrorLayer::default()) + .with( + tracing_subscriber::fmt::layer() + .json() + .with_filter(LevelFilter::INFO), + ) + .init(), + } + let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)? } else { From 64c8333d44a1fff069d83ce337bc42b51033a7ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:21:03 +0000 Subject: [PATCH 2/3] Refactor tracing setup into init_tracing helper function in each CLI --- apt2aptly/src/main.rs | 15 +++++++++------ aptly-latest-snapshots/src/main.rs | 17 ++++++++++------- aptlyctl/src/main.rs | 15 +++++++++------ obs2aptly/src/main.rs | 15 +++++++++------ 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/apt2aptly/src/main.rs b/apt2aptly/src/main.rs index 7ade2bb..7993522 100644 --- a/apt2aptly/src/main.rs +++ b/apt2aptly/src/main.rs @@ -482,12 +482,8 @@ async fn sync_dist( Ok(()) } -#[tokio::main] -async fn main() -> Result<()> { - color_eyre::install().unwrap(); - let opts = Opts::parse(); - - match opts.log_format { +fn init_tracing(format: LogFormat) { + match format { LogFormat::Pretty => tracing_subscriber::registry() .with(ErrorLayer::default()) .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) @@ -501,6 +497,13 @@ async fn main() -> Result<()> { ) .init(), } +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install().unwrap(); + let opts = Opts::parse(); + init_tracing(opts.log_format); let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? diff --git a/aptly-latest-snapshots/src/main.rs b/aptly-latest-snapshots/src/main.rs index 6b2211e..3637588 100644 --- a/aptly-latest-snapshots/src/main.rs +++ b/aptly-latest-snapshots/src/main.rs @@ -43,13 +43,8 @@ struct Opts { log_format: LogFormat, } -#[tokio::main] -async fn main() -> Result<()> { - color_eyre::install().unwrap(); - - let opts = Opts::parse(); - - match opts.log_format { +fn init_tracing(format: LogFormat) { + match format { LogFormat::Pretty => tracing_subscriber::registry() .with(ErrorLayer::default()) .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) @@ -63,6 +58,14 @@ async fn main() -> Result<()> { ) .init(), } +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install().unwrap(); + + let opts = Opts::parse(); + init_tracing(opts.log_format); let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? diff --git a/aptlyctl/src/main.rs b/aptlyctl/src/main.rs index 6fa60e0..292a320 100644 --- a/aptlyctl/src/main.rs +++ b/aptlyctl/src/main.rs @@ -72,12 +72,8 @@ struct Opts { log_format: LogFormat, } -#[tokio::main] -async fn main() -> Result { - color_eyre::install().unwrap(); - let opts = Opts::parse(); - - match opts.log_format { +fn init_tracing(format: LogFormat) { + match format { LogFormat::Pretty => tracing_subscriber::registry() .with(ErrorLayer::default()) .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) @@ -91,6 +87,13 @@ async fn main() -> Result { ) .init(), } +} + +#[tokio::main] +async fn main() -> Result { + color_eyre::install().unwrap(); + let opts = Opts::parse(); + init_tracing(opts.log_format); let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)? diff --git a/obs2aptly/src/main.rs b/obs2aptly/src/main.rs index 63ba2fc..ebe5b19 100644 --- a/obs2aptly/src/main.rs +++ b/obs2aptly/src/main.rs @@ -52,12 +52,8 @@ struct Opts { log_format: LogFormat, } -#[tokio::main] -async fn main() -> Result<()> { - color_eyre::install().unwrap(); - let opts = Opts::parse(); - - match opts.log_format { +fn init_tracing(format: LogFormat) { + match format { LogFormat::Pretty => tracing_subscriber::registry() .with(ErrorLayer::default()) .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::INFO)) @@ -71,6 +67,13 @@ async fn main() -> Result<()> { ) .init(), } +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install().unwrap(); + let opts = Opts::parse(); + init_tracing(opts.log_format); let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)? From 0a245c2b8037a04960eb7e309889482baa9459fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:23:33 +0000 Subject: [PATCH 3/3] Fix initialization order: parse opts and init tracing before color_eyre::install() --- apt2aptly/src/main.rs | 2 +- aptly-latest-snapshots/src/main.rs | 3 +-- aptlyctl/src/main.rs | 2 +- obs2aptly/src/main.rs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apt2aptly/src/main.rs b/apt2aptly/src/main.rs index 7993522..4d7c56f 100644 --- a/apt2aptly/src/main.rs +++ b/apt2aptly/src/main.rs @@ -501,9 +501,9 @@ fn init_tracing(format: LogFormat) { #[tokio::main] async fn main() -> Result<()> { - color_eyre::install().unwrap(); let opts = Opts::parse(); init_tracing(opts.log_format); + color_eyre::install()?; let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? diff --git a/aptly-latest-snapshots/src/main.rs b/aptly-latest-snapshots/src/main.rs index 3637588..d7681c3 100644 --- a/aptly-latest-snapshots/src/main.rs +++ b/aptly-latest-snapshots/src/main.rs @@ -62,10 +62,9 @@ fn init_tracing(format: LogFormat) { #[tokio::main] async fn main() -> Result<()> { - color_eyre::install().unwrap(); - let opts = Opts::parse(); init_tracing(opts.log_format); + color_eyre::install()?; let aptly = if let Some(token) = &opts.api_token { AptlyRest::new_with_token(opts.api_url.clone(), token)? diff --git a/aptlyctl/src/main.rs b/aptlyctl/src/main.rs index 292a320..53de1a6 100644 --- a/aptlyctl/src/main.rs +++ b/aptlyctl/src/main.rs @@ -91,9 +91,9 @@ fn init_tracing(format: LogFormat) { #[tokio::main] async fn main() -> Result { - color_eyre::install().unwrap(); let opts = Opts::parse(); init_tracing(opts.log_format); + color_eyre::install()?; let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)? diff --git a/obs2aptly/src/main.rs b/obs2aptly/src/main.rs index ebe5b19..c5c03bf 100644 --- a/obs2aptly/src/main.rs +++ b/obs2aptly/src/main.rs @@ -71,9 +71,9 @@ fn init_tracing(format: LogFormat) { #[tokio::main] async fn main() -> Result<()> { - color_eyre::install().unwrap(); let opts = Opts::parse(); init_tracing(opts.log_format); + color_eyre::install()?; let aptly = if let Some(token) = opts.api_token { AptlyRest::new_with_token(opts.api_url, &token)?