Telemetry

This commit is contained in:
Alphonse Paix
2025-08-22 08:14:59 +02:00
parent ded2a611e2
commit 5cc5758097
11 changed files with 239 additions and 46 deletions

View File

@@ -1,3 +1,4 @@
use secrecy::{ExposeSecret, SecretString};
use serde::Deserialize;
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
@@ -19,24 +20,33 @@ pub struct Settings {
#[derive(Deserialize)]
pub struct DatabaseSettings {
pub username: String,
pub password: String,
pub password: SecretString,
pub port: u16,
pub host: String,
pub database_name: String,
}
impl DatabaseSettings {
pub fn connection_string(&self) -> String {
pub fn connection_string(&self) -> SecretString {
format!(
"postgres://{}:{}@{}:{}/{}",
self.username, self.password, self.host, self.port, self.database_name
self.username,
self.password.expose_secret(),
self.host,
self.port,
self.database_name
)
.into()
}
pub fn connection_string_without_db(&self) -> String {
pub fn connection_string_without_db(&self) -> SecretString {
format!(
"postgres://{}:{}@{}:{}",
self.username, self.password, self.host, self.port
self.username,
self.password.expose_secret(),
self.host,
self.port
)
.into()
}
}

View File

@@ -1,3 +1,4 @@
pub mod configuration;
pub mod routes;
pub mod startup;
pub mod telemetry;

View File

@@ -1,31 +1,20 @@
use secrecy::ExposeSecret;
use sqlx::PgPool;
use tokio::net::TcpListener;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use zero2prod::{configuration::get_configuration, startup::run};
use zero2prod::{configuration::get_configuration, startup::run, telemetry::init_subscriber};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!(
"{}=debug,tower_http=debug,axum::rejection=trace",
env!("CARGO_CRATE_NAME")
)
.into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
init_subscriber(std::io::stdout);
let configuration = get_configuration().expect("Failed to read configuration");
let listener = TcpListener::bind(format!("127.0.0.1:{}", configuration.application_port))
.await
.unwrap();
tracing::debug!("listening on {}", listener.local_addr().unwrap());
let connection_pool = PgPool::connect(&configuration.database.connection_string())
.await
.unwrap();
let connection_pool =
PgPool::connect(configuration.database.connection_string().expose_secret())
.await
.unwrap();
run(listener, connection_pool).await
}

View File

@@ -4,32 +4,53 @@ use serde::Deserialize;
use sqlx::PgPool;
use uuid::Uuid;
#[tracing::instrument(
name = "Adding a new subscriber",
skip(connection, form),
fields(
subscriber_email = %form.email,
subscriber_name = %form.name
)
)]
pub async fn subscribe(
State(connection): State<PgPool>,
form: Form<FormData>,
) -> impl IntoResponse {
match sqlx::query!(
if insert_subscriber(&connection, &form).await.is_err() {
StatusCode::INTERNAL_SERVER_ERROR
} else {
StatusCode::OK
}
}
#[tracing::instrument(
name = "Saving new subscriber details in the database",
skip(connection, form)
)]
pub async fn insert_subscriber(
connection: &PgPool,
form: &Form<FormData>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
insert into subscriptions (id, email, name, subscribed_at)
values ($1, $2, $3, $4);
"#,
INSERT INTO subscriptions (id, email, name, subscribed_at)
VALUES ($1, $2, $3, $4);
"#,
Uuid::new_v4(),
form.email,
form.name,
Utc::now()
)
.execute(&connection)
.execute(connection)
.await
{
Ok(_) => StatusCode::OK,
Err(e) => {
eprintln!("Failed to execute query: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
}
}
.map_err(|e| {
tracing::error!("Failed to execute query: {:?}", e);
e
})?;
Ok(())
}
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct FormData {
name: String,

View File

@@ -8,6 +8,7 @@ use axum::{
use sqlx::PgPool;
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer;
use uuid::Uuid;
pub async fn run(listener: TcpListener, connection_pool: PgPool) {
axum::serve(listener, app(connection_pool)).await.unwrap();
@@ -23,11 +24,13 @@ pub fn app(connection_pool: PgPool) -> Router {
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);
let request_id = Uuid::new_v4().to_string();
tracing::info_span!(
"http_request",
method = ?request.method(),
matched_path,
request_id,
some_other_field = tracing::field::Empty,
)
}),

22
src/telemetry.rs Normal file
View File

@@ -0,0 +1,22 @@
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::{fmt::MakeWriter, layer::SubscriberExt, util::SubscriberInitExt};
pub fn init_subscriber<Sink>(sink: Sink)
where
Sink: for<'a> MakeWriter<'a> + Send + Sync + 'static,
{
let formatting_layer = BunyanFormattingLayer::new(env!("CARGO_CRATE_NAME").into(), sink);
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!(
"{}=debug,tower_http=debug,axum::rejection=trace",
env!("CARGO_CRATE_NAME")
)
.into()
}),
)
.with(JsonStorageLayer)
.with(formatting_layer)
.init();
}