summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.sample4
-rw-r--r--Cargo.lock39
-rw-r--r--Cargo.toml2
-rw-r--r--src/db.rs6
-rw-r--r--src/error.rs3
-rw-r--r--src/handlers.rs24
-rw-r--r--src/main.rs17
-rw-r--r--src/models.rs2
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
diff --git a/Cargo.lock b/Cargo.lock
index 6dc9fd0..6dee477 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
]
diff --git a/Cargo.toml b/Cargo.toml
index f9da302..996b163 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/src/db.rs b/src/db.rs
index 7e3610b..21c7db4 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -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]