Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rework CLI introspections #292

Merged
merged 5 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions libs/pavex_cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,55 @@ use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;

const INTROSPECTION_HEADING: &str = "Introspection";

#[derive(Parser)]
#[clap(author, version = VERSION, about, long_about = None)]
#[clap(
author,
version = VERSION, about,
long_about = None,
after_long_help = "Use `pavex -h` rather than `pavex --help` for a more concise summary of the available options."
)]
pub struct Cli {
/// Pavex will expose the full error chain when reporting diagnostics.
///
/// It will also emit tracing output, both to stdout and to disk.
/// The file serialized on disk (`trace-[...].json`) can be opened in
/// Google Chrome by visiting chrome://tracing for further analysis.
#[clap(long, env = "PAVEX_DEBUG")]
pub debug: bool,
#[clap(long, env = "PAVEX_COLOR", default_value_t = Color::Auto)]
/// Color settings for the CLI output: auto, always, never.
pub color: Color,
#[clap(subcommand)]
pub command: Command,
#[clap(
long,
env = "PAVEX_DEBUG",
help = "Pavex will expose the full error chain when reporting diagnostics.",
long_help = "Pavex will expose the full error chain when reporting diagnostics.\nSet `PAVEX_DEBUG=1` to enable this option."
)]
pub debug: bool,
#[clap(
long,
env = "PAVEX_LOG",
help_heading = Some(INTROSPECTION_HEADING),
hide_short_help = true,
hide_env = true,
long_help = "Pavex will emit internal logs to the console.\nSet `PAVEX_LOG=true` to enable this option using an environment variable."
)]
pub log: bool,
#[clap(
long,
env = "PAVEX_LOG_FILTER",
help_heading = Some(INTROSPECTION_HEADING),
hide_short_help = true,
hide_env = true,
long_help = "Control which logs are emitted if `--log` or `--perf-profile` are enabled.\nIf no filter is specified, Pavex will default to `info,pavex=trace`."
)]
pub log_filter: Option<String>,
#[clap(
long,
env = "PAVEX_PERF_PROFILE",
help_heading = Some(INTROSPECTION_HEADING),
hide_short_help = true,
hide_env = true,
long_help = "Pavex will serialize to disk tracing information to profile command execution.\nThe file (`trace-[...].json`) can be opened using https://ui.perfetto.dev/ or in Google Chrome by visiting chrome://tracing.\nSet `PAVEX_PERF_PROFILE=true` to enable this option using an environment variable."
)]
pub perf_profile: bool,
}

// Same structure used by `cargo --version`.
Expand Down
101 changes: 76 additions & 25 deletions libs/pavex_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,9 @@ static PAVEX_CACHED_KEYSET: &str = include_str!("../jwks.json");
fn main() -> Result<ExitCode, miette::Error> {
let cli = Cli::parse();
init_miette_hook(&cli);
let _guard = cli.debug.then(init_telemetry);

let mut client = Client::new().color(cli.color.into());
if cli.debug {
client = client.debug();
} else {
client = client.no_debug();
}
let _guard = init_telemetry(cli.log_filter.clone(), cli.log, cli.perf_profile);

let client = pavexc_client(&cli);
let system_home_dir = xdg_home::home_dir().ok_or_else(|| {
miette::miette!("Failed to get the system home directory from the environment")
})?;
Expand Down Expand Up @@ -103,6 +97,30 @@ fn main() -> Result<ExitCode, miette::Error> {
.map_err(utils::anyhow2miette)
}

/// Propagate introspection options from `pavex` to pavexc`.
fn pavexc_client(cli: &Cli) -> Client {
let mut client = Client::new().color(cli.color.into());
if cli.debug {
client = client.debug();
} else {
client = client.no_debug();
}
if cli.log {
client = client.log();
} else {
client = client.no_log();
}
if cli.perf_profile {
client = client.perf_profile();
} else {
client = client.no_perf_profile();
}
if let Some(log_filter) = &cli.log_filter {
client = client.log_filter(log_filter.to_owned());
}
client
}

#[tracing::instrument("Generate server sdk", skip(client, locator, shell))]
fn generate(
shell: &mut Shell,
Expand Down Expand Up @@ -471,23 +489,56 @@ fn use_color_on_stderr(color_profile: Color) -> bool {
}
}

fn init_telemetry() -> FlushGuard {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_file(false)
.with_target(false)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_timer(tracing_subscriber::fmt::time::uptime());
let (chrome_layer, guard) = ChromeLayerBuilder::new().include_args(true).build();
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info,pavexc=trace"))
.unwrap();

tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(chrome_layer)
.init();
guard
fn init_telemetry(
log_filter: Option<String>,
console_logging: bool,
profiling: bool,
) -> Option<FlushGuard> {
let filter_layer = log_filter
.map(|f| EnvFilter::try_new(f).expect("Invalid log filter configuration"))
.unwrap_or_else(|| {
EnvFilter::try_new("info,pavex=trace").expect("Invalid log filter configuration")
});
let base = tracing_subscriber::registry().with(filter_layer);
let mut chrome_guard = None;
let trace_filename = format!(
"./trace-pavex-{}.json",
std::time::SystemTime::UNIX_EPOCH
.elapsed()
.unwrap()
.as_millis()
);

match console_logging {
true => {
let fmt_layer = tracing_subscriber::fmt::layer()
.with_file(false)
.with_target(false)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_timer(tracing_subscriber::fmt::time::uptime());
if profiling {
let (chrome_layer, guard) = ChromeLayerBuilder::new()
.file(trace_filename)
.include_args(true)
.build();
chrome_guard = Some(guard);
base.with(fmt_layer).with(chrome_layer).init();
} else {
base.with(fmt_layer).init();
}
}
false => {
if profiling {
let (chrome_layer, guard) = ChromeLayerBuilder::new()
.file(trace_filename)
.include_args(true)
.build();
chrome_guard = Some(guard);
base.with(chrome_layer).init()
}
}
}
chrome_guard
}

fn init_miette_hook(cli: &Cli) {
Expand Down
61 changes: 61 additions & 0 deletions libs/pavex_cli_client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct Client {
pavex_cli_path: Option<PathBuf>,
color: Color,
debug: bool,
log: bool,
log_filter: Option<String>,
perf_profile: bool,
}

impl Default for Client {
Expand All @@ -20,6 +23,9 @@ impl Default for Client {
pavex_cli_path: None,
color: Color::Auto,
debug: false,
log: false,
log_filter: None,
perf_profile: false,
}
}
}
Expand Down Expand Up @@ -50,6 +56,18 @@ impl Client {
cmd.arg("--debug");
}

if self.log {
cmd.arg("--log");
}

if let Some(filter) = self.log_filter {
cmd.arg("--log-filter").arg(filter);
}

if self.perf_profile {
cmd.arg("--perf-profile");
}

cmd
}

Expand Down Expand Up @@ -107,4 +125,47 @@ impl Client {
let cmd = self.command();
NewBuilder::new(cmd, path)
}

/// Enable logging.
///
/// `pavex` will emit internal log messages to the console.
pub fn log(mut self) -> Self {
self.log = true;
self
}

/// Disable logging.
///
/// `pavex` will not emit internal log messages to the console.
/// This is the default behaviour.
pub fn no_log(mut self) -> Self {
self.log = false;
self
}

/// Set the log filter.
///
/// Control which logs are emitted if `--log` or `--perf-profile` are enabled.
/// If no filter is specified, Pavex will default to `info,pavex=trace`.
pub fn log_filter(mut self, filter: String) -> Self {
self.log_filter = Some(filter);
self
}

/// Enable performance profiling.
///
/// `pavex` will serialize to disk tracing information to profile command execution.
pub fn perf_profile(mut self) -> Self {
self.perf_profile = true;
self
}

/// Disable performance profiling.
///
/// `pavex` will not serialize to disk tracing information to profile command execution.
/// This is the default behaviour.
pub fn no_perf_profile(mut self) -> Self {
self.perf_profile = false;
self
}
}
2 changes: 1 addition & 1 deletion libs/pavex_cli_client/src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl GenerateBuilder {
///
/// This method can be useful if you need to customize the command before running it.
/// If that's not your usecase, consider using [`GenerateBuilder::execute`] instead.
pub fn command(mut self) -> Result<std::process::Command, BlueprintPersistenceError> {
pub fn command(mut self) -> Result<Command, BlueprintPersistenceError> {
// TODO: Pass the blueprint via `stdin` instead of writing it to a file.
let bp_path = self.output_directory.join("blueprint.ron");
self.blueprint
Expand Down
Loading