Compare commits

...

4 Commits

13 changed files with 96 additions and 74 deletions

View File

@@ -26,6 +26,7 @@ from django.utils.crypto import get_random_string
from django_forwardemail.services import EmailService
from .models import EmailVerification, User, UserDeletionRequest, UserProfile
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -130,7 +131,7 @@ class AccountService:
html=email_html,
)
except Exception as e:
logger.error(f"Failed to send password change confirmation email: {e}")
capture_and_log(e, 'Send password change confirmation email', source='service', severity='medium')
@staticmethod
def initiate_email_change(
@@ -206,7 +207,7 @@ class AccountService:
html=email_html,
)
except Exception as e:
logger.error(f"Failed to send email verification: {e}")
capture_and_log(e, 'Send email verification', source='service', severity='medium')
@staticmethod
def verify_email_change(*, token: str) -> dict[str, Any]:

View File

@@ -17,6 +17,7 @@ from django.utils import timezone
from django_forwardemail.services import EmailService
from apps.accounts.models import NotificationPreference, User, UserNotification
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -264,7 +265,7 @@ class NotificationService:
logger.info(f"Email notification sent to {user.email} for notification {notification.id}")
except Exception as e:
logger.error(f"Failed to send email notification {notification.id}: {str(e)}")
capture_and_log(e, f'Send email notification {notification.id}', source='service')
@staticmethod
def get_user_notifications(

View File

@@ -20,6 +20,8 @@ if TYPE_CHECKING:
else:
User = get_user_model()
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -62,7 +64,7 @@ class SocialProviderService:
return True, "Provider can be safely disconnected."
except Exception as e:
logger.error(f"Error checking disconnect permission for user {user.id}, provider {provider}: {e}")
capture_and_log(e, f'Check disconnect permission for user {user.id}, provider {provider}', source='service')
return False, "Unable to verify disconnection safety. Please try again."
@staticmethod
@@ -97,7 +99,7 @@ class SocialProviderService:
return connected_providers
except Exception as e:
logger.error(f"Error getting connected providers for user {user.id}: {e}")
capture_and_log(e, f'Get connected providers for user {user.id}', source='service')
return []
@staticmethod
@@ -140,7 +142,7 @@ class SocialProviderService:
return available_providers
except Exception as e:
logger.error(f"Error getting available providers: {e}")
capture_and_log(e, 'Get available providers', source='service')
return []
@staticmethod
@@ -177,7 +179,7 @@ class SocialProviderService:
return True, f"{provider.title()} account disconnected successfully."
except Exception as e:
logger.error(f"Error disconnecting {provider} for user {user.id}: {e}")
capture_and_log(e, f'Disconnect {provider} for user {user.id}', source='service')
return False, f"Failed to disconnect {provider} account. Please try again."
@staticmethod
@@ -210,7 +212,7 @@ class SocialProviderService:
}
except Exception as e:
logger.error(f"Error getting auth status for user {user.id}: {e}")
capture_and_log(e, f'Get auth status for user {user.id}', source='service')
return {"error": "Unable to retrieve authentication status"}
@staticmethod
@@ -236,5 +238,5 @@ class SocialProviderService:
return True, f"Provider '{provider}' is valid and available."
except Exception as e:
logger.error(f"Error validating provider {provider}: {e}")
capture_and_log(e, f'Validate provider {provider}', source='service')
return False, "Unable to validate provider."

View File

@@ -18,6 +18,8 @@ from django.db import transaction
from django.template.loader import render_to_string
from django.utils import timezone
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
User = get_user_model()
@@ -292,5 +294,5 @@ class UserDeletionService:
logger.info(f"Deletion verification email sent to {user.email}")
except Exception as e:
logger.error(f"Failed to send deletion verification email to {user.email}: {str(e)}")
capture_and_log(e, f'Send deletion verification email to {user.email}', source='service')
raise

View File

@@ -16,6 +16,7 @@ from django.utils import timezone
from apps.parks.models import Park
from apps.rides.models import Ride
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -90,7 +91,7 @@ class Command(BaseCommand):
self.stdout.write(f" {item['name']} ({item['park']}) - opened: {item['date_opened']}")
except Exception as e:
logger.error(f"Error calculating new content: {e}", exc_info=True)
capture_and_log(e, 'Calculate new content', source='management', severity='high')
raise CommandError(f"Failed to calculate new content: {e}") from None
def _get_new_parks(self, cutoff_date: datetime, limit: int) -> list[dict[str, Any]]:

View File

@@ -16,6 +16,7 @@ from django.utils import timezone
from apps.core.analytics import PageView
from apps.parks.models import Park
from apps.rides.models import Ride
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -99,7 +100,7 @@ class Command(BaseCommand):
self.stdout.write(f" {item['name']} (score: {item.get('views_change', 'N/A')})")
except Exception as e:
logger.error(f"Error calculating trending content: {e}", exc_info=True)
capture_and_log(e, 'Calculate trending content', source='management', severity='high')
raise CommandError(f"Failed to calculate trending content: {e}") from None
def _calculate_trending_parks(
@@ -199,7 +200,7 @@ class Command(BaseCommand):
return final_score
except Exception as e:
logger.error(f"Error calculating score for {content_type} {content_obj.id}: {e}")
capture_and_log(e, f'Calculate score for {content_type} {content_obj.id}', source='management', severity='medium')
return 0.0
def _calculate_view_growth_score(

View File

@@ -9,6 +9,8 @@ from django.conf import settings
from django.db import connection
from django.utils.deprecation import MiddlewareMixin
from apps.core.utils import capture_and_log
performance_logger = logging.getLogger("performance")
logger = logging.getLogger(__name__)
@@ -130,12 +132,11 @@ class PerformanceMiddleware(MiddlewareMixin):
),
}
performance_logger.error(
f"Request exception: {request.method} {request.path} - "
f"{duration:.3f}s, {total_queries} queries, {type(exception).__name__}: {
exception
}",
extra=performance_data,
capture_and_log(
exception,
f'Request exception: {request.method} {request.path} - {duration:.3f}s, {total_queries} queries',
source='middleware',
severity='high',
)
# Don't return anything - let the exception propagate normally

View File

@@ -19,6 +19,8 @@ from collections.abc import Callable
from django.core.cache import cache
from django.http import HttpRequest, HttpResponse, JsonResponse
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -215,7 +217,9 @@ class SecurityEventLogger:
user = getattr(request, "user", None)
username = user.username if user and user.is_authenticated else "anonymous"
logger.error(
f"Suspicious activity detected - Type: {activity_type}, "
f"IP: {client_ip}, User: {username}, Details: {details}"
capture_and_log(
RuntimeError(f'Suspicious activity detected - Type: {activity_type}'),
f'Suspicious activity - IP: {client_ip}, User: {username}, Details: {details}',
source='security',
severity='high',
)

View File

@@ -130,23 +130,28 @@ class ErrorService:
# Merge request_context into metadata
merged_metadata = {**(metadata or {}), "request_context": request_context}
# Build create kwargs, only including error_id if provided
create_kwargs = {
"error_type": error_type,
"error_message": error_message[:5000], # Limit message length
"error_stack": error_stack[:10000], # Limit stack length
"error_code": error_code,
"severity": severity,
"source": source,
"endpoint": endpoint,
"http_method": http_method,
"user_agent": user_agent[:1000],
"user": user,
"ip_address_hash": ip_address_hash,
"metadata": merged_metadata,
"environment": environment or {},
}
# Only include error_id if explicitly provided, else let model default
if error_id is not None:
create_kwargs["error_id"] = error_id
# Create and save error
app_error = ApplicationError.objects.create(
error_id=error_id or None, # Let model generate if not provided
error_type=error_type,
error_message=error_message[:5000], # Limit message length
error_stack=error_stack[:10000], # Limit stack length
error_code=error_code,
severity=severity,
source=source,
endpoint=endpoint,
http_method=http_method,
user_agent=user_agent[:1000],
user=user,
ip_address_hash=ip_address_hash,
metadata=merged_metadata,
environment=environment or {},
)
app_error = ApplicationError.objects.create(**create_kwargs)
logger.info(
f"Captured error {app_error.short_error_id}: {error_type} from {source}"

View File

@@ -14,6 +14,8 @@ from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from PIL import ExifTags, Image
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__)
@@ -193,5 +195,5 @@ class MediaService:
"available_space": "unknown",
}
except Exception as e:
logger.error(f"Failed to get storage stats: {str(e)}")
capture_and_log(e, 'Get storage stats', source='service', severity='low')
return {"error": str(e)}

View File

@@ -496,9 +496,10 @@ class TransitionCallbackRegistry:
failures.append((callback, None))
overall_success = False
if not callback.continue_on_error:
if not callback.continue_on_error:
logger.error(
f"Aborting callback chain - {callback.name} failed " f"and continue_on_error=False"
f"Aborting callback chain - {callback.name} failed "
f"and continue_on_error=False"
)
break
@@ -509,7 +510,8 @@ class TransitionCallbackRegistry:
if not callback.continue_on_error:
logger.error(
f"Aborting callback chain - {callback.name} raised exception " f"and continue_on_error=False"
f"Aborting callback chain - {callback.name} raised exception "
f"and continue_on_error=False"
)
break

View File

@@ -84,7 +84,8 @@ def with_callbacks(
if not pre_success and pre_failures:
for callback, exc in pre_failures:
if not callback.continue_on_error:
logger.error(f"Pre-transition callback {callback.name} failed, " f"aborting transition")
logger.error(f"Pre-transition callback {callback.name} failed, "
f"aborting transition")
if exc:
raise exc
raise RuntimeError(f"Pre-transition callback {callback.name} failed")

View File

@@ -12,6 +12,7 @@ from apps.rides.models import (
RollerCoasterStats,
)
from apps.rides.models.company import Company as RideCompany
from apps.core.utils import capture_and_log
class Command(BaseCommand):
@@ -111,9 +112,11 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS("Successfully cleaned up existing sample data!"))
except Exception as e:
self.logger.error(
f"Error during data cleanup: {str(e)}",
exc_info=True,
capture_and_log(
e,
'Data cleanup error',
source='management',
severity='high',
)
self.stdout.write(self.style.ERROR(f"Failed to clean up existing data: {str(e)}"))
raise
@@ -152,7 +155,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS("Successfully created comprehensive sample data!"))
except Exception as e:
self.logger.error(f"Error during sample data creation: {str(e)}", exc_info=True)
capture_and_log(e, 'Sample data creation error', source='management', severity='high')
self.stdout.write(self.style.ERROR(f"Failed to create sample data: {str(e)}"))
raise
@@ -333,7 +336,7 @@ class Command(BaseCommand):
}"
)
except Exception as e:
self.logger.error(f"Error creating park company {data['name']}: {str(e)}")
capture_and_log(e, f"Create park company {data['name']}", source='management', severity='medium')
raise
# Create companies in rides app (for manufacturers and designers)
@@ -356,11 +359,11 @@ class Command(BaseCommand):
}"
)
except Exception as e:
self.logger.error(f"Error creating ride company {data['name']}: {str(e)}")
capture_and_log(e, f"Create ride company {data['name']}", source='management', severity='medium')
raise
except Exception as e:
self.logger.error(f"Error in create_companies: {str(e)}")
capture_and_log(e, 'Create companies', source='management', severity='high')
raise
def create_parks(self):
@@ -518,19 +521,18 @@ class Command(BaseCommand):
park_location.set_coordinates(loc_data["latitude"], loc_data["longitude"])
park_location.save()
except Exception as e:
self.logger.error(
f"Error creating location for park {
park_data['name']
}: {str(e)}"
capture_and_log(
e, f"Create location for park {park_data['name']}",
source='management', severity='medium'
)
raise
except Exception as e:
self.logger.error(f"Error creating park {park_data['name']}: {str(e)}")
capture_and_log(e, f"Create park {park_data['name']}", source='management', severity='medium')
raise
except Exception as e:
self.logger.error(f"Error in create_parks: {str(e)}")
capture_and_log(e, 'Create parks', source='management', severity='high')
raise
def create_rides(self):
@@ -597,7 +599,7 @@ class Command(BaseCommand):
}"
)
except Exception as e:
self.logger.error(f"Error creating ride model {model_data['name']}: {str(e)}")
capture_and_log(e, f"Create ride model {model_data['name']}", source='management', severity='medium')
raise
# Create rides
@@ -822,19 +824,18 @@ class Command(BaseCommand):
stats_data = ride_data["coaster_stats"]
RollerCoasterStats.objects.create(ride=ride, **stats_data)
except Exception as e:
self.logger.error(
f"Error creating stats for ride {ride_data['name']}: {
str(e)
}"
capture_and_log(
e, f"Create stats for ride {ride_data['name']}",
source='management', severity='medium'
)
raise
except Exception as e:
self.logger.error(f"Error creating ride {ride_data['name']}: {str(e)}")
capture_and_log(e, f"Create ride {ride_data['name']}", source='management', severity='medium')
raise
except Exception as e:
self.logger.error(f"Error in create_rides: {str(e)}")
capture_and_log(e, 'Create rides', source='management', severity='high')
raise
def create_park_areas(self):
@@ -967,11 +968,11 @@ class Command(BaseCommand):
} in {park.name}"
)
except Exception as e:
self.logger.error(f"Error creating areas for park {area_group['park']}: {str(e)}")
capture_and_log(e, f"Create areas for park {area_group['park']}", source='management', severity='medium')
raise
except Exception as e:
self.logger.error(f"Error in create_park_areas: {str(e)}")
capture_and_log(e, 'Create park areas', source='management', severity='high')
raise
def create_reviews(self):
@@ -1043,10 +1044,9 @@ class Command(BaseCommand):
}"
)
except Exception as e:
self.logger.error(
f"Error creating park review for {review_data['park']}: {
str(e)
}"
capture_and_log(
e, f"Create park review for {review_data['park']}",
source='management', severity='medium'
)
raise
@@ -1102,15 +1102,14 @@ class Command(BaseCommand):
}"
)
except Exception as e:
self.logger.error(
f"Error creating ride review for {review_data['ride']}: {
str(e)
}"
capture_and_log(
e, f"Create ride review for {review_data['ride']}",
source='management', severity='medium'
)
raise
self.stdout.write(self.style.SUCCESS("Sample data creation completed!"))
except Exception as e:
self.logger.error(f"Error in create_reviews: {str(e)}")
capture_and_log(e, 'Create reviews', source='management', severity='high')
raise