diff --git a/Cargo.lock b/Cargo.lock index 817bf5e97..6f9afb6c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2225,6 +2225,8 @@ name = "docs_rs_logging" version = "0.0.0" dependencies = [ "anyhow", + "docs_rs_config", + "docs_rs_env_vars", "docs_rs_utils", "sentry", "tracing", diff --git a/crates/bin/cratesfyi/src/main.rs b/crates/bin/cratesfyi/src/main.rs index 891051302..ccdb6c767 100644 --- a/crates/bin/cratesfyi/src/main.rs +++ b/crates/bin/cratesfyi/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Result; use clap::Parser; use cratesfyi::daemon::start_daemon; +use docs_rs_config::AppConfig as _; use docs_rs_context::Context; use std::env; use tokio::runtime; @@ -8,8 +9,11 @@ use tokio::runtime; fn main() { // set the global log::logger for backwards compatibility // through rustwide. - docs_rs_builder::logging::init(); - let guard = docs_rs_logging::init().expect("error initializing logging"); + let logging_config = + docs_rs_logging::Config::from_environment().expect("error loading logging config"); + docs_rs_builder::logging::init(&logging_config); + let guard = + docs_rs_logging::init_with_config(&logging_config).expect("error initializing logging"); if let Err(err) = CommandLine::parse().handle_args() { eprintln!("error running watcher: {:?}", err); diff --git a/crates/bin/docs_rs_admin/src/main.rs b/crates/bin/docs_rs_admin/src/main.rs index 3293da44f..5e52a97d0 100644 --- a/crates/bin/docs_rs_admin/src/main.rs +++ b/crates/bin/docs_rs_admin/src/main.rs @@ -26,7 +26,7 @@ use std::iter; #[tokio::main] async fn main() -> Result<()> { - let _guard = docs_rs_logging::init().context("error initializing logging")?; + let _guard = docs_rs_logging::init_from_environment().context("error initializing logging")?; if let Err(err) = CommandLine::parse().handle_args().await { eprintln!("error running admin CLI: {:?}", err); diff --git a/crates/bin/docs_rs_builder/src/logging.rs b/crates/bin/docs_rs_builder/src/logging.rs index da1ddded1..ca4b978a7 100644 --- a/crates/bin/docs_rs_builder/src/logging.rs +++ b/crates/bin/docs_rs_builder/src/logging.rs @@ -1,5 +1,10 @@ +use docs_rs_logging::Config; use tracing_log::LogTracer; -pub fn init() { - rustwide::logging::init_with(LogTracer::new()); +pub fn init(config: &Config) { + if config.log_build_logs { + rustwide::logging::init_with(LogTracer::new()); + } else { + rustwide::logging::init(); + } } diff --git a/crates/bin/docs_rs_builder/src/main.rs b/crates/bin/docs_rs_builder/src/main.rs index 9ed3616e7..1a328b6c4 100644 --- a/crates/bin/docs_rs_builder/src/main.rs +++ b/crates/bin/docs_rs_builder/src/main.rs @@ -10,8 +10,10 @@ use std::{path::PathBuf, sync::Arc}; use tokio::runtime; fn main() -> Result<()> { - docs_rs_builder::logging::init(); - let _guard = docs_rs_logging::init().context("error initializing logging")?; + let logging_config = docs_rs_logging::Config::from_environment()?; + docs_rs_builder::logging::init(&logging_config); + let _guard = + docs_rs_logging::init_with_config(&logging_config).context("error initializing logging")?; if let Err(err) = CommandLine::parse().handle_args() { eprintln!("error running builder: {:?}", err); diff --git a/crates/bin/docs_rs_builder/src/testing/test_env.rs b/crates/bin/docs_rs_builder/src/testing/test_env.rs index 006f329ed..e0db390e7 100644 --- a/crates/bin/docs_rs_builder/src/testing/test_env.rs +++ b/crates/bin/docs_rs_builder/src/testing/test_env.rs @@ -1,5 +1,6 @@ use crate::{Config as BuilderConfig, RustwideBuilder}; use anyhow::Result; +use docs_rs_config::AppConfig as _; pub(crate) type TestEnvironment = docs_rs_context::testing::BlockingTestEnvironment; @@ -9,7 +10,7 @@ pub(crate) trait TestEnvironmentExt { impl TestEnvironmentExt for TestEnvironment { fn build_builder(&self) -> Result { - crate::logging::init(); // initialize rustwide logging + crate::logging::init(&docs_rs_logging::Config::test_config()?); // initialize rustwide logging RustwideBuilder::init(self.config().clone(), self) } } diff --git a/crates/bin/docs_rs_import_release/src/main.rs b/crates/bin/docs_rs_import_release/src/main.rs index f81601950..5b67d51e2 100644 --- a/crates/bin/docs_rs_import_release/src/main.rs +++ b/crates/bin/docs_rs_import_release/src/main.rs @@ -11,7 +11,7 @@ use docs_rs_types::{KrateName, ReqVersion}; #[tokio::main] async fn main() -> Result<()> { - let _guard = docs_rs_logging::init().context("error initializing logging")?; + let _guard = docs_rs_logging::init_from_environment().context("error initializing logging")?; if let Err(err) = CommandLine::parse().handle_args().await { eprintln!("error importing release: {err:?}"); diff --git a/crates/bin/docs_rs_watcher/src/main.rs b/crates/bin/docs_rs_watcher/src/main.rs index 088932ceb..f89dc40b9 100644 --- a/crates/bin/docs_rs_watcher/src/main.rs +++ b/crates/bin/docs_rs_watcher/src/main.rs @@ -8,7 +8,7 @@ use std::sync::Arc; #[tokio::main] async fn main() -> Result<()> { - let _guard = docs_rs_logging::init().context("error initializing logging")?; + let _guard = docs_rs_logging::init_from_environment().context("error initializing logging")?; if let Err(err) = CommandLine::parse().handle_args().await { eprintln!("error running watcher: {:?}", err); diff --git a/crates/bin/docs_rs_web/src/main.rs b/crates/bin/docs_rs_web/src/main.rs index 6172d1a8d..962a27a24 100644 --- a/crates/bin/docs_rs_web/src/main.rs +++ b/crates/bin/docs_rs_web/src/main.rs @@ -17,7 +17,7 @@ struct Cli { #[tokio::main] async fn main() -> anyhow::Result<()> { - let _guard = docs_rs_logging::init().context("error initializing logging")?; + let _guard = docs_rs_logging::init_from_environment().context("error initializing logging")?; let args = Cli::parse(); let context = build_context().await?; diff --git a/crates/lib/docs_rs_logging/Cargo.toml b/crates/lib/docs_rs_logging/Cargo.toml index df8458df6..14a72fe49 100644 --- a/crates/lib/docs_rs_logging/Cargo.toml +++ b/crates/lib/docs_rs_logging/Cargo.toml @@ -5,11 +5,16 @@ repository.workspace = true edition.workspace = true [features] -testing = [] +testing = ["docs_rs_config/testing"] [dependencies] anyhow = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_utils = { path = "../docs_rs_utils" } sentry = { workspace = true } tracing = { workspace = true } tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } + +[dev-dependencies] +docs_rs_config = { path = "../docs_rs_config", features = ["testing"] } diff --git a/crates/lib/docs_rs_logging/src/config.rs b/crates/lib/docs_rs_logging/src/config.rs new file mode 100644 index 000000000..c6a766a9b --- /dev/null +++ b/crates/lib/docs_rs_logging/src/config.rs @@ -0,0 +1,55 @@ +use crate::LogFormat; +use docs_rs_config::AppConfig; +use docs_rs_env_vars::{env, maybe_env}; +use std::str::FromStr; +use tracing_subscriber::{EnvFilter, filter::Directive}; + +#[derive(Debug)] +pub struct SentryConfig { + pub dsn: sentry::types::Dsn, + pub traces_sample_rate: f32, +} + +#[derive(Debug)] +pub struct Config { + pub format: LogFormat, + pub filter: EnvFilter, + pub sentry: Option, + + /// Whether to output the build logs to stdout too, + /// or just store them on S3. + pub log_build_logs: bool, +} + +impl Config { + fn filter_from_env(default_directive: &str) -> anyhow::Result { + Ok(EnvFilter::builder() + .with_default_directive(Directive::from_str(default_directive)?) + .with_env_var("DOCSRS_LOG") + .from_env_lossy()) + } +} + +impl AppConfig for Config { + fn from_environment() -> anyhow::Result { + Ok(Self { + format: maybe_env("DOCSRS_LOG_FORMAT")?.unwrap_or_default(), + filter: Self::filter_from_env("info")?, + sentry: maybe_env("SENTRY_DSN")?.map(|dsn| SentryConfig { + dsn, + traces_sample_rate: env("SENTRY_TRACES_SAMPLE_RATE", 0.0).unwrap_or(0.0), + }), + log_build_logs: env("DOCSRS_LOG_BUILD_LOGS", true)?, + }) + } + + #[cfg(any(test, feature = "testing"))] + fn test_config() -> anyhow::Result { + Ok(Self { + format: LogFormat::Pretty, + filter: Self::filter_from_env("trace")?, + sentry: None, + log_build_logs: true, + }) + } +} diff --git a/crates/lib/docs_rs_logging/src/lib.rs b/crates/lib/docs_rs_logging/src/lib.rs index d56bdb9b6..363d1e8e5 100644 --- a/crates/lib/docs_rs_logging/src/lib.rs +++ b/crates/lib/docs_rs_logging/src/lib.rs @@ -1,12 +1,18 @@ +mod config; +pub mod log_format; #[cfg(feature = "testing")] pub mod testing; +pub use config::Config; +pub use log_format::LogFormat; + +use docs_rs_config::AppConfig as _; use sentry::{ TransactionContext, integrations::panic as sentry_panic, integrations::tracing as sentry_tracing, }; -use std::{env, str::FromStr as _, sync::Arc}; -use tracing_subscriber::{EnvFilter, filter::Directive, prelude::*}; +use std::sync::Arc; +use tracing_subscriber::prelude::*; /// defines the transaction name to be used for our rustwide builder. /// @@ -22,25 +28,21 @@ pub struct Guard { sentry_guard: Option, } -pub fn init() -> anyhow::Result { - let log_formatter = { - let log_format = env::var("DOCSRS_LOG_FORMAT").unwrap_or_default(); +pub fn init_from_environment() -> anyhow::Result { + init_with_config(&Config::from_environment()?) +} - if log_format == "json" { - tracing_subscriber::fmt::layer().json().boxed() - } else { - tracing_subscriber::fmt::layer().boxed() - } +pub fn init_with_config(config: &Config) -> anyhow::Result { + let log_formatter = match config.format { + LogFormat::Json => tracing_subscriber::fmt::layer().json().boxed(), + LogFormat::Pretty => tracing_subscriber::fmt::layer().boxed(), }; - let tracing_registry = tracing_subscriber::registry().with(log_formatter).with( - EnvFilter::builder() - .with_default_directive(Directive::from_str("info")?) - .with_env_var("DOCSRS_LOG") - .from_env_lossy(), - ); + let tracing_registry = tracing_subscriber::registry() + .with(log_formatter) + .with(config.filter.clone()); - let sentry_guard = if let Ok(sentry_dsn) = env::var("SENTRY_DSN") { + let sentry_guard = if let Some(sentry_config) = &config.sentry { tracing::subscriber::set_global_default(tracing_registry.with( sentry_tracing::layer().event_filter(|md| { if md.fields().field("reported_to_sentry").is_some() { @@ -51,11 +53,7 @@ pub fn init() -> anyhow::Result { }), ))?; - let traces_sample_rate = env::var("SENTRY_TRACES_SAMPLE_RATE") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(0.0); - + let sample_rate = sentry_config.traces_sample_rate; let traces_sampler = move |ctx: &TransactionContext| -> f32 { if let Some(sampled) = ctx.sampled() { // if the transaction was already marked as "to be sampled" by @@ -67,12 +65,12 @@ pub fn init() -> anyhow::Result { // record all transactions for builds 1. } else { - traces_sample_rate + sample_rate } }; Some(sentry::init(( - sentry_dsn, + sentry_config.dsn.clone(), sentry::ClientOptions { release: Some(docs_rs_utils::BUILD_VERSION.into()), attach_stacktrace: true, diff --git a/crates/lib/docs_rs_logging/src/log_format.rs b/crates/lib/docs_rs_logging/src/log_format.rs new file mode 100644 index 000000000..eafea3473 --- /dev/null +++ b/crates/lib/docs_rs_logging/src/log_format.rs @@ -0,0 +1,31 @@ +use std::{fmt, str::FromStr}; + +#[derive(Debug, Default)] +pub enum LogFormat { + Json, + #[default] + Pretty, +} + +#[derive(Debug)] +pub struct InvalidLogFormat(String); + +impl fmt::Display for InvalidLogFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid log format: {}", self.0) + } +} + +impl std::error::Error for InvalidLogFormat {} + +impl FromStr for LogFormat { + type Err = InvalidLogFormat; + + fn from_str(s: &str) -> Result { + match s { + "json" => Ok(Self::Json), + "pretty" => Ok(Self::Pretty), + _ => Err(InvalidLogFormat(s.to_string())), + } + } +} diff --git a/justfiles/testing.just b/justfiles/testing.just index f7c79175f..4b3d3b5e6 100644 --- a/justfiles/testing.just +++ b/justfiles/testing.just @@ -23,6 +23,7 @@ sqlx-prepare *args: _ensure_db_and_s3_are_running -- --all-targets --all-features for subcrate in crates/lib/* crates/bin/*; do + echo "Preparing SQLx metadata for ${subcrate}..." # NOTE: potential optimization: only run this command when `sqlx` # is in the dependencies of the subcrate. (cd "${subcrate}" && cargo sqlx prepare \