Add proper logging

This commit is contained in:
Yash Karandikar 2022-08-25 14:21:32 -05:00
parent 9ab0cb2a6e
commit 1c77822b49
5 changed files with 138 additions and 7 deletions

75
Cargo.lock generated
View file

@ -31,6 +31,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.61"
@ -937,6 +946,15 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
@ -1382,6 +1400,15 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
@ -1533,6 +1560,15 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@ -1826,6 +1862,8 @@ dependencies = [
"toml",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -1943,6 +1981,7 @@ dependencies = [
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@ -1988,6 +2027,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
dependencies = [
"ansi_term",
"matchers",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
@ -2127,6 +2196,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"

View file

@ -18,6 +18,8 @@ thiserror = "1.0.32"
tokio = { version = "1.20.1", features = ["full"] }
toml = "0.5.9"
tower = "0.4.13"
tower-http = { version = "0.3.4", features = ["fs"] }
tower-http = { version = "0.3.4", features = ["fs", "trace"] }
tracing = "0.1.36"
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
[features]

View file

@ -20,6 +20,7 @@ use axum_sessions::{async_session::MemoryStore, SessionLayer};
use serde::Deserialize;
use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
use std::net::SocketAddr;
use std::sync::Arc;
use tasks::RawTask;
use tasks::Task;
@ -27,6 +28,10 @@ use tera::Tera;
use thiserror::Error as ThisError;
use tower::ServiceBuilder;
use tower_http::services::ServeDir;
use tower_http::trace::TraceLayer;
use tracing::info;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::FmtSubscriber;
#[derive(ThisError, Debug)]
pub enum Error {
@ -97,27 +102,42 @@ pub struct Config {
connection_string: String,
template_dir: Option<String>,
static_dir: Option<std::path::PathBuf>,
address: Option<SocketAddr>,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let subscriber = FmtSubscriber::builder()
.with_env_filter(EnvFilter::from_env("TMTD_LOG"))
.with_ansi(true)
.with_file(true)
.with_line_number(true)
.finish();
tracing::subscriber::set_global_default(subscriber).unwrap();
let filename = std::env::var("TMTD_CONFIG").unwrap_or_else(|_| "config.toml".into());
info!("Loading file {}", filename);
let contents = std::fs::read_to_string(filename)?;
let config: Config = toml::from_str(&contents)?;
info!("Config loaded, connecting to database...");
let pool = Arc::new(
PgPoolOptions::new()
.connect(&config.connection_string)
.await?,
);
info!("Creating default tables (if needed)");
sqlx::query("create table if not exists users(id serial primary key, username text not null unique, password_hash text not null)").execute(&*pool).await?;
sqlx::query("create table if not exists tasks(id serial primary key, owner int not null, title text not null, description text not null, status int not null)").execute(&*pool).await?;
info!("Loading templates...");
let tera = Arc::new(Tera::new(&format!(
"{}/**/*.html",
config.template_dir.as_deref().unwrap_or("templates")
))?);
info!("Creating session layer");
let store = MemoryStore::new();
if config.secret.len() < 64 {
return Err("Secret must be at least 64 bytes!".to_string().into());
@ -154,10 +174,16 @@ async fn main() -> Result<(), Error> {
ServiceBuilder::new()
.layer(Extension(pool))
.layer(Extension(tera))
.layer(session_layer),
.layer(session_layer)
.layer(TraceLayer::new_for_http()),
);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
info!("Starting server...");
let address = config
.address
.unwrap_or_else(|| "0.0.0.0:3000".parse().unwrap());
info!("Server started on {}", address);
axum::Server::bind(&address)
.serve(app.into_make_service())
.await
.unwrap();
@ -170,7 +196,10 @@ macro_rules! login_or_redirect {
($session:expr, $to:literal) => {
match $session.get::<String>("logged_in_as") {
Some(username) => username,
None => return Err(::axum::response::Redirect::to($to).into()),
None => {
::tracing::warn!("User not logged in, redirecting to {}", $to);
return Err(::axum::response::Redirect::to($to).into());
}
}
};
}
@ -195,21 +224,25 @@ async fn homepage(
) -> Result<Html<String>, Error> {
let username = login_or_redirect!(session, "/login");
info!("Getting user ID...");
let (id,): (i32,) = sqlx::query_as("select id from users where username=$1")
.bind(&username)
.fetch_one(&*pool)
.await?;
info!("Getting tasks");
let tasks: Vec<RawTask> =
sqlx::query_as("select id,title,description,status from tasks where owner=$1")
.bind(id)
.fetch_all(&*pool)
.await?;
info!("Sorting tasks");
let mut tasks: Vec<Task> = tasks.into_iter().map(Task::from).collect();
tasks.sort_unstable();
tasks.reverse();
info!("rendering pagee");
let rendered = tera.render(
"home.html",
&ctx! {
@ -217,5 +250,6 @@ async fn homepage(
},
)?;
info!("Rendering finished");
Ok(Html(rendered))
}

View file

@ -16,6 +16,7 @@ use sqlx::FromRow;
use sqlx::{Pool, Postgres};
use std::sync::Arc;
use tera::Tera;
use tracing::info;
#[derive(Deserialize)]
pub struct BareTask {
@ -101,11 +102,13 @@ pub async fn update_form(
) -> Result<Html<String>, Error> {
let username = login_or_redirect!(session, "/login");
info!("Getting user id");
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
.bind(&username)
.fetch_one(&*pool)
.await?;
info!("Getting task");
let task: Option<RawTask> =
sqlx::query_as("select id,title,description,status from tasks where id=$1 and owner=$2")
.bind(id)
@ -114,6 +117,7 @@ pub async fn update_form(
.await?;
if let Some(task) = task {
info!("Rendering template");
let c = ctx! {
"task" => task,
"url" => uri.to_string()
@ -121,6 +125,7 @@ pub async fn update_form(
let rendered = tera.render("tasks/update.html", &c)?;
info!("Rendering finished, returning");
#[allow(clippy::needless_return)] // it's a compile error, clippy
return Ok(Html(rendered));
}
@ -167,11 +172,13 @@ pub async fn create_backend(
) -> Result<Redirect, Error> {
let username = login_or_redirect!(session, "/login");
info!("Getting user id");
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
.bind(&username)
.fetch_one(&*pool)
.await?;
info!("Inserting task...");
let (id,): (i32,) = sqlx::query_as(
"insert into tasks (owner,title, description, status) values ($1, $2, $3, $4) returning id",
)
@ -182,6 +189,7 @@ pub async fn create_backend(
.fetch_one(&*pool)
.await?;
info!("Done, redirecting");
Ok(Redirect::to(&format!("/tasks/{}", id)))
}
@ -193,11 +201,13 @@ pub async fn task_detail(
) -> Result<Html<String>, Error> {
let username = login_or_redirect!(session, "/login");
info!("Getting user id");
let (user_id,): (i32,) = sqlx::query_as("select id from users where username=$1")
.bind(&username)
.fetch_one(&*pool)
.await?;
info!("Getting task");
let task: Option<RawTask> =
sqlx::query_as("select id,title,description,status from tasks where id=$1 and owner=$2")
.bind(id)
@ -206,12 +216,14 @@ pub async fn task_detail(
.await?;
if let Some(task) = task.map(Task::from) {
info!("Rendering");
let c = ctx! {
"task" => task
};
let rendered = tera.render("tasks/detail.html", &c)?;
info!("Rendering complete, returning");
return Ok(Html(rendered));
}

View file

@ -14,6 +14,8 @@ use sqlx::{Pool, Postgres};
use std::sync::Arc;
use tera::Context;
use tera::Tera;
use tracing::info;
use tracing::warn;
#[derive(Clone, Debug, Deserialize)]
pub struct RawUser {
@ -26,6 +28,7 @@ pub async fn create_user(
Extension(pool): Extension<Arc<Pool<Postgres>>>,
) -> Result<Redirect, Error> {
let handle = tokio::task::spawn_blocking(move || {
info!("Encrypting password");
let salt = SaltString::generate(&mut OsRng);
let password_hash = Pbkdf2.hash_password(data.password.as_bytes(), &salt);
@ -34,6 +37,7 @@ pub async fn create_user(
let hash = handle.await??;
info!("Inserting into database");
sqlx::query("INSERT INTO users (username, password_hash) VALUES ($1, $2)")
.bind(data.username)
.bind(hash)
@ -48,17 +52,20 @@ pub async fn login_backend(
Extension(pool): Extension<Arc<Pool<Postgres>>>,
mut session: WritableSession,
) -> Result<Redirect, Error> {
info!("Fetching user hash");
let hash: Option<(String,)> =
sqlx::query_as("SELECT password_hash FROM users WHERE username=$1")
.bind(&data.username)
.fetch_optional(&*pool)
.await?;
let hash = hash
.map(|(h,)| h)
.ok_or_else(|| "Error: user not found. Have you registered?".to_string())?;
let hash = hash.map(|(h,)| h).ok_or_else(|| {
warn!("User was not found in the database");
"Error: user not found. Have you registered?".to_string()
})?;
let handle = tokio::task::spawn_blocking(move || {
info!("Verifying password");
let parsed_hash = PasswordHash::new(&hash)?;
Pbkdf2
.verify_password(data.password.as_bytes(), &parsed_hash)
@ -77,6 +84,7 @@ pub async fn login_backend(
macro_rules! login_and_redirect {
($session:expr, $to:literal) => {
if $session.get::<String>("logged_in_as").is_some() {
::tracing::warn!("User already logged in, redirecting to {}", $to);
return Err(::axum::response::Redirect::to($to).into());
}
};