BLOG-90 Intergrate error tracking with Sentry #120
10
backend/Cargo.lock
generated
10
backend/Cargo.lock
generated
@ -426,6 +426,7 @@ dependencies = [
|
|||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"common",
|
||||||
"log",
|
"log",
|
||||||
"openidconnect",
|
"openidconnect",
|
||||||
"sentry",
|
"sentry",
|
||||||
@ -619,6 +620,13 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "common"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"sqlx",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -1743,6 +1751,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"auth",
|
"auth",
|
||||||
|
"common",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"sentry",
|
"sentry",
|
||||||
@ -2399,6 +2408,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"auth",
|
"auth",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"common",
|
||||||
"log",
|
"log",
|
||||||
"sentry",
|
"sentry",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["server", "feature/auth", "feature/image", "feature/post"]
|
members = [
|
||||||
|
"server",
|
||||||
|
"feature/auth",
|
||||||
|
"feature/common",
|
||||||
|
"feature/image",
|
||||||
|
"feature/post",
|
||||||
|
"feature/common",
|
||||||
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
actix-multipart = "0.7.2"
|
actix-multipart = "0.7.2"
|
||||||
actix-session = { version = "0.10.1", features = ["redis-session"] }
|
actix-session = { version = "0.10.1", features = ["redis-session"] }
|
||||||
@ -41,5 +51,6 @@ utoipa-redoc = { version = "6.0.0", features = ["actix-web"] }
|
|||||||
|
|
||||||
server.path = "server"
|
server.path = "server"
|
||||||
auth.path = "feature/auth"
|
auth.path = "feature/auth"
|
||||||
|
common.path = "feature/common"
|
||||||
image.path = "feature/image"
|
image.path = "feature/image"
|
||||||
post.path = "feature/post"
|
post.path = "feature/post"
|
||||||
|
@ -14,3 +14,5 @@ sentry.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
utoipa.workspace = true
|
utoipa.workspace = true
|
||||||
|
|
||||||
|
common.workspace = true
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use common::framework::error::DatabaseError;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -31,7 +32,7 @@ impl UserDbService for UserDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_optional(&self.db_pool)
|
.fetch_optional(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AuthError::Unexpected(e.into()))?;
|
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
match record {
|
match record {
|
||||||
Some(record) => Ok(record.into_mapper()),
|
Some(record) => Ok(record.into_mapper()),
|
||||||
@ -56,7 +57,7 @@ impl UserDbService for UserDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_optional(&self.db_pool)
|
.fetch_optional(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AuthError::Unexpected(e.into()))?;
|
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
match record {
|
match record {
|
||||||
Some(record) => Ok(record.into_mapper()),
|
Some(record) => Ok(record.into_mapper()),
|
||||||
@ -78,7 +79,7 @@ impl UserDbService for UserDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_one(&self.db_pool)
|
.fetch_one(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| AuthError::Unexpected(e.into()))?;
|
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ impl AuthOidcService for AuthOidcServiceImpl {
|
|||||||
let token_response = self
|
let token_response = self
|
||||||
.oidc_client
|
.oidc_client
|
||||||
.exchange_code(AuthorizationCode::new(code.to_string()))
|
.exchange_code(AuthorizationCode::new(code.to_string()))
|
||||||
.map_err(|e| AuthError::Unexpected(e.into()))?
|
.unwrap()
|
||||||
.request_async(&self.http_client)
|
.request_async(&self.http_client)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| AuthError::InvalidAuthCode)?;
|
.map_err(|_| AuthError::InvalidAuthCode)?;
|
||||||
|
7
backend/feature/common/Cargo.toml
Normal file
7
backend/feature/common/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "common"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sqlx.workspace = true
|
1
backend/feature/common/src/framework.rs
Normal file
1
backend/feature/common/src/framework.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod error;
|
21
backend/feature/common/src/framework/error.rs
Normal file
21
backend/feature/common/src/framework/error.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IOError(pub std::io::Error);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DatabaseError(pub sqlx::Error);
|
||||||
|
|
||||||
|
impl std::error::Error for IOError {}
|
||||||
|
impl Display for IOError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for DatabaseError {}
|
||||||
|
impl Display for DatabaseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
1
backend/feature/common/src/lib.rs
Normal file
1
backend/feature/common/src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod framework;
|
@ -16,3 +16,4 @@ sqlx.workspace = true
|
|||||||
utoipa.workspace = true
|
utoipa.workspace = true
|
||||||
|
|
||||||
auth.workspace = true
|
auth.workspace = true
|
||||||
|
common.workspace = true
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use common::framework::error::DatabaseError;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -34,7 +35,7 @@ impl ImageDbService for ImageDbServiceImpl {
|
|||||||
|
|
||||||
match id {
|
match id {
|
||||||
Ok(id) => Ok(id),
|
Ok(id) => Ok(id),
|
||||||
Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))),
|
Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ impl ImageDbService for ImageDbServiceImpl {
|
|||||||
}),
|
}),
|
||||||
None => Err(ImageError::NotFound),
|
None => Err(ImageError::NotFound),
|
||||||
},
|
},
|
||||||
Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))),
|
Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ use std::{
|
|||||||
io::Write,
|
io::Write,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use common::framework::error::IOError;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adapter::gateway::image_storage::ImageStorage, application::error::image_error::ImageError,
|
adapter::gateway::image_storage::ImageStorage, application::error::image_error::ImageError,
|
||||||
};
|
};
|
||||||
@ -22,10 +24,11 @@ impl ImageStorageImpl {
|
|||||||
impl ImageStorage for ImageStorageImpl {
|
impl ImageStorage for ImageStorageImpl {
|
||||||
fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> {
|
fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> {
|
||||||
let dir_path = format!("{}/images", self.sotrage_path);
|
let dir_path = format!("{}/images", self.sotrage_path);
|
||||||
fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(e.into()))?;
|
fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?;
|
||||||
|
|
||||||
let file_path = format!("{}/{}", dir_path, id);
|
let file_path = format!("{}/{}", dir_path, id);
|
||||||
let mut file = File::create(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?;
|
let mut file =
|
||||||
|
File::create(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?;
|
||||||
file.write_all(data)
|
file.write_all(data)
|
||||||
.map_err(|e| ImageError::Unexpected(e.into()))?;
|
.map_err(|e| ImageError::Unexpected(e.into()))?;
|
||||||
|
|
||||||
@ -34,7 +37,7 @@ impl ImageStorage for ImageStorageImpl {
|
|||||||
|
|
||||||
fn read_data(&self, id: i32) -> Result<Vec<u8>, ImageError> {
|
fn read_data(&self, id: i32) -> Result<Vec<u8>, ImageError> {
|
||||||
let file_path = format!("{}/images/{}", self.sotrage_path, id);
|
let file_path = format!("{}/images/{}", self.sotrage_path, id);
|
||||||
let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?;
|
let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,3 +15,4 @@ sqlx.workspace = true
|
|||||||
utoipa.workspace = true
|
utoipa.workspace = true
|
||||||
|
|
||||||
auth.workspace = true
|
auth.workspace = true
|
||||||
|
common.workspace = true
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use common::framework::error::DatabaseError;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -31,7 +32,7 @@ impl LabelDbService for LabelDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_one(&self.db_pool)
|
.fetch_one(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
@ -49,7 +50,7 @@ impl LabelDbService for LabelDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.execute(&self.db_pool)
|
.execute(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
|
||||||
.rows_affected();
|
.rows_affected();
|
||||||
|
|
||||||
if affected_rows == 0 {
|
if affected_rows == 0 {
|
||||||
@ -71,7 +72,7 @@ impl LabelDbService for LabelDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_optional(&self.db_pool)
|
.fetch_optional(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
match record {
|
match record {
|
||||||
Some(record) => Ok(record.into_mapper()),
|
Some(record) => Ok(record.into_mapper()),
|
||||||
@ -91,7 +92,7 @@ impl LabelDbService for LabelDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_all(&self.db_pool)
|
.fetch_all(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
let mappers = records
|
let mappers = records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use common::framework::error::DatabaseError;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -64,7 +65,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
.build_query_as::<PostInfoWithLabelRecord>()
|
.build_query_as::<PostInfoWithLabelRecord>()
|
||||||
.fetch_all(&self.db_pool)
|
.fetch_all(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
let mut post_info_mappers_map = HashMap::<i32, PostInfoMapper>::new();
|
let mut post_info_mappers_map = HashMap::<i32, PostInfoMapper>::new();
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
.build_query_as::<PostWithLabelRecord>()
|
.build_query_as::<PostWithLabelRecord>()
|
||||||
.fetch_all(&self.db_pool)
|
.fetch_all(&self.db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
if records.is_empty() {
|
if records.is_empty() {
|
||||||
return Err(PostError::NotFound);
|
return Err(PostError::NotFound);
|
||||||
@ -188,7 +189,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
.db_pool
|
.db_pool
|
||||||
.begin()
|
.begin()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
let post_id = sqlx::query_scalar!(
|
let post_id = sqlx::query_scalar!(
|
||||||
r#"
|
r#"
|
||||||
@ -205,7 +206,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.fetch_one(&mut *tx)
|
.fetch_one(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
for (order, &label_id) in label_ids.iter().enumerate() {
|
for (order, &label_id) in label_ids.iter().enumerate() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -221,12 +222,12 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.commit()
|
tx.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
Ok(post_id)
|
Ok(post_id)
|
||||||
}
|
}
|
||||||
@ -236,7 +237,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
.db_pool
|
.db_pool
|
||||||
.begin()
|
.begin()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
let affected_rows = sqlx::query!(
|
let affected_rows = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
@ -258,7 +259,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
|
||||||
.rows_affected();
|
.rows_affected();
|
||||||
|
|
||||||
if affected_rows == 0 {
|
if affected_rows == 0 {
|
||||||
@ -274,7 +275,7 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
for (order, &label_id) in label_ids.iter().enumerate() {
|
for (order, &label_id) in label_ids.iter().enumerate() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@ -290,12 +291,12 @@ impl PostDbService for PostDbServiceImpl {
|
|||||||
)
|
)
|
||||||
.execute(&mut *tx)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.commit()
|
tx.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| PostError::Unexpected(e.into()))?;
|
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ impl SentryConfiguration {
|
|||||||
traces_sample_rate: 1.0,
|
traces_sample_rate: 1.0,
|
||||||
send_default_pii: true,
|
send_default_pii: true,
|
||||||
max_request_body_size: sentry::MaxRequestBodySize::Always,
|
max_request_body_size: sentry::MaxRequestBodySize::Always,
|
||||||
|
attach_stacktrace: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user