pub extern crate anyhow;
pub extern crate clap;
pub extern crate tracing;
pub extern crate tracing_subscriber;
#[cfg(feature = "macros")]
pub extern crate entrypoint_macros;
#[cfg(feature = "macros")]
pub mod macros {
pub use crate::entrypoint_macros::entrypoint;
pub use crate::entrypoint_macros::DotEnvDefault;
pub use crate::entrypoint_macros::LoggerDefault;
}
pub mod prelude {
pub use crate::anyhow;
pub use crate::anyhow::Context;
pub use crate::clap;
pub use crate::clap::Parser;
pub use crate::tracing;
pub use crate::tracing::{
debug, enabled, error, event, info, instrument, trace, warn, Level, Subscriber,
};
pub use crate::tracing::{debug_span, error_span, info_span, span, trace_span, warn_span};
pub use crate::tracing_subscriber;
pub use crate::tracing_subscriber::filter::LevelFilter;
pub use crate::tracing_subscriber::fmt::{
format::{Compact, Format, Full, Json, Pretty},
FormatEvent, FormatFields, Layer, MakeWriter,
};
pub use crate::tracing_subscriber::prelude::*;
pub use crate::tracing_subscriber::registry::LookupSpan;
pub use crate::tracing_subscriber::reload;
pub use crate::tracing_subscriber::Registry;
pub use crate::Entrypoint;
pub use crate::{DotEnvParser, DotEnvParserConfig};
pub use crate::{Logger, LoggerConfig};
#[cfg(feature = "macros")]
pub use crate::macros::*;
}
pub use crate::prelude::*;
pub trait Entrypoint: clap::Parser + DotEnvParserConfig + LoggerConfig {
fn entrypoint<F, T>(self, function: F) -> anyhow::Result<T>
where
F: FnOnce(Self) -> anyhow::Result<T>,
{
let entrypoint = {
let _log = tracing::subscriber::set_default(
Registry::default().with(self.default_log_layer()),
);
self.process_dotenv_files()?;
Self::parse() .process_dotenv_files()? .log_init(None)?
};
info!("setup/config complete; executing entrypoint function");
function(entrypoint)
}
}
impl<T: clap::Parser + DotEnvParserConfig + LoggerConfig> Entrypoint for T {}
pub trait LoggerConfig: clap::Parser {
fn bypass_log_init(&self) -> bool {
false
}
fn default_log_level(&self) -> LevelFilter {
tracing_subscriber::fmt::Subscriber::DEFAULT_MAX_LEVEL
}
fn default_log_format<S, N>(&self) -> impl FormatEvent<S, N> + Send + Sync + 'static
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'writer> FormatFields<'writer> + 'static,
{
Format::default()
}
fn default_log_writer(&self) -> impl for<'writer> MakeWriter<'writer> + Send + Sync + 'static {
std::io::stdout
}
fn default_log_layer(
&self,
) -> Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync + 'static> {
let (layer, _) = reload::Layer::new(
tracing_subscriber::fmt::Layer::default()
.event_format(self.default_log_format())
.with_writer(self.default_log_writer())
.with_filter(self.default_log_level()),
);
layer.boxed()
}
}
pub trait Logger: LoggerConfig {
fn log_init(
self,
layers: Option<Vec<Box<dyn tracing_subscriber::Layer<Registry> + Send + Sync + 'static>>>,
) -> anyhow::Result<Self> {
let layers = match (self.bypass_log_init(), &layers) {
(false, Some(_)) => {
anyhow::bail!("bypass_log_init() is false, but layers were passed into log_init()");
}
(false, None) => Some(vec![self.default_log_layer()]),
(true, _) => layers,
};
if layers.is_some()
&& tracing_subscriber::registry()
.with(layers)
.try_init()
.is_err()
{
anyhow::bail!("tracing::subscriber::set_global_default failed");
}
info!(
"log level: {}",
LevelFilter::current()
.into_level()
.expect("invalid LevelFilter::current()")
);
Ok(self)
}
}
impl<T: LoggerConfig> Logger for T {}
pub trait DotEnvParserConfig: clap::Parser {
fn additional_dotenv_files(&self) -> Option<Vec<std::path::PathBuf>> {
None
}
fn dotenv_can_override(&self) -> bool {
false
}
}
pub trait DotEnvParser: DotEnvParserConfig {
fn process_dotenv_files(self) -> anyhow::Result<Self> {
if self.dotenv_can_override() {
dotenvy::dotenv_override()
.map(|file| info!("dotenv::from_filename_override({})", file.display()))
} else {
dotenvy::dotenv().map(|file| info!("dotenv::from_filename({})", file.display()))
}
.map_err(|_| warn!("no .env file found"))
.unwrap_or(());
self.additional_dotenv_files().map_or(Ok(()), |files| {
#[allow(clippy::manual_try_fold)]
files.into_iter().fold(Ok(()), |accum, file| {
let process = |res: Result<std::path::PathBuf, dotenvy::Error>, msg| {
res.map(|_| info!(msg)).map_err(|e| {
error!(msg);
e
})
};
if self.dotenv_can_override() {
process(
dotenvy::from_filename_override(file.clone()),
format!("dotenv::from_filename_override({})", file.display()),
)
} else {
process(
dotenvy::from_filename(file.clone()),
format!("dotenv::from_filename({})", file.display()),
)
}
.and(accum)
})
})?;
Ok(self)
}
}
impl<T: DotEnvParserConfig> DotEnvParser for T {}