diff options
| author | batsumaru <> | 2026-10-16 16:18:02 +0900 |
|---|---|---|
| committer | batsumaru <> | 2026-10-16 16:18:02 +0900 |
| commit | e45e9fa8b50daa028233fd739fe96a3bdc8ec54b (patch) | |
| tree | 680bc21040e0d1f2d121cd0da1087b6078840bb0 | |
| parent | 13219eb405db7c825d2875b3e88a7a53a73e8b7e (diff) | |
| -rw-r--r-- | .env.sample | 4 | ||||
| -rw-r--r-- | Cargo.lock | 39 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/db.rs | 6 | ||||
| -rw-r--r-- | src/error.rs | 3 | ||||
| -rw-r--r-- | src/handlers.rs | 24 | ||||
| -rw-r--r-- | src/main.rs | 17 | ||||
| -rw-r--r-- | src/models.rs | 2 |
8 files changed, 87 insertions, 10 deletions
diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..caaf694 --- /dev/null +++ b/.env.sample @@ -0,0 +1,4 @@ +DATABASE_URL=postgres://health:health@127.0.0.1:5432/health +SQLX_OFFLINE=false +HEALTH_BIND_ADDRESS=127.0.0.1:8991 +RUST_LOG=health_log=info
\ No newline at end of file @@ -3,6 +3,15 @@ version = 4 [[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -953,6 +962,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] name = "matchit" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1141,6 +1159,23 @@ dependencies = [ ] [[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1859,10 +1894,14 @@ version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex-automata", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] @@ -6,7 +6,7 @@ edition = "2024" [dependencies] anyhow = "1.0.104" tracing = "0.1" -tracing-subscriber = "0.3.23" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } axum = { version = "0.8.9", features = ["http2", "macros"] } tokio = { version = "1.53.1", features = ["full"] } sqlx = { version = "0.9.0", features = ["runtime-tokio", "tls-rustls", "postgres", "sqlx-toml", "derive", "macros", "uuid", "chrono", "bigdecimal"] } @@ -4,8 +4,10 @@ use std::env; const DATABASE_URL_FALLBACK: &str = "postgres://health:health@127.0.0.1:5432/health"; pub async fn connect() -> Result<PgPool, sqlx::Error> { - let database_url = - env::var("DATABASE_URL").unwrap_or_else(|_| DATABASE_URL_FALLBACK.to_string()); + let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| { + tracing::warn!("DATABASE_URL not set, falling back to the default local database"); + DATABASE_URL_FALLBACK.to_string() + }); PgPoolOptions::new() .max_connections(5) .connect(&database_url) diff --git a/src/error.rs b/src/error.rs index d16106a..6bc4fc5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -76,6 +76,9 @@ impl IntoResponse for AppError { AppError::NotFound => (StatusCode::NOT_FOUND, "not found".to_owned()), AppError::Rejection(status, message) => (status, message), }; + if status.is_client_error() { + tracing::debug!(%status, error = %message, "request failed"); + } (status, Json(json!({ "error": message }))).into_response() } } diff --git a/src/handlers.rs b/src/handlers.rs index 7067f3a..9ae87c3 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -29,6 +29,7 @@ pub async fn export_measurements( AppQuery(query): AppQuery<ListQuery>, ) -> Result<([(HeaderName, &'static str); 2], Vec<u8>), AppError> { let rows = fetch_measurements(&pool, &query).await?; + tracing::info!(count = rows.len(), "exporting measurements"); let mut writer = csv::WriterBuilder::new() .has_headers(false) .from_writer(Vec::new()); @@ -65,6 +66,14 @@ async fn fetch_measurements( { return Err(AppError::BadRequest("from must not be after to")); } + tracing::debug!( + from = ?query.from, + to = ?query.to, + metric_type = ?query.metric_type, + limit = ?limit, + order = ?query.order, + "fetching measurements" + ); // SQL can't bind ASC/DESC as a parameter. The CASE yields measured_at only when // `order` is desc (sorted DESC) and NULL otherwise, so that key ties for every // row and the ASC key decides the order. If this gets slow, use an if/else with a @@ -88,6 +97,7 @@ async fn fetch_measurements( ) .fetch_all(pool) .await?; + tracing::debug!(count = rows.len(), "fetched measurements"); Ok(rows) } @@ -110,6 +120,11 @@ pub async fn create_measurement( ) .fetch_one(&pool) .await?; + tracing::info!( + id = %measurement.id, + metric_type = ?measurement.metric_type, + "created measurement" + ); let location = format!("/measurements/{}", measurement.id); Ok(( StatusCode::CREATED, @@ -122,6 +137,7 @@ pub async fn get_measurement( State(pool): State<PgPool>, AppPath(id): AppPath<Uuid>, ) -> Result<Json<MeasurementView>, AppError> { + tracing::debug!(%id, "fetching measurement"); let measurement = sqlx::query_as!( MeasurementView, " @@ -160,6 +176,7 @@ pub async fn update_measurement( if result.rows_affected() == 0 { return Err(AppError::NotFound); } + tracing::info!(%id, "updated measurement"); Ok(StatusCode::NO_CONTENT) } @@ -167,9 +184,14 @@ pub async fn delete_measurement( State(pool): State<PgPool>, AppPath(id): AppPath<Uuid>, ) -> Result<StatusCode, AppError> { - sqlx::query!("DELETE FROM measurement WHERE id = $1", id) + let result = sqlx::query!("DELETE FROM measurement WHERE id = $1", id) .execute(&pool) .await?; + if result.rows_affected() == 0 { + tracing::debug!(%id, "measurement to delete did not exist"); + } else { + tracing::info!(%id, "deleted measurement"); + } Ok(StatusCode::NO_CONTENT) } diff --git a/src/main.rs b/src/main.rs index 4f34c0c..91d56b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use std::env; use axum::{Router, routing::get}; use dotenvy::dotenv; use sqlx::PgPool; +use tracing_subscriber::EnvFilter; use crate::error::AppError; @@ -38,14 +39,20 @@ fn router(pool: PgPool) -> Router { #[tokio::main] async fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt::init(); dotenv().ok(); + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), + ) + .init(); let pool = db::connect().await?; + tracing::info!("connected to database"); let app = router(pool); - let listener = tokio::net::TcpListener::bind( - env::var("HEALTH_BIND_ADDRESS").unwrap_or_else(|_| BIND_ADDRESS_FALLBACK.to_string()), - ) - .await?; + let bind_address = env::var("HEALTH_BIND_ADDRESS").unwrap_or_else(|_| { + tracing::info!("HEALTH_BIND_ADDRESS not set, falling back to {BIND_ADDRESS_FALLBACK}"); + BIND_ADDRESS_FALLBACK.to_string() + }); + let listener = tokio::net::TcpListener::bind(bind_address).await?; tracing::info!("listening at {}", listener.local_addr()?); axum::serve(listener, app).await?; Ok(()) diff --git a/src/models.rs b/src/models.rs index 480ef2f..c23dab7 100644 --- a/src/models.rs +++ b/src/models.rs @@ -87,7 +87,7 @@ pub struct MetricUnitPair { pub unit: Unit, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, Debug)] #[serde(rename_all = "lowercase")] pub enum Order { #[default] |
