From e1bd472a36ac243bcd2eb0faeae019d271dcd254 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Wed, 18 Mar 2026 12:08:41 -0400 Subject: [PATCH] feat: significantly improve logging for api requests --- src/pages/api/comparisons.rs | 8 +- src/pages/api/dates.rs | 64 +++++------ src/pages/api/exports.rs | 1 - src/pages/api/files.rs | 64 +++++++---- src/pages/api/providers.rs | 35 +++--- src/pages/api/scholarships.rs | 146 ++++++++++++++++---------- src/pages/api/students.rs | 89 ++++++++++------ src/pages/student/eligibility_page.rs | 35 +++--- 8 files changed, 264 insertions(+), 178 deletions(-) diff --git a/src/pages/api/comparisons.rs b/src/pages/api/comparisons.rs index 053debf..beed7a3 100644 --- a/src/pages/api/comparisons.rs +++ b/src/pages/api/comparisons.rs @@ -435,6 +435,7 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> { .await { let msg = err.message().unwrap_or("An unknown error occurred"); + leptos::logging::error!("{}", msg); return Err(ServerFnError::new(msg)); } } @@ -453,7 +454,6 @@ pub async fn get_comparison_info() -> Result, ServerFnError> match client.scan().table_name(COMPARISONS_TABLE).send().await { Ok(output) => { if let Some(items) = output.items { - log!("Found comparisons from API: {:?}", items); Ok(serde_dynamo::from_items(items)?) } else { Ok(vec![]) @@ -461,6 +461,7 @@ pub async fn get_comparison_info() -> Result, ServerFnError> } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); + leptos::logging::error!("{}", msg); Err(ServerFnError::new(msg)) } } @@ -493,11 +494,14 @@ pub async fn get_comparisons_categorized() Ok(categorized) } else { - Err(ServerFnError::new("Couldn't find any comparisons.")) + let msg = "Couldn't find any comparisons."; + leptos::logging::error!("{}", msg); + Err(ServerFnError::new(msg)) } } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); + leptos::logging::error!("{}", msg); Err(ServerFnError::new(msg)) } } diff --git a/src/pages/api/dates.rs b/src/pages/api/dates.rs index 3cbf387..e655464 100644 --- a/src/pages/api/dates.rs +++ b/src/pages/api/dates.rs @@ -1,61 +1,60 @@ -use std::collections::HashMap; -use leptos::logging::log; -use leptos::prelude::*; use crate::common::DateInfo; +use leptos::logging::{error, log}; +use leptos::prelude::*; +use std::collections::HashMap; #[cfg(feature = "ssr")] use super::DATES_TABLE; -#[cfg(feature = "ssr")] -use aws_sdk_dynamodb::{ - types::{WriteRequest, PutRequest}, - Client as DynamoClient -}; - #[cfg(feature = "ssr")] use crate::utils::server::create_dynamo_client; - +use aws_sdk_dynamodb::error::ProvideErrorMetadata; #[cfg(feature = "ssr")] -use std::sync::Arc; +use aws_sdk_dynamodb::{ + Client as DynamoClient, + types::{PutRequest, WriteRequest}, +}; #[server] pub async fn create_dates(dates: Vec) -> Result<(), ServerFnError> { // Serialize and put dates into the database. let client = create_dynamo_client().await; - + log!("Creating dates: {:?}", dates); - + let requests = dates .into_iter() - .map(|date| { - let item = serde_dynamo::to_item(&date) - .unwrap_or_default(); - - let put_request = PutRequest::builder() - .set_item(Some(item)) - .build() - .expect("Couldn't build request, item is not present"); - - WriteRequest::builder().put_request(put_request).build() + .filter_map(|date| { + let item = serde_dynamo::to_item(&date).unwrap_or_default(); + + let put_request = PutRequest::builder().set_item(Some(item)).build().ok()?; + + Some(WriteRequest::builder().put_request(put_request).build()) }) .collect::>(); - + let mut request_map = HashMap::new(); request_map.insert(DATES_TABLE.to_string(), requests); - + client .batch_write_item() .set_request_items(Some(request_map)) .send() .await .map(|_| ()) - .map_err(ServerFnError::from) + .map_err(|err| { + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); + ServerFnError::new(msg) + }) } #[server] pub async fn get_important_dates() -> Result, ServerFnError> { + log!("Getting important dates list"); + let client = create_dynamo_client().await; - + client .scan() .table_name(DATES_TABLE) @@ -65,14 +64,15 @@ pub async fn get_important_dates() -> Result, ServerFnError> { if let Some(items) = output.items { items .into_iter() - .filter_map(|item| { - serde_dynamo::from_item::<_, DateInfo>(item) - .ok() - }) + .filter_map(|item| serde_dynamo::from_item::<_, DateInfo>(item).ok()) .collect::>() } else { Vec::new() } }) - .map_err(ServerFnError::from) + .map_err(|err| { + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); + ServerFnError::new(msg) + }) } diff --git a/src/pages/api/exports.rs b/src/pages/api/exports.rs index 54a39f3..324ac59 100644 --- a/src/pages/api/exports.rs +++ b/src/pages/api/exports.rs @@ -231,7 +231,6 @@ pub async fn get_scholarship_csv() -> Result, ServerFnError> { writer.flush()?; let result = writer.into_inner()?; - debug_log!("{:?}", String::from_utf8(result.clone())); Ok(result) } diff --git a/src/pages/api/files.rs b/src/pages/api/files.rs index 9692b7f..aac22b9 100644 --- a/src/pages/api/files.rs +++ b/src/pages/api/files.rs @@ -1,4 +1,5 @@ -use leptos::logging::debug_log; +use leptos::leptos_dom::error; +use leptos::logging::debug_log; use leptos::prelude::*; use server_fn::codec::{MultipartData, MultipartFormData}; @@ -83,10 +84,12 @@ pub async fn upload_file(data: MultipartData) -> Result { .await .map(|_| ()) .map_err(|e| { - ServerFnError::new(format!( + let msg = format!( "Couldn't put file to S3: {}", - e.message().unwrap_or_default() - )) + e.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) })?; // Besides uploading the file to S3, we also want to store a FILE entry in Dynamo. @@ -113,11 +116,13 @@ pub async fn upload_file(data: MultipartData) -> Result { .item("file_key".to_string(), AttributeValue::S(key.clone())) .send() .await - .map_err(|e| { - ServerFnError::new(format!( + .map_err(|err| { + let msg = format!( "Couldn't put file to Dynamo, file rolled back: {}", - e.message().unwrap_or_default() - )) + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) }); // If Dynamo fails, we want to handle the error by rolling back the file. Then, we return a failure. @@ -128,11 +133,13 @@ pub async fn upload_file(data: MultipartData) -> Result { .key(&key) .send() .await - .map_err(|e| { - ServerFnError::new(format!( - "Couldn't roll back S3 operation: {}", - e.message().unwrap_or_default() - )) + .map_err(|err| { + let msg = format!( + "Failed to rollback Dynamo entry: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) })?; return Err(err); @@ -179,7 +186,14 @@ pub async fn delete_file( .key(key) .send() .await - .map_err(ServerFnError::from); + .map_err(|err| { + let msg = format!( + "Failed to put file into S3: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) + }); if let Err(err) = s3_result { // Add the key back to Dynamo and return an error. @@ -190,10 +204,12 @@ pub async fn delete_file( .send() .await .map_err(|err| { - ServerFnError::new(format!( - "Couldn't roll back Dynamo operation: {}", - err.message().unwrap_or_default() - )) + let msg = format!( + "Failed to rollback Dynamo entry: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) })?; return Err(err); @@ -242,10 +258,12 @@ pub async fn list_files( }) .collect::>() }) - .map_err(|e| { - ServerFnError::new(format!( - "Couldn't list files: {}", - e.message().unwrap_or_default() - )) + .map_err(|err| { + let msg = format!( + "Failed to get files: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) }) } diff --git a/src/pages/api/providers.rs b/src/pages/api/providers.rs index 988e932..071def2 100644 --- a/src/pages/api/providers.rs +++ b/src/pages/api/providers.rs @@ -1,8 +1,8 @@ -use leptos::prelude::*; -use leptos::logging::debug_log; -use std::collections::HashMap; +use crate::common::ValueType; +use leptos::logging::{debug_log, error}; +use leptos::prelude::*; use leptos::server_fn::codec::Json; -use crate::common::ValueType; +use std::collections::HashMap; #[cfg(feature = "ssr")] use crate::utils::server::{create_dynamo_client, into_attr_map}; @@ -12,8 +12,8 @@ static PROVIDER_CONTACT_TABLE: &str = "leptos-provider-contacts"; #[server] pub async fn get_provider_contact(id: String) -> Result, ServerFnError> { - use aws_sdk_dynamodb::types::AttributeValue; use aws_sdk_dynamodb::error::ProvideErrorMetadata; + use aws_sdk_dynamodb::types::AttributeValue; let client = create_dynamo_client().await; @@ -31,29 +31,39 @@ pub async fn get_provider_contact(id: String) -> Result>(); + (key.clone(), val_type) + }) + .collect::>(); Ok(map) } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred."); + error!("{}", msg); Err(ServerFnError::new(msg)) } } } #[server(input = Json)] -pub async fn put_provider_contact(id: String, contact_info: HashMap) -> Result<(), ServerFnError> { +pub async fn put_provider_contact( + id: String, + contact_info: HashMap, +) -> Result<(), ServerFnError> { use aws_sdk_dynamodb::error::ProvideErrorMetadata; let client = create_dynamo_client().await; let mut item = into_attr_map(contact_info); - item.insert("subject".to_owned(), aws_sdk_dynamodb::types::AttributeValue::S(id)); + item.insert( + "subject".to_owned(), + aws_sdk_dynamodb::types::AttributeValue::S(id), + ); match client .put_item() @@ -65,7 +75,8 @@ pub async fn put_provider_contact(id: String, contact_info: HashMap Ok(()), Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred."); + error!("{}", msg); Err(ServerFnError::new(msg)) } } -} \ No newline at end of file +} diff --git a/src/pages/api/scholarships.rs b/src/pages/api/scholarships.rs index a7764de..1bc7069 100644 --- a/src/pages/api/scholarships.rs +++ b/src/pages/api/scholarships.rs @@ -1,15 +1,11 @@ #[cfg(feature = "ssr")] mod imports { - pub use leptos::logging::debug_log; - pub use aws_sdk_dynamodb::{ - types::AttributeValue, - error::ProvideErrorMetadata, - }; - pub use leptos::leptos_dom::log; - pub use uuid::Uuid; - pub use crate::utils::server::{create_dynamo_client}; - pub use crate::common::ValueType; pub use super::super::SCHOLARSHIPS_TABLE; + pub use crate::common::ValueType; + pub use crate::utils::server::create_dynamo_client; + pub use aws_sdk_dynamodb::{error::ProvideErrorMetadata, types::AttributeValue}; + pub use leptos::logging::{debug_log, error, log}; + pub use uuid::Uuid; } use crate::common::ExpandableInfo; @@ -20,7 +16,7 @@ use leptos::server_fn::codec::Json; #[server(GetScholarshipInfo)] pub async fn get_scholarship_info(id: String) -> Result { use imports::*; - + let client = create_dynamo_client().await; // Perform the operation - we just want to return all data that's contained in this entry, @@ -39,11 +35,14 @@ pub async fn get_scholarship_info(id: String) -> Result { let msg = err.message().unwrap_or("An unknown error occurred"); + error!("{}", msg); Err(ServerFnError::new(msg)) } } @@ -51,12 +50,15 @@ pub async fn get_scholarship_info(id: String) -> Result Result<(), ServerFnError> { - use imports::*; use aws_sdk_dynamodb::error::ProvideErrorMetadata; + use imports::*; let client = create_dynamo_client().await; - log!("Creating or updating scholarship with ID {:?}", info.subject); + log!( + "Creating or updating scholarship with ID {:?}", + info.subject + ); match client .put_item() @@ -68,6 +70,7 @@ pub async fn create_scholarship_info(info: ExpandableInfo) -> Result<(), ServerF Ok(_) => Ok(()), Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); + error!("{}", msg); Err(ServerFnError::new(msg)) } } @@ -76,24 +79,20 @@ pub async fn create_scholarship_info(info: ExpandableInfo) -> Result<(), ServerF #[server(GetAllScholarshipInfo)] pub async fn get_all_scholarship_info() -> Result, ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; debug_log!("Getting all scholarship info"); - match client - .scan() - .table_name(SCHOLARSHIPS_TABLE) - .send() - .await - { + match client.scan().table_name(SCHOLARSHIPS_TABLE).send().await { Ok(output) => { if let Some(items) = output.items { Ok(serde_dynamo::from_items(items)?) } else { Ok(Vec::new()) } - }, + } Err(err) => { - let msg = err.message().unwrap_or("An unknown error occurred"); + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); Err(ServerFnError::new(msg)) } } @@ -101,17 +100,25 @@ pub async fn get_all_scholarship_info() -> Result, ServerFnE /// Gets all scholarships that are associated with the given scholarship provider's ID. #[server(GetProviderScholarships)] -pub async fn get_provider_scholarships(provider_id: String) -> Result, ServerFnError> { +pub async fn get_provider_scholarships( + provider_id: String, +) -> Result, ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; - - debug_log!("Getting provider scholarships for provider with ID {:?}", provider_id); - + + debug_log!( + "Getting provider scholarships for provider with ID {:?}", + provider_id + ); + match client .scan() .table_name(SCHOLARSHIPS_TABLE) - .expression_attribute_values(":id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?) + .expression_attribute_values( + ":id", + serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?, + ) .filter_expression("provider_id = :id") .send() .await @@ -124,7 +131,8 @@ pub async fn get_provider_scholarships(provider_id: String) -> Result { - let msg = err.message().unwrap_or("An unknown error occurred"); + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); Err(ServerFnError::new(msg)) } } @@ -134,14 +142,20 @@ pub async fn get_provider_scholarships(provider_id: String) -> Result Result { use imports::*; - + let client = create_dynamo_client().await; - - debug_log!("Creating scholarship for provider with ID {:?}", provider_id); - + + debug_log!( + "Creating scholarship for provider with ID {:?}", + provider_id + ); + let mut current_uuid = Uuid::new_v4().to_string(); let mut item = ExpandableInfo::new(current_uuid.clone()); - item.data.insert("provider_id".to_string(), ValueType::String(Some(provider_id))); + item.data.insert( + "provider_id".to_string(), + ValueType::String(Some(provider_id)), + ); loop { let ser_item = serde_dynamo::to_item(&item)?; match client @@ -165,10 +179,12 @@ pub async fn register_scholarship(provider_id: String) -> Result { - return Err(ServerFnError::new(err.message().unwrap_or("An unknown error occurred"))); + return Err(ServerFnError::new( + err.message().unwrap_or("Unknown error occurred"), + )); } } } @@ -178,41 +194,51 @@ pub async fn register_scholarship(provider_id: String) -> Result Result<(), ServerFnError> { +pub async fn delete_provider_scholarship( + provider_id: String, + scholarship_id: String, +) -> Result<(), ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; - - debug_log!("Deleting scholarship with ID {:?} for provider with ID {:?}", scholarship_id, provider_id); - + + debug_log!( + "Deleting scholarship with ID {:?} for provider with ID {:?}", + scholarship_id, + provider_id + ); + // When we delete a scholarship, we need to ensure that the provider's ID matches the scholarship, // otherwise everyone can delete anyone else's scholarships. match client .delete_item() .table_name(SCHOLARSHIPS_TABLE) .key("subject", AttributeValue::S(scholarship_id)) - .expression_attribute_values(":provider_id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?) + .expression_attribute_values( + ":provider_id", + serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?, + ) .condition_expression("provider_id = :provider_id") .send() .await { - Ok(_) => { - Ok(()) - } + Ok(_) => Ok(()), Err(err) => { - let msg = err.message().unwrap_or("An unknown error occurred"); + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); Err(ServerFnError::new(msg)) } } } #[server] -pub async fn get_all_scholarships() -> Result>, ServerFnError> { +pub async fn get_all_scholarships() +-> Result>, ServerFnError> { use imports::*; use std::collections::HashMap; - + let client = create_dynamo_client().await; - + client .scan() .table_name(SCHOLARSHIPS_TABLE) @@ -222,12 +248,20 @@ pub async fn get_all_scholarships() -> Result>() - }).collect::>() + + items + .iter() + .map(|item| { + // Each item should be simply converted from an AttributeValue to a ValueType. + item.iter() + .map(|(k, v)| (k.clone(), ValueType::from(v))) + .collect::>() + }) + .collect::>() + }) + .map_err(|err| { + let msg = err.message().unwrap_or("Unknown error occurred"); + error!("{}", msg); + ServerFnError::new(msg) }) - .map_err(ServerFnError::from) } diff --git a/src/pages/api/students.rs b/src/pages/api/students.rs index f5d7eb1..c18f657 100644 --- a/src/pages/api/students.rs +++ b/src/pages/api/students.rs @@ -3,36 +3,34 @@ use leptos::server_fn::codec::Json; #[cfg(feature = "ssr")] mod imports { - pub use crate::utils::server::*; - pub use aws_sdk_dynamodb::types::AttributeValue; pub use super::super::MAIN_TABLE_NAME; pub use crate::common::ValueType; + pub use crate::utils::server::*; + pub use aws_sdk_dynamodb::error::ProvideErrorMetadata; + pub use aws_sdk_dynamodb::types::AttributeValue; + pub use leptos::logging::{debug_log, error}; pub use std::collections::HashMap; - pub use leptos::logging::debug_log; } #[server(input = Json)] pub async fn put_student_data( subject: String, data_type: String, - data_map: std::collections::HashMap + data_map: std::collections::HashMap, ) -> Result<(), ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; - + let mut data_map_attr = into_attr_map(data_map); data_map_attr.insert( "HK".into(), - AttributeValue::S(format!("STUDENT#{}", subject)) + AttributeValue::S(format!("STUDENT#{}", subject)), ); - data_map_attr.insert( - "SK".into(), - AttributeValue::S(data_type.to_uppercase()) - ); - + data_map_attr.insert("SK".into(), AttributeValue::S(data_type.to_uppercase())); + debug_log!("Inserting this item: {:?}", data_map_attr); - + client .put_item() .table_name(MAIN_TABLE_NAME) @@ -40,7 +38,14 @@ pub async fn put_student_data( .send() .await .map(|_| ()) - .map_err(ServerFnError::from) + .map_err(|err| { + let msg = format!( + "Couldn't put item into Dynamo: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) + }) } #[server] @@ -49,9 +54,9 @@ pub async fn get_student_data( data_type: String, ) -> Result, ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; - + client .query() .table_name(MAIN_TABLE_NAME) @@ -65,37 +70,42 @@ pub async fn get_student_data( let Some(list) = output.items else { return HashMap::new(); }; - + let mut map = HashMap::new(); - - let _ = list - .into_iter() - .flatten() - .for_each(|(k, v)| { - map.insert(k, ValueType::from(&v)); - }); - + + let _ = list.into_iter().flatten().for_each(|(k, v)| { + map.insert(k, ValueType::from(&v)); + }); + map }) - .map_err(ServerFnError::from) + .map_err(|err| { + let msg = format!( + "Couldn't get item from Dynamo: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) + }) } /// Gets and flattens all of a student's information from the database, regardless of their defined /// sort key. -/// +/// /// Note: if sorted data from the `put_student_data` function contains the same fields, this /// function will have unexpected behavior. For example, if a `first_name` field appears in multiple /// locations in the database, all for this single student, they will overwrite each other in no /// specific order. #[server] pub async fn get_all_student_data( - subject: String + subject: String, ) -> Result, ServerFnError> { use imports::*; - + let client = create_dynamo_client().await; - - client.query() + + client + .query() .table_name(MAIN_TABLE_NAME) .key_condition_expression("HK = :hk") .expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{}", subject))) @@ -105,10 +115,19 @@ pub async fn get_all_student_data( let Some(items) = output.items else { return HashMap::new(); }; - - items.iter().flatten() - .map(|(|k, v)| (k.clone(), ValueType::from(v))) + + items + .iter() + .flatten() + .map(|(k, v)| (k.clone(), ValueType::from(v))) .collect::>() }) - .map_err(ServerFnError::from) + .map_err(|err| { + let msg = format!( + "Couldn't get data from Dynamo: {}", + err.message().unwrap_or("Unknown error occurred") + ); + error!("{}", msg); + ServerFnError::new(msg) + }) } diff --git a/src/pages/student/eligibility_page.rs b/src/pages/student/eligibility_page.rs index c49d896..dadc1ce 100644 --- a/src/pages/student/eligibility_page.rs +++ b/src/pages/student/eligibility_page.rs @@ -104,23 +104,24 @@ pub fn StudentEligibilityPage() -> impl IntoView { debug_log!( "Resolved requirements: {:?}", resolved_requirements.iter().flatten().map(|rel| rel.display_text.clone()).collect::>() ); - let valid = !scholarship_name.is_empty() && resolved_requirements - .iter() - .all(|list| { - list.iter() - .fold( - false, - |prev, requirement| { - let result = requirement - .compare(&student_info) - .unwrap_or_else(|err| { - debug_log!("Requirement failed: {err}"); - false - }); - if prev { prev } else { result } - }, - ) - }); + let valid = !scholarship_name.is_empty() + && resolved_requirements + .iter() + .all(|list| { + list.iter() + .fold( + false, + |prev, requirement| { + let result = requirement + .compare(&student_info) + .unwrap_or_else(|err| { + debug_log!("Requirement failed: {err}"); + false + }); + if prev { prev } else { result } + }, + ) + }); if valid { debug_log!("Scholarship is valid."); let scholarship_id = StoredValue::new(scholarship.subject.clone());