Tracing with axum/tower_http via layer

This commit is contained in:
Daniel Flanagan 2024-05-15 17:13:41 -05:00
parent d5a3d1582d
commit 27dd80830f
5 changed files with 82 additions and 22 deletions

11
Cargo.lock generated
View file

@ -814,11 +814,9 @@ dependencies = [
"minijinja", "minijinja",
"notify", "notify",
"pathdiff", "pathdiff",
"redact",
"serde", "serde",
"thiserror", "thiserror",
"tokio", "tokio",
"tower",
"tower-http", "tower-http",
"tower-livereload", "tower-livereload",
"tracing", "tracing",
@ -1123,15 +1121,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "redact"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7301863b5e5486c9f18320ccc1aedce9a5fdf3056ae89fa021933d6f054430f"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.4.1" version = "0.4.1"

View file

@ -19,20 +19,18 @@ panic = "abort"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
axum = { version = "0.7.5", features = ["macros", "tokio"] } axum = { version = "0.7.5", features = ["macros", "tokio", "tracing"] }
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive", "env"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
config = "0.14.0" config = "0.14.0"
futures = "0.3.30" futures = "0.3.30"
minijinja = { version = "2.0.1", features = ["loader"] } minijinja = { version = "2.0.1", features = ["loader"] }
notify = "6.1.1" notify = "6.1.1"
pathdiff = "0.2.1" pathdiff = "0.2.1"
redact = { version = "0.1.9", features = ["serde"] }
serde = "1.0.201" serde = "1.0.201"
thiserror = "1.0.60" thiserror = "1.0.60"
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
tower = "0.4.13" tower-http = { version = "0.5.2", features = ["fs", "trace"] }
tower-http = { version = "0.5.2", features = ["fs"] }
tower-livereload = "0.9.2" tower-livereload = "0.9.2"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View file

@ -1,4 +1,4 @@
use crate::{prelude::*, router::NewRouterError}; use crate::{observe, prelude::*, router::NewRouterError};
use prelude::*; use prelude::*;
use thiserror::Error; use thiserror::Error;
@ -13,6 +13,9 @@ mod prelude {
pub struct Cli { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Commands, command: Commands,
#[arg(global = true, long, value_enum, default_value = "info,lyrs=trace")]
log_env_filter: String,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -64,10 +67,14 @@ pub fn cli() -> Cli {
pub enum ExecError { pub enum ExecError {
#[error("run error: {0}")] #[error("run error: {0}")]
Run(#[from] RunError), Run(#[from] RunError),
#[error("{0}")]
Eyre(#[from] color_eyre::Report),
} }
impl Cli { impl Cli {
pub async fn exec(self) -> Result<(), ExecError> { pub async fn exec(self) -> Result<(), ExecError> {
observe::setup_logging(&self.log_env_filter)?;
match self.command { match self.command {
Commands::Run(args) => Ok(args.run().await?), Commands::Run(args) => Ok(args.run().await?),
} }

View file

@ -1,11 +1,23 @@
pub fn setup_logging() { use crate::prelude::*;
color_eyre::install().expect("Failed to install color_eyre");
pub fn setup_logging(env_filter: &str) -> Result<(), color_eyre::Report> {
color_eyre::install()?;
let filter = tracing_subscriber::EnvFilter::builder() let filter = tracing_subscriber::EnvFilter::builder()
.with_default_directive(<tracing_subscriber::filter::Directive>::from( .with_default_directive(<tracing_subscriber::filter::Directive>::from(
tracing::level_filters::LevelFilter::TRACE, tracing::level_filters::LevelFilter::TRACE,
)) ))
.parse_lossy("info,lyrs=trace"); .parse_lossy(env_filter);
tracing_subscriber::fmt().with_env_filter(filter).init(); tracing_subscriber::fmt().with_env_filter(filter).init();
let filter = tracing_subscriber::EnvFilter::builder()
.with_default_directive(<tracing_subscriber::filter::Directive>::from(
tracing::level_filters::LevelFilter::TRACE,
))
.parse_lossy(env_filter);
info!("{filter}");
Ok(())
} }

View file

@ -1,3 +1,5 @@
use std::time::Duration;
use crate::{ use crate::{
file_watcher::FileWatcher, file_watcher::FileWatcher,
prelude::*, prelude::*,
@ -5,16 +7,19 @@ use crate::{
static_files, static_files,
}; };
use axum::{ use axum::{
extract::State, body::Bytes,
http::StatusCode, extract::{MatchedPath, State},
http::{HeaderMap, Request, Response, StatusCode},
response::{Html, IntoResponse}, response::{Html, IntoResponse},
routing::get, routing::get,
Router, Router,
}; };
use minijinja::context; use minijinja::context;
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
use tower_livereload::LiveReloadLayer; use tower_livereload::LiveReloadLayer;
use thiserror::Error; use thiserror::Error;
use tracing::{info_span, Span};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum NewRouterError { pub enum NewRouterError {
@ -71,6 +76,52 @@ pub async fn router(
.route("/", get(index)) .route("/", get(index))
.route("/about", get(about)) .route("/about", get(about))
.nest_service("/static", static_file_service) .nest_service("/static", static_file_service)
// `TraceLayer` is provided by tower-http so you have to add that as a dependency.
// It provides good defaults but is also very customizable.
//
// See https://docs.rs/tower-http/0.1.1/tower_http/trace/index.html for more details.
//
// If you want to customize the behavior using closures here is how.
.layer(
TraceLayer::new_for_http()
.make_span_with(|request: &Request<_>| {
// Log the matched route's path (with placeholders not filled in).
// Use request.uri() or OriginalUri if you want the real path.
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
})
.on_request(|_request: &Request<_>, _span: &Span| {
// You can use `_span.record("some_other_field", value)` in one of these
// closures to attach a value to the initially empty field in the info_span
// created above.
})
.on_response(|response: &Response<_>, latency: Duration, _span: &Span| {
trace!("on_response: {response:?} in {latency:?}");
})
.on_body_chunk(|_chunk: &Bytes, _latency: Duration, _span: &Span| {
// ...
})
.on_eos(
|_trailers: Option<&HeaderMap>, _stream_duration: Duration, _span: &Span| {
// ...
},
)
.on_failure(
|error: ServerErrorsFailureClass, latency: Duration, _span: &Span| {
error!("on_failure: {error:?} in {latency:?}");
// ...
},
),
)
.with_state(state.clone()); .with_state(state.clone());
if let Some(lr) = live_reload_layer { if let Some(lr) = live_reload_layer {
@ -82,6 +133,7 @@ pub async fn router(
Ok((result, watchers)) Ok((result, watchers))
} }
#[instrument(skip(state))]
async fn index(State(state): State<AppState>) -> ReqResult<Html<String>> { async fn index(State(state): State<AppState>) -> ReqResult<Html<String>> {
Ok(Html( Ok(Html(
state state
@ -90,6 +142,8 @@ async fn index(State(state): State<AppState>) -> ReqResult<Html<String>> {
.await?, .await?,
)) ))
} }
#[instrument(skip(state))]
async fn about(State(state): State<AppState>) -> ReqResult<Html<String>> { async fn about(State(state): State<AppState>) -> ReqResult<Html<String>> {
Ok(Html( Ok(Html(
state state