Skip to content

Commit b96c5cd

Browse files
authored
Improve error logging and observability (#4443)
* Replace actix tracing with custom error tracing * Fix logging * wip: JSON logging * Use LABRINTH_FORMAT to change to JSON output * sqlx fix? * CI fix * Add tracing span info to HTTP requests * Merge Result and Option error wrapping * Add http.authorized to tracing
1 parent 7e84659 commit b96c5cd

File tree

9 files changed

+326
-31
lines changed

9 files changed

+326
-31
lines changed

Cargo.lock

Lines changed: 71 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ hex = "0.4.3"
6767
hickory-resolver = "0.25.2"
6868
hmac = "0.12.1"
6969
hyper = "1.6.0"
70+
tracing-ecs = "0.5.0"
7071
hyper-rustls = { version = "0.27.7", default-features = false, features = [
7172
"http1",
7273
"native-tokio",
@@ -165,9 +166,11 @@ tokio-stream = "0.1.17"
165166
tokio-util = "0.7.16"
166167
totp-rs = "5.7.0"
167168
tracing = "0.1.41"
168-
tracing-actix-web = "0.7.19"
169+
tracing-actix-web = { version = "0.7.19", default-features = false }
169170
tracing-error = "0.2.1"
170171
tracing-subscriber = "0.3.19"
172+
eyre = "0.6.12"
173+
color-eyre = "0.6.5"
171174
url = "2.5.4"
172175
urlencoding = "2.1.3"
173176
uuid = "1.17.0"

apps/labrinth/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ actix-web-prom = { workspace = true, features = ["process"] }
2323
tracing.workspace = true
2424
tracing-actix-web.workspace = true
2525
console-subscriber.workspace = true
26+
tracing-subscriber.workspace = true
27+
tracing-ecs.workspace = true
28+
eyre.workspace = true
29+
color-eyre.workspace = true
2630

2731
tokio = { workspace = true, features = ["sync", "rt-multi-thread"] }
2832
tokio-stream.workspace = true

apps/labrinth/src/main.rs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use actix_web::dev::Service;
12
use actix_web::middleware::from_fn;
23
use actix_web::{App, HttpServer};
34
use actix_web_prom::PrometheusMetricsBuilder;
45
use clap::Parser;
6+
57
use labrinth::app_config;
68
use labrinth::background_task::BackgroundTask;
79
use labrinth::database::redis::RedisPool;
@@ -13,9 +15,15 @@ use labrinth::util::env::parse_var;
1315
use labrinth::util::ratelimit::rate_limit_middleware;
1416
use labrinth::{check_env_vars, clickhouse, database, file_hosting, queue};
1517
use std::ffi::CStr;
18+
use std::str::FromStr;
1619
use std::sync::Arc;
17-
use tracing::{error, info};
20+
use tracing::level_filters::LevelFilter;
21+
use tracing::{Instrument, error, info, info_span};
1822
use tracing_actix_web::TracingLogger;
23+
use tracing_ecs::ECSLayerBuilder;
24+
use tracing_subscriber::EnvFilter;
25+
use tracing_subscriber::layer::SubscriberExt;
26+
use tracing_subscriber::util::SubscriberInitExt;
1927

2028
#[cfg(target_os = "linux")]
2129
#[global_allocator]
@@ -46,12 +54,59 @@ struct Args {
4654
run_background_task: Option<BackgroundTask>,
4755
}
4856

57+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
58+
enum OutputFormat {
59+
#[default]
60+
Human,
61+
Json,
62+
}
63+
64+
impl FromStr for OutputFormat {
65+
type Err = ();
66+
67+
fn from_str(s: &str) -> Result<Self, Self::Err> {
68+
match s {
69+
"human" => Ok(Self::Human),
70+
"json" => Ok(Self::Json),
71+
_ => Err(()),
72+
}
73+
}
74+
}
75+
4976
#[actix_rt::main]
5077
async fn main() -> std::io::Result<()> {
5178
let args = Args::parse();
5279

80+
color_eyre::install().expect("failed to install `color-eyre`");
5381
dotenvy::dotenv().ok();
54-
console_subscriber::init();
82+
let console_layer = console_subscriber::spawn();
83+
let env_filter = EnvFilter::builder()
84+
.with_default_directive(LevelFilter::INFO.into())
85+
.from_env_lossy();
86+
87+
let output_format =
88+
dotenvy::var("LABRINTH_FORMAT").map_or(OutputFormat::Human, |format| {
89+
format
90+
.parse::<OutputFormat>()
91+
.unwrap_or_else(|_| panic!("invalid output format '{format}'"))
92+
});
93+
94+
match output_format {
95+
OutputFormat::Human => {
96+
tracing_subscriber::registry()
97+
.with(console_layer)
98+
.with(env_filter)
99+
.with(tracing_subscriber::fmt::layer())
100+
.init();
101+
}
102+
OutputFormat::Json => {
103+
tracing_subscriber::registry()
104+
.with(console_layer)
105+
.with(env_filter)
106+
.with(ECSLayerBuilder::default().stdout())
107+
.init();
108+
}
109+
}
55110

56111
if check_env_vars() {
57112
error!("Some environment variables are missing!");
@@ -199,6 +254,33 @@ async fn main() -> std::io::Result<()> {
199254
HttpServer::new(move || {
200255
App::new()
201256
.wrap(TracingLogger::default())
257+
.wrap_fn(|req, srv| {
258+
// We capture the same fields as `tracing-actix-web`'s `RootSpanBuilder`.
259+
// See `root_span!` macro.
260+
let span = info_span!(
261+
"HTTP request",
262+
http.method = %req.method(),
263+
http.client_ip = %req.connection_info().realip_remote_addr().unwrap_or(""),
264+
http.user_agent = %req.headers().get("User-Agent").map_or("", |h| h.to_str().unwrap_or("")),
265+
http.target = %req.uri().path_and_query().map_or("", |p| p.as_str()),
266+
http.authenticated = %req.headers().get("Authorization").is_some()
267+
);
268+
269+
let fut = srv.call(req);
270+
async move {
271+
fut.await.inspect(|resp| {
272+
let _span = info_span!(
273+
"HTTP response",
274+
http.status = %resp.response().status().as_u16(),
275+
).entered();
276+
277+
resp.response()
278+
.error()
279+
.inspect(|err| log_error(err));
280+
})
281+
}
282+
.instrument(span)
283+
})
202284
.wrap(prometheus.clone())
203285
.wrap(from_fn(rate_limit_middleware))
204286
.wrap(actix_web::middleware::Compress::default())
@@ -209,3 +291,15 @@ async fn main() -> std::io::Result<()> {
209291
.run()
210292
.await
211293
}
294+
295+
fn log_error(err: &actix_web::Error) {
296+
if err.as_response_error().status_code().is_client_error() {
297+
tracing::debug!(
298+
"Error encountered while processing the incoming HTTP request: {err}"
299+
);
300+
} else {
301+
tracing::error!(
302+
"Error encountered while processing the incoming HTTP request: {err}"
303+
);
304+
}
305+
}

0 commit comments

Comments
 (0)