Create a special logging formatter for release builds

This commit is contained in:
Z. Charles Dziura 2024-10-02 12:41:25 -04:00
parent 8c6ffbfb5c
commit f83757702a
11 changed files with 163 additions and 75 deletions

View file

@ -1,7 +1,8 @@
HOSTNAME=localhost
PORT=42069
TOKEN_KEY=k4.local.hWoS2ZulK9xPEATtXH1Dvj_iynzqfUv5ER5_IFTg5-Q
DATABASE_URL=postgres://debt_pirate:HRURqlUmtjIy@192.168.122.251/debt_pirate
ASSETS_DIR=/home/zcdziura/Documents/Projects/debt-pirate/api/assets
DATABASE_URL=postgres://debt_pirate:HRURqlUmtjIy@192.168.122.251/debt_pirate
HOSTNAME=localhost
MAINTENANCE_USER_ACCOUNT=debt_pirate:HRURqlUmtjIy
PORT=42069
RUST_LOG=debt_pirate=trace
SEND_VERIFICATION_EMAIL=false
TOKEN_KEY=k4.local.hWoS2ZulK9xPEATtXH1Dvj_iynzqfUv5ER5_IFTg5-Q

View file

@ -7,15 +7,15 @@
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'auth-test'",
"name": "Debug executable 'debt-pirate'",
"cargo": {
"args": [
"build",
"--bin=auth-test",
"--package=auth-test"
"--bin=debt-pirate",
"--package=debt-pirate"
],
"filter": {
"name": "auth-test",
"name": "debt-pirate",
"kind": "bin"
}
},
@ -25,16 +25,16 @@
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'auth-test'",
"name": "Debug unit tests in executable 'debt-pirate'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=auth-test",
"--package=auth-test"
"--bin=debt-pirate",
"--package=debt-pirate"
],
"filter": {
"name": "auth-test",
"name": "debt-pirate",
"kind": "bin"
}
},

View file

@ -25,9 +25,7 @@ lettre = { version = "0.11", default-features = false, features = [
"tokio1",
"tokio1-rustls-tls",
] }
log = "0.4"
num_cpus = "1.16"
once_cell = "1.19"
pasetors = "0.7"
serde = { version = "1.0", features = ["derive", "rc", "std"] }
serde_json = "1.0"

View file

@ -123,7 +123,7 @@
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: sans-serif; font-size: 16px; line-height: 24px;">
<p style="margin: 0;">Click or tap the button below to confirm your email address. If you didn't create an account with <a href="#">Auth-Test</a>, you can safely delete this email.</p>
<p style="margin: 0;">Click or tap the button below to confirm your email address. If you didn't create an account with <a href="#">Debt Pirate</a>, you can safely delete this email.</p>
</td>
</tr>
<!-- end copy -->
@ -160,7 +160,7 @@
<!-- start copy -->
<tr>
<td align="left" bgcolor="#ffffff" style="padding: 24px; font-family: sans-serif; font-size: 16px; line-height: 24px; border-bottom: 3px solid #d4dadf">
<p style="margin: 0;">Thank you,<br> The Auth-Test Team</p>
<p style="margin: 0;">Thank you,<br> The Debt Pirate Team</p>
</td>
</tr>
<!-- end copy -->

View file

@ -1,38 +1,24 @@
use std::{
collections::HashSet,
path::{Path, PathBuf},
sync::mpsc::Sender,
};
use once_cell::sync::Lazy;
use pasetors::{keys::SymmetricKey, version4::V4};
use crate::services::UserConfirmationMessage;
use super::AppError;
static REQUIRED_ENV_VARS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
[
"HOSTNAME",
"PORT",
"TOKEN_KEY",
"DATABASE_URL",
"ASSETS_DIR",
"RUST_LOG",
]
.into_iter()
.collect()
});
#[derive(Clone)]
pub struct Environment {
hostname: String,
port: u32,
token_key: SymmetricKey<V4>,
assets_dir: PathBuf,
database_url: String,
email_sender: Sender<UserConfirmationMessage>,
assets_dir: PathBuf,
hostname: String,
port: u32,
rust_log: String,
send_verification_email: bool,
token_key: SymmetricKey<V4>,
}
impl Environment {
@ -41,14 +27,14 @@ impl Environment {
dotenvy::dotenv_iter()
.expect("Missing .env file")
.filter_map(|item| item.ok())
.filter(|(key, _)| REQUIRED_ENV_VARS.contains(key.as_str()))
.for_each(|(key, value)| match key.as_str() {
"ASSETS_DIR" => builder.with_assets_dir(value),
"DATABASE_URL" => builder.with_database_url(value),
"HOSTNAME" => builder.with_hostname(value),
"PORT" => builder.with_port(value),
"TOKEN_KEY" => builder.with_token_key(value),
"DATABASE_URL" => builder.with_database_url(value),
"ASSETS_DIR" => builder.with_assets_dir(value),
"RUST_LOG" => builder.with_rust_log(value),
"SEND_VERIFICATION_EMAIL" => builder.with_send_verification_email(value),
"TOKEN_KEY" => builder.with_token_key(value),
_ => {}
});
@ -87,41 +73,48 @@ impl Environment {
pub fn rust_log(&self) -> &str {
self.rust_log.as_str()
}
pub fn send_verification_email(&self) -> bool {
self.send_verification_email
}
}
impl From<EnvironmentObjectBuilder> for Environment {
fn from(builder: EnvironmentObjectBuilder) -> Self {
let EnvironmentObjectBuilder {
hostname,
port,
token_key,
assets_dir,
database_url,
email_sender,
assets_dir,
hostname,
port,
rust_log,
send_verification,
token_key,
} = builder;
Self {
hostname: hostname.unwrap(),
port: port.unwrap(),
token_key: token_key.unwrap(),
assets_dir: assets_dir.unwrap(),
database_url: database_url.unwrap(),
email_sender: email_sender.unwrap(),
assets_dir: assets_dir.unwrap(),
hostname: hostname.unwrap(),
port: port.unwrap(),
rust_log: rust_log.unwrap(),
send_verification_email: send_verification.unwrap_or(true),
token_key: token_key.unwrap(),
}
}
}
#[derive(Debug, Default)]
pub struct EnvironmentObjectBuilder {
pub hostname: Option<String>,
pub port: Option<u32>,
pub token_key: Option<SymmetricKey<V4>>,
pub assets_dir: Option<PathBuf>,
pub database_url: Option<String>,
pub email_sender: Option<Sender<UserConfirmationMessage>>,
pub assets_dir: Option<PathBuf>,
pub hostname: Option<String>,
pub port: Option<u32>,
pub rust_log: Option<String>,
pub send_verification: Option<bool>,
pub token_key: Option<SymmetricKey<V4>>,
}
impl EnvironmentObjectBuilder {
@ -199,4 +192,9 @@ impl EnvironmentObjectBuilder {
pub fn with_rust_log(&mut self, rust_log: String) {
self.rust_log = Some(rust_log);
}
pub fn with_send_verification_email(&mut self, send_verification_email: String) {
let send_verification_email = send_verification_email.to_lowercase() == "true";
self.send_verification = Some(send_verification_email);
}
}

View file

@ -116,10 +116,7 @@ enum ErrorKind {
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
match &self.kind {
ErrorKind::DuplicateRecord => (),
_ => (),
}
if let ErrorKind::DuplicateRecord = &self.kind { }
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}

View file

@ -1,8 +1,15 @@
mod user;
use axum::Router;
use std::time::Duration;
use axum::{
extract::{MatchedPath, Request},
response::Response,
Router,
};
use tokio::net::TcpListener;
use tracing::info;
use tower_http::trace::TraceLayer;
use tracing::{error, info, info_span, warn, Span};
use crate::{
db::DbPool,
@ -38,8 +45,31 @@ pub async fn start_app(pool: DbPool, env: Environment) -> Result<(), AppError> {
.await
.unwrap();
let logging_layer = TraceLayer::new_for_http()
.make_span_with(|request: &Request| {
let request_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str).unwrap();
info_span!("http_request", method = %request.method(), path = %request_path, status = tracing::field::Empty)
})
.on_response(|response: &Response, _duration: Duration, _span: &Span| {
let status = response.status();
_span.record("status", status.to_string());
match status.as_u16() {
w if (300..400).contains(&w) => warn!(""),
e if e >= 400 => error!(""),
_ => info!("")
}
});
let app_state = AppState::new(pool, env);
let app = Router::new().merge(user::requests(app_state.clone()));
let app = Router::new()
.merge(user::requests(app_state.clone()))
.layer(logging_layer);
info!("API started successfully.");
axum::serve(listener, app)

View file

@ -51,13 +51,15 @@ pub async fn user_registration_post_handler(
auth_token: auth_token.clone(),
};
let _ = app_state
.env()
.email_sender()
.send(new_user_confirmation_message)
.inspect_err(|err| {
eprintln!("Got the rollowing error while sending across the channel: {err}");
});
if app_state.env().send_verification_email() {
let _ = app_state
.env()
.email_sender()
.send(new_user_confirmation_message)
.inspect_err(|err| {
eprintln!("Got the rollowing error while sending across the channel: {err}");
});
}
let response = (
StatusCode::CREATED,

View file

@ -104,7 +104,7 @@ fn generate_token(
};
local::encrypt(
&key,
key,
&claims,
Some(&footer),
Some("TODO_ENV_NAME_HERE".as_bytes()),

View file

@ -1,18 +1,75 @@
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{fmt::time::UtcTime, EnvFilter};
#![allow(unused)]
use std::time::SystemTime;
use humantime::format_rfc3339_millis;
use tracing::{level_filters::LevelFilter, Event, Subscriber};
use tracing_subscriber::{
fmt::{
format::{DefaultFields, Writer},
FmtContext, FormatEvent, FormatFields, FormattedFields,
},
registry::{LookupSpan, Scope},
EnvFilter,
};
use crate::models::Environment;
pub fn initialize_logger(env: &Environment) {
let log_level = env.rust_log();
#[cfg(debug_assertions)]
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into())
.parse_lossy(log_level),
)
.event_format(tracing_subscriber::fmt::format().compact())
.with_target(false)
.with_file(true)
.with_line_number(true)
.init();
#[cfg(not(debug_assertions))]
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.parse_lossy(log_level),
)
.with_timer(UtcTime::rfc_3339())
.with_target(false)
.event_format(EventFormatter)
.fmt_fields(DefaultFields::new())
.init();
}
struct EventFormatter;
impl<S, N> FormatEvent<S, N> for EventFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let timestamp = format_rfc3339_millis(SystemTime::now());
let level = event.metadata().level();
write!(writer, "{timestamp} {level:<5} ")?;
ctx.field_format().format_fields(writer.by_ref(), event)?;
for span in ctx.event_scope().into_iter().flat_map(Scope::from_root) {
let exts = span.extensions();
if let Some(fields) = exts.get::<FormattedFields<N>>() {
if !fields.is_empty() {
write!(writer, "{}", fields.fields)?;
}
}
}
writeln!(writer)
}
}

View file

@ -1,4 +1,10 @@
use std::{fs::File, io::Read, path::Path, sync::mpsc::Receiver, thread};
use std::{
fs::File,
io::Read,
path::Path,
sync::{mpsc::Receiver, LazyLock},
thread,
};
use lettre::{
message::{header::ContentType, Mailbox},
@ -8,21 +14,20 @@ use lettre::{
},
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
};
use once_cell::sync::Lazy;
use tokio::runtime::Handle;
use super::UserConfirmationMessage;
static CREDENTIALS: Lazy<Credentials> = Lazy::new(|| {
static CREDENTIALS: LazyLock<Credentials> = LazyLock::new(|| {
Credentials::new(
"donotreply@mail.dziura.cloud".to_owned(),
"hunter2".to_owned(),
)
});
static NUM_CPUS: Lazy<u32> = Lazy::new(|| num_cpus::get() as u32);
static NUM_CPUS: LazyLock<u32> = LazyLock::new(|| num_cpus::get() as u32);
static FROM_MAILBOX: Lazy<Mailbox> = Lazy::new(|| {
static FROM_MAILBOX: LazyLock<Mailbox> = LazyLock::new(|| {
"No Not Reply Auth-Test <donotreply@mail.dziura.cloud>"
.parse()
.unwrap()