feat(telemetry): logging implementation
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
d13de9acbb
commit
9679435a40
|
@ -20,9 +20,10 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
|||
serde = { version = "1", features = ["derive"]}
|
||||
validator = { version = "0.16", features = ["derive"] }
|
||||
actix-web-validator = "5.0.1"
|
||||
log = "0.4"
|
||||
env_logger = "0.9.3"
|
||||
config = "0.13"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
|
||||
tracing-bunyan-formatter = "0.3"
|
||||
tracing-log = "0.1"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
|
||||
dotenv = "0.15.0"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# Zero_to_prod_in_rust
|
||||
|
||||
### TODO
|
||||
- configure db from env
|
||||
- get port from args with default
|
||||
- continue book...
|
||||
- build ci
|
||||
- release ci
|
|
@ -1,6 +1,4 @@
|
|||
//! src/configuration.rs
|
||||
use std::process;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{Connection, Executor, PgConnection, PgPool};
|
||||
|
@ -15,15 +13,11 @@ pub struct DatabaseConfig {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
pub fn get_db_config() -> DatabaseConfig {
|
||||
pub fn parse_db_config() -> DatabaseConfig {
|
||||
dotenv().ok();
|
||||
match envy::prefixed("DB_").from_env::<DatabaseConfig>() {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
eprintln!("Database config environment variables: {}", error);
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
envy::prefixed("DB_")
|
||||
.from_env::<DatabaseConfig>()
|
||||
.expect("Database config environment variables")
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
|
@ -46,22 +40,21 @@ impl DatabaseConfig {
|
|||
|
||||
pub async fn test_connection_pool(&mut self) -> PgPool {
|
||||
let test_db_name = Uuid::new_v4().to_string();
|
||||
let server_connection_string = self.server_connection_string();
|
||||
let create_db_query = format!(r#"CREATE DATABASE "{}";"#, test_db_name);
|
||||
|
||||
let mut connection = PgConnection::connect(&self.server_connection_string())
|
||||
PgConnection::connect(&server_connection_string)
|
||||
.await
|
||||
.expect("Failed to connect to Postgres");
|
||||
connection
|
||||
.execute(format!(r#"CREATE DATABASE "{}";"#, test_db_name).as_str())
|
||||
.expect("Failed to connect to Postgres")
|
||||
.execute(create_db_query.as_str())
|
||||
.await
|
||||
.expect("Failed to create database.");
|
||||
|
||||
let connection_pool = PgPool::connect(&format!(
|
||||
"{}/{}",
|
||||
self.server_connection_string(),
|
||||
test_db_name
|
||||
))
|
||||
.await
|
||||
.expect("Failed to connect to Postgres.");
|
||||
let test_db_connection_string = format!("{}/{}", server_connection_string, test_db_name);
|
||||
|
||||
let connection_pool = PgPool::connect(&test_db_connection_string)
|
||||
.await
|
||||
.expect("Failed to connect to Postgres.");
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
pub mod configuration;
|
||||
pub mod routes;
|
||||
pub mod startup;
|
||||
pub mod telemetry;
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -1,7 +1,8 @@
|
|||
use clap::{command, Parser};
|
||||
use std::net::TcpListener;
|
||||
use zero2prod::configuration::get_db_config;
|
||||
use zero2prod::configuration::parse_db_config;
|
||||
use zero2prod::startup::run;
|
||||
use zero2prod::telemetry::{get_subscriber, init_subscriber};
|
||||
|
||||
/// Zero2prod newsletter server
|
||||
#[derive(Parser, Debug)]
|
||||
|
@ -14,10 +15,13 @@ struct Args {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
let subscriber = get_subscriber("zero2prod".into(), "info".into());
|
||||
init_subscriber(subscriber);
|
||||
|
||||
let args = Args::parse();
|
||||
let connection_pool = get_db_config().connection_pool().await;
|
||||
let address = format!("127.0.0.1:{}", args.port);
|
||||
let listener = TcpListener::bind(address)?;
|
||||
let connection_pool = parse_db_config().connection_pool().await;
|
||||
|
||||
run(listener, connection_pool)?.await
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use actix_web::{get, HttpResponse, Responder};
|
||||
use log::info;
|
||||
|
||||
#[get("/health_check")]
|
||||
pub async fn health_check() -> impl Responder {
|
||||
info!("GET /health_check");
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder};
|
||||
use actix_web_validator::Form;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::{Error, PgPool};
|
||||
use tracing::Instrument;
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
|
@ -20,9 +20,28 @@ pub struct FormData {
|
|||
name: String,
|
||||
}
|
||||
|
||||
// TO EXTRACT WITH OTHERS GENERICS DATABASE OR ERRORS RELATED FUNCTIONS
|
||||
fn query_error_is_duplicate_key(error: Error) -> bool {
|
||||
match error {
|
||||
Error::Database(db_error) => match db_error.code() {
|
||||
Some(code) => code == "23505",
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/subscriptions")]
|
||||
pub async fn subscriptions(form: Form<FormData>, pool: web::Data<PgPool>) -> impl Responder {
|
||||
info!("POST /subscriptions {:?}", form.0);
|
||||
let request_id = Uuid::new_v4();
|
||||
let request_span = tracing::info_span!(
|
||||
"Adding a new subscriber",
|
||||
%request_id,
|
||||
subscriber_email = %form.email,
|
||||
subscriber_name = %form.name
|
||||
);
|
||||
let _request_span_guard = request_span.enter();
|
||||
let query_span = tracing::info_span!("Saving new subscriber details in the database");
|
||||
match sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO subscriptions (id, email, name, subscribed_at) VALUES ($1, $2, $3, $4)
|
||||
|
@ -33,11 +52,15 @@ pub async fn subscriptions(form: Form<FormData>, pool: web::Data<PgPool>) -> imp
|
|||
Utc::now()
|
||||
)
|
||||
.execute(pool.get_ref())
|
||||
.instrument(query_span)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
println!("Failed to execute query: {}", e);
|
||||
HttpResponse::InternalServerError()
|
||||
tracing::error!("Failed to execute query: {:?}", e);
|
||||
match query_error_is_duplicate_key(e) {
|
||||
true => HttpResponse::Conflict(),
|
||||
false => HttpResponse::InternalServerError(),
|
||||
}
|
||||
}
|
||||
_ => HttpResponse::Created(),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::routes::{health_check, subscriptions};
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::{dev::Server, web, App, HttpServer};
|
||||
use sqlx::PgPool;
|
||||
use std::net::TcpListener;
|
||||
|
@ -7,6 +8,7 @@ pub fn run(listener: TcpListener, db_pool: PgPool) -> Result<Server, std::io::Er
|
|||
let db_pool = web::Data::new(db_pool);
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.service(health_check)
|
||||
.service(subscriptions)
|
||||
.app_data(db_pool.clone())
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
//! src/telemetry.rs
|
||||
use tracing::subscriber::set_global_default;
|
||||
use tracing::Subscriber;
|
||||
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
|
||||
|
||||
pub fn get_subscriber(name: String, env_filter: String) -> impl Subscriber + Send + Sync {
|
||||
let env_filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter));
|
||||
let formatting_layer = BunyanFormattingLayer::new(name, std::io::stdout);
|
||||
Registry::default()
|
||||
.with(env_filter)
|
||||
.with(JsonStorageLayer)
|
||||
.with(formatting_layer)
|
||||
}
|
||||
|
||||
pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) {
|
||||
LogTracer::init().expect("Failed to set logger");
|
||||
set_global_default(subscriber).expect("Failed to set subscriber");
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use sqlx::PgPool;
|
||||
use std::net::TcpListener;
|
||||
use zero2prod::configuration::get_db_config;
|
||||
use zero2prod::configuration::parse_db_config;
|
||||
use zero2prod::startup::run;
|
||||
|
||||
pub struct TestApp {
|
||||
|
@ -13,7 +13,7 @@ async fn spawn_app() -> TestApp {
|
|||
let port = listener.local_addr().unwrap().port();
|
||||
let address = format!("http://127.0.0.1:{}", port);
|
||||
|
||||
let connection_pool = get_db_config().test_connection_pool().await;
|
||||
let connection_pool = parse_db_config().test_connection_pool().await;
|
||||
let server = run(listener, connection_pool.clone()).expect("Failed to bind address");
|
||||
let _ = tokio::spawn(server);
|
||||
|
||||
|
@ -84,7 +84,6 @@ async fn subscribe_returns_a_400_when_data_is_missing() {
|
|||
assert_eq!(
|
||||
400,
|
||||
response.status().as_u16(),
|
||||
// Additional customised error message on test failure
|
||||
"The API did not fail with 400 Bad Request when the payload was {}.",
|
||||
error_message
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue