Skip to content

Commit 21d15b4

Browse files
committed
feat: improved logging support
1 parent 672dd8a commit 21d15b4

10 files changed

Lines changed: 275 additions & 92 deletions

File tree

Cargo.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ bytes-utils = "0.1.4"
6464

6565
# Logging
6666
tracing = "0.1.44"
67-
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
6867

6968
# Serialization and JSON
7069
serde = { version = "1.0.228", features = ["derive"] }

packages/docbox-http/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ serde_with.workspace = true
4545

4646
# Logging
4747
tracing.workspace = true
48-
tracing-subscriber.workspace = true
4948

5049
bytes.workspace = true
5150

packages/docbox/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ thiserror.workspace = true
3939

4040
# Logging
4141
tracing.workspace = true
42-
tracing-subscriber.workspace = true
42+
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] }
4343

4444
# Sentry
4545
sentry = { version = "=0.46.1", default-features = false, features = [

packages/docbox/src/logging.rs

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use thiserror::Error;
2+
3+
use crate::logging::{
4+
fmt::{LoggingFormatConfig, LoggingFormatConfigError},
5+
sentry::SentryLoggingConfig,
6+
};
7+
8+
/// Configuration for logging
9+
#[derive(Default)]
10+
pub struct LoggingConfig {
11+
pub format: LoggingFormatConfig,
12+
pub sentry: SentryLoggingConfig,
13+
}
14+
15+
#[derive(Debug, Error)]
16+
pub enum LoggingConfigError {
17+
#[error(transparent)]
18+
LoggingFormatConfig(#[from] LoggingFormatConfigError),
19+
}
20+
21+
impl LoggingConfig {
22+
pub fn from_env() -> Result<Self, LoggingConfigError> {
23+
let format = LoggingFormatConfig::from_env()?;
24+
let sentry = SentryLoggingConfig::from_env()?;
25+
Ok(Self { format, sentry })
26+
}
27+
}

packages/docbox/src/logging/fmt.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use std::str::{FromStr, ParseBoolError};
2+
3+
use thiserror::Error;
4+
use tracing::Subscriber;
5+
use tracing_subscriber::{EnvFilter, Layer, fmt, registry::LookupSpan};
6+
7+
/// Logging format to use
8+
#[derive(Debug, Default)]
9+
pub enum LoggingFormat {
10+
Text,
11+
#[default]
12+
Json,
13+
}
14+
15+
#[derive(Debug, Error)]
16+
#[error("unknown logging format: {0}")]
17+
pub struct UnknownLoggingFormat(String);
18+
19+
impl FromStr for LoggingFormat {
20+
type Err = UnknownLoggingFormat;
21+
22+
fn from_str(value: &str) -> Result<Self, Self::Err> {
23+
match value {
24+
"text" => Ok(LoggingFormat::Text),
25+
"json" => Ok(LoggingFormat::Json),
26+
value => Err(UnknownLoggingFormat(value.to_string())),
27+
}
28+
}
29+
}
30+
31+
impl LoggingFormat {
32+
pub fn from_env() -> Result<LoggingFormat, UnknownLoggingFormat> {
33+
let format = match std::env::var("DOCBOX_LOGGING_FORMAT") {
34+
Ok(value) => value,
35+
Err(_) => return Ok(LoggingFormat::default()),
36+
};
37+
38+
format.parse()
39+
}
40+
}
41+
42+
#[derive(Debug, Default)]
43+
pub struct LoggingFormatConfig {
44+
pub format: LoggingFormat,
45+
pub allow_noisy: bool,
46+
}
47+
48+
#[derive(Debug, Error)]
49+
pub enum LoggingFormatConfigError {
50+
#[error(transparent)]
51+
LoggingFormat(#[from] UnknownLoggingFormat),
52+
#[error("failed to parse DOCBOX_LOGGING_ALLOW_NOISY")]
53+
InvalidAllowNoisy(ParseBoolError),
54+
}
55+
56+
impl LoggingFormatConfig {
57+
pub fn from_env() -> Result<Self, LoggingFormatConfigError> {
58+
let format = LoggingFormat::from_env()?;
59+
let allow_noisy = std::env::var("DOCBOX_LOGGING_ALLOW_NOISY")
60+
.ok()
61+
.map(|value| value.parse::<bool>())
62+
.transpose()
63+
.map_err(LoggingFormatConfigError::InvalidAllowNoisy)?
64+
.unwrap_or_default();
65+
66+
Ok(Self {
67+
format,
68+
allow_noisy,
69+
})
70+
}
71+
}
72+
73+
/// Create a formatting layer from the provided format config
74+
pub fn fmt_layer<S>(config: LoggingFormatConfig) -> Box<dyn Layer<S> + Send + Sync + 'static>
75+
where
76+
S: Subscriber + for<'a> LookupSpan<'a>,
77+
{
78+
match config.format {
79+
LoggingFormat::Text => text_fmt_layer().boxed(),
80+
LoggingFormat::Json => json_fmt_layer().boxed(),
81+
}
82+
}
83+
84+
/// Layer the outputs content using a text based formatting
85+
pub fn text_fmt_layer<S>() -> fmt::Layer<S> {
86+
fmt::layer()
87+
// Display source code file paths
88+
.with_file(true)
89+
// Display source code line numbers
90+
.with_line_number(true)
91+
// Don't display the event's target (module path)
92+
.with_target(false)
93+
}
94+
95+
/// Layer that outputs content using a JSON formatting
96+
pub fn json_fmt_layer<S>()
97+
-> fmt::Layer<S, fmt::format::JsonFields, fmt::format::Format<fmt::format::Json>> {
98+
fmt::layer::<S>()
99+
.json()
100+
.with_span_list(true)
101+
// Display source code file paths
102+
.with_file(true)
103+
// Display source code line numbers
104+
.with_line_number(true)
105+
// Don't display the event's target (module path)
106+
.with_target(false)
107+
}
108+
109+
pub fn filter_layer(allow_noisy: bool) -> EnvFilter {
110+
if allow_noisy {
111+
return EnvFilter::from_default_env();
112+
}
113+
114+
EnvFilter::from_default_env()
115+
// Increase logging requirements for noisy dependencies
116+
.add_directive(
117+
"aws_sdk_secretsmanager=info"
118+
.parse()
119+
.expect("directive was invalid"),
120+
)
121+
.add_directive("aws_runtime=info".parse().expect("directive was invalid"))
122+
.add_directive(
123+
"aws_smithy_runtime=info"
124+
.parse()
125+
.expect("directive was invalid"),
126+
)
127+
.add_directive("hyper_util=info".parse().expect("directive was invalid"))
128+
.add_directive("aws_sdk_sqs=info".parse().expect("directive was invalid"))
129+
.add_directive("h2=info".parse().expect("directive was invalid"))
130+
}

packages/docbox/src/logging/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::error::Error;
2+
3+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
4+
5+
use crate::logging::{
6+
config::LoggingConfig,
7+
fmt::{filter_layer, fmt_layer},
8+
sentry::sentry_layer,
9+
};
10+
11+
pub mod config;
12+
pub mod fmt;
13+
pub mod sentry;
14+
15+
/// Guards that must be held for active loggers
16+
#[derive(Default)]
17+
pub struct LoggingGuards {
18+
sentry: Option<::sentry::ClientInitGuard>,
19+
}
20+
21+
pub fn init_logging(config: LoggingConfig) -> Result<LoggingGuards, Box<dyn Error>> {
22+
let mut guards = LoggingGuards::default();
23+
24+
let filter_layer = filter_layer(config.format.allow_noisy);
25+
26+
let mut sentry = None;
27+
28+
if let Some((sentry_layer, client_init_guard)) = sentry_layer(config.sentry) {
29+
guards.sentry = Some(client_init_guard);
30+
sentry = Some(sentry_layer);
31+
}
32+
33+
tracing_subscriber::registry()
34+
.with(fmt_layer(config.format))
35+
.with(sentry)
36+
.with(filter_layer)
37+
.init();
38+
39+
Ok(guards)
40+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use sentry_tracing::SentryLayer;
2+
use tracing::{Level, Subscriber};
3+
use tracing_subscriber::registry::LookupSpan;
4+
5+
use crate::logging::config::LoggingConfigError;
6+
7+
/// Configuration for sentry logging
8+
#[derive(Default)]
9+
pub struct SentryLoggingConfig {
10+
pub dsn: Option<String>,
11+
}
12+
13+
impl SentryLoggingConfig {
14+
pub fn from_env() -> Result<Self, LoggingConfigError> {
15+
let dsn = std::env::var("SENTRY_DSN")
16+
.ok()
17+
.or(std::env::var("DOCBOX_SENTRY_DSN").ok());
18+
19+
Ok(Self { dsn })
20+
}
21+
}
22+
23+
pub fn sentry_layer<S>(
24+
config: SentryLoggingConfig,
25+
) -> Option<(SentryLayer<S>, sentry::ClientInitGuard)>
26+
where
27+
S: Subscriber + for<'a> LookupSpan<'a>,
28+
{
29+
let dsn = config.dsn?;
30+
let options = sentry::ClientOptions {
31+
release: sentry::release_name!(),
32+
..Default::default()
33+
};
34+
let client_init_guard = sentry::init((dsn, options));
35+
36+
let sentry_layer = sentry_tracing::layer()
37+
.event_filter(|event| {
38+
match event.level() {
39+
&Level::ERROR => {
40+
// Ignore errors emitted from the docbox_web_scraper when emitting
41+
// errors to sentry (These are errors caused by the upstream site)
42+
if let Some(module_path) = event.module_path()
43+
&& module_path.starts_with("docbox_web_scraper")
44+
{
45+
return sentry_tracing::EventFilter::Ignore;
46+
}
47+
48+
sentry_tracing::EventFilter::Event
49+
}
50+
&Level::WARN | &Level::INFO => sentry_tracing::EventFilter::Breadcrumb,
51+
&Level::DEBUG | &Level::TRACE => sentry_tracing::EventFilter::Ignore,
52+
}
53+
})
54+
.enable_span_attributes();
55+
56+
Some((sentry_layer, client_init_guard))
57+
}

0 commit comments

Comments
 (0)