BLOG-90 Intergrate error tracking with Sentry #120

Merged
squid merged 4 commits from BLOG-90_sentry_intergated into main 2025-08-06 20:20:47 +08:00
16 changed files with 88 additions and 25 deletions
Showing only changes of commit beffee8f8e - Show all commits

10
backend/Cargo.lock generated
View File

@ -426,6 +426,7 @@ dependencies = [
"actix-web",
"anyhow",
"async-trait",
"common",
"log",
"openidconnect",
"sentry",
@ -619,6 +620,13 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "common"
version = "0.2.0"
dependencies = [
"sqlx",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -1743,6 +1751,7 @@ dependencies = [
"anyhow",
"async-trait",
"auth",
"common",
"futures",
"log",
"sentry",
@ -2399,6 +2408,7 @@ dependencies = [
"async-trait",
"auth",
"chrono",
"common",
"log",
"sentry",
"serde",

View File

@ -1,11 +1,21 @@
[workspace]
members = ["server", "feature/auth", "feature/image", "feature/post"]
members = [
"server",
"feature/auth",
"feature/common",
"feature/image",
"feature/post",
"feature/common",
]
resolver = "2"
[workspace.package]
version = "0.2.0"
edition = "2024"
[profile.release]
debug = true
[workspace.dependencies]
actix-multipart = "0.7.2"
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"
auth.path = "feature/auth"
common.path = "feature/common"
image.path = "feature/image"
post.path = "feature/post"

View File

@ -14,3 +14,5 @@ sentry.workspace = true
serde.workspace = true
sqlx.workspace = true
utoipa.workspace = true
common.workspace = true

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use common::framework::error::DatabaseError;
use sqlx::{Pool, Postgres};
use crate::{
@ -31,7 +32,7 @@ impl UserDbService for UserDbServiceImpl {
)
.fetch_optional(&self.db_pool)
.await
.map_err(|e| AuthError::Unexpected(e.into()))?;
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
match record {
Some(record) => Ok(record.into_mapper()),
@ -56,7 +57,7 @@ impl UserDbService for UserDbServiceImpl {
)
.fetch_optional(&self.db_pool)
.await
.map_err(|e| AuthError::Unexpected(e.into()))?;
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
match record {
Some(record) => Ok(record.into_mapper()),
@ -78,7 +79,7 @@ impl UserDbService for UserDbServiceImpl {
)
.fetch_one(&self.db_pool)
.await
.map_err(|e| AuthError::Unexpected(e.into()))?;
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
Ok(id)
}

View File

@ -80,7 +80,7 @@ impl AuthOidcService for AuthOidcServiceImpl {
let token_response = self
.oidc_client
.exchange_code(AuthorizationCode::new(code.to_string()))
.map_err(|e| AuthError::Unexpected(e.into()))?
.unwrap()
.request_async(&self.http_client)
.await
.map_err(|_| AuthError::InvalidAuthCode)?;

View File

@ -0,0 +1,7 @@
[package]
name = "common"
version.workspace = true
edition.workspace = true
[dependencies]
sqlx.workspace = true

View File

@ -0,0 +1 @@
pub mod error;

View 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)
}
}

View File

@ -0,0 +1 @@
pub mod framework;

View File

@ -16,3 +16,4 @@ sqlx.workspace = true
utoipa.workspace = true
auth.workspace = true
common.workspace = true

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use common::framework::error::DatabaseError;
use sqlx::{Pool, Postgres};
use crate::{
@ -34,7 +35,7 @@ impl ImageDbService for ImageDbServiceImpl {
match 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),
},
Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))),
Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())),
}
}
}

View File

@ -3,6 +3,8 @@ use std::{
io::Write,
};
use common::framework::error::IOError;
use crate::{
adapter::gateway::image_storage::ImageStorage, application::error::image_error::ImageError,
};
@ -22,10 +24,11 @@ impl ImageStorageImpl {
impl ImageStorage for ImageStorageImpl {
fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> {
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 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)
.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> {
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)
}
}

View File

@ -15,3 +15,4 @@ sqlx.workspace = true
utoipa.workspace = true
auth.workspace = true
common.workspace = true

View File

@ -1,4 +1,5 @@
use async_trait::async_trait;
use common::framework::error::DatabaseError;
use sqlx::{Pool, Postgres};
use crate::{
@ -31,7 +32,7 @@ impl LabelDbService for LabelDbServiceImpl {
)
.fetch_one(&self.db_pool)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
Ok(id)
}
@ -49,7 +50,7 @@ impl LabelDbService for LabelDbServiceImpl {
)
.execute(&self.db_pool)
.await
.map_err(|e| PostError::Unexpected(e.into()))?
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
.rows_affected();
if affected_rows == 0 {
@ -71,7 +72,7 @@ impl LabelDbService for LabelDbServiceImpl {
)
.fetch_optional(&self.db_pool)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
match record {
Some(record) => Ok(record.into_mapper()),
@ -91,7 +92,7 @@ impl LabelDbService for LabelDbServiceImpl {
)
.fetch_all(&self.db_pool)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
let mappers = records
.into_iter()

View File

@ -1,6 +1,7 @@
use std::collections::HashMap;
use async_trait::async_trait;
use common::framework::error::DatabaseError;
use sqlx::{Pool, Postgres};
use crate::{
@ -64,7 +65,7 @@ impl PostDbService for PostDbServiceImpl {
.build_query_as::<PostInfoWithLabelRecord>()
.fetch_all(&self.db_pool)
.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();
@ -136,7 +137,7 @@ impl PostDbService for PostDbServiceImpl {
.build_query_as::<PostWithLabelRecord>()
.fetch_all(&self.db_pool)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
if records.is_empty() {
return Err(PostError::NotFound);
@ -188,7 +189,7 @@ impl PostDbService for PostDbServiceImpl {
.db_pool
.begin()
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
let post_id = sqlx::query_scalar!(
r#"
@ -205,7 +206,7 @@ impl PostDbService for PostDbServiceImpl {
)
.fetch_one(&mut *tx)
.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() {
sqlx::query!(
@ -221,12 +222,12 @@ impl PostDbService for PostDbServiceImpl {
)
.execute(&mut *tx)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
}
tx.commit()
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
Ok(post_id)
}
@ -236,7 +237,7 @@ impl PostDbService for PostDbServiceImpl {
.db_pool
.begin()
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
let affected_rows = sqlx::query!(
r#"
@ -258,7 +259,7 @@ impl PostDbService for PostDbServiceImpl {
)
.execute(&mut *tx)
.await
.map_err(|e| PostError::Unexpected(e.into()))?
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?
.rows_affected();
if affected_rows == 0 {
@ -274,7 +275,7 @@ impl PostDbService for PostDbServiceImpl {
)
.execute(&mut *tx)
.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() {
sqlx::query!(
@ -290,12 +291,12 @@ impl PostDbService for PostDbServiceImpl {
)
.execute(&mut *tx)
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
}
tx.commit()
.await
.map_err(|e| PostError::Unexpected(e.into()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
Ok(())
}

View File

@ -15,6 +15,7 @@ impl SentryConfiguration {
traces_sample_rate: 1.0,
send_default_pii: true,
max_request_body_size: sentry::MaxRequestBodySize::Always,
attach_stacktrace: true,
..Default::default()
},
}