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", "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",

View File

@ -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"

View File

@ -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

View File

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

View File

@ -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)?;

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 utoipa.workspace = true
auth.workspace = true auth.workspace = true
common.workspace = true

View File

@ -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())),
} }
} }
} }

View File

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

View File

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

View File

@ -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()

View File

@ -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(())
} }

View File

@ -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()
}, },
} }