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 django_forwardemail.services import EmailService
from .models import EmailVerification, User, UserDeletionRequest, UserProfile from .models import EmailVerification, User, UserDeletionRequest, UserProfile
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -130,7 +131,7 @@ class AccountService:
html=email_html, html=email_html,
) )
except Exception as e: 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 @staticmethod
def initiate_email_change( def initiate_email_change(
@@ -206,7 +207,7 @@ class AccountService:
html=email_html, html=email_html,
) )
except Exception as e: 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 @staticmethod
def verify_email_change(*, token: str) -> dict[str, Any]: 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 django_forwardemail.services import EmailService
from apps.accounts.models import NotificationPreference, User, UserNotification from apps.accounts.models import NotificationPreference, User, UserNotification
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -264,7 +265,7 @@ class NotificationService:
logger.info(f"Email notification sent to {user.email} for notification {notification.id}") logger.info(f"Email notification sent to {user.email} for notification {notification.id}")
except Exception as e: 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 @staticmethod
def get_user_notifications( def get_user_notifications(

View File

@@ -20,6 +20,8 @@ if TYPE_CHECKING:
else: else:
User = get_user_model() User = get_user_model()
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,7 +64,7 @@ class SocialProviderService:
return True, "Provider can be safely disconnected." return True, "Provider can be safely disconnected."
except Exception as e: 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." return False, "Unable to verify disconnection safety. Please try again."
@staticmethod @staticmethod
@@ -97,7 +99,7 @@ class SocialProviderService:
return connected_providers return connected_providers
except Exception as e: 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 [] return []
@staticmethod @staticmethod
@@ -140,7 +142,7 @@ class SocialProviderService:
return available_providers return available_providers
except Exception as e: except Exception as e:
logger.error(f"Error getting available providers: {e}") capture_and_log(e, 'Get available providers', source='service')
return [] return []
@staticmethod @staticmethod
@@ -177,7 +179,7 @@ class SocialProviderService:
return True, f"{provider.title()} account disconnected successfully." return True, f"{provider.title()} account disconnected successfully."
except Exception as e: 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." return False, f"Failed to disconnect {provider} account. Please try again."
@staticmethod @staticmethod
@@ -210,7 +212,7 @@ class SocialProviderService:
} }
except Exception as e: 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"} return {"error": "Unable to retrieve authentication status"}
@staticmethod @staticmethod
@@ -236,5 +238,5 @@ class SocialProviderService:
return True, f"Provider '{provider}' is valid and available." return True, f"Provider '{provider}' is valid and available."
except Exception as e: 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." 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.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
User = get_user_model() User = get_user_model()
@@ -292,5 +294,5 @@ class UserDeletionService:
logger.info(f"Deletion verification email sent to {user.email}") logger.info(f"Deletion verification email sent to {user.email}")
except Exception as e: 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 raise

View File

@@ -16,6 +16,7 @@ from django.utils import timezone
from apps.parks.models import Park from apps.parks.models import Park
from apps.rides.models import Ride from apps.rides.models import Ride
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -90,7 +91,7 @@ class Command(BaseCommand):
self.stdout.write(f" {item['name']} ({item['park']}) - opened: {item['date_opened']}") self.stdout.write(f" {item['name']} ({item['park']}) - opened: {item['date_opened']}")
except Exception as e: 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 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]]: 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.core.analytics import PageView
from apps.parks.models import Park from apps.parks.models import Park
from apps.rides.models import Ride from apps.rides.models import Ride
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -99,7 +100,7 @@ class Command(BaseCommand):
self.stdout.write(f" {item['name']} (score: {item.get('views_change', 'N/A')})") self.stdout.write(f" {item['name']} (score: {item.get('views_change', 'N/A')})")
except Exception as e: 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 raise CommandError(f"Failed to calculate trending content: {e}") from None
def _calculate_trending_parks( def _calculate_trending_parks(
@@ -199,7 +200,7 @@ class Command(BaseCommand):
return final_score return final_score
except Exception as e: 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 return 0.0
def _calculate_view_growth_score( def _calculate_view_growth_score(

View File

@@ -9,6 +9,8 @@ from django.conf import settings
from django.db import connection from django.db import connection
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from apps.core.utils import capture_and_log
performance_logger = logging.getLogger("performance") performance_logger = logging.getLogger("performance")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -130,12 +132,11 @@ class PerformanceMiddleware(MiddlewareMixin):
), ),
} }
performance_logger.error( capture_and_log(
f"Request exception: {request.method} {request.path} - " exception,
f"{duration:.3f}s, {total_queries} queries, {type(exception).__name__}: { f'Request exception: {request.method} {request.path} - {duration:.3f}s, {total_queries} queries',
exception source='middleware',
}", severity='high',
extra=performance_data,
) )
# Don't return anything - let the exception propagate normally # 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.core.cache import cache
from django.http import HttpRequest, HttpResponse, JsonResponse from django.http import HttpRequest, HttpResponse, JsonResponse
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -215,7 +217,9 @@ class SecurityEventLogger:
user = getattr(request, "user", None) user = getattr(request, "user", None)
username = user.username if user and user.is_authenticated else "anonymous" username = user.username if user and user.is_authenticated else "anonymous"
logger.error( capture_and_log(
f"Suspicious activity detected - Type: {activity_type}, " RuntimeError(f'Suspicious activity detected - Type: {activity_type}'),
f"IP: {client_ip}, User: {username}, Details: {details}" 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 # Merge request_context into metadata
merged_metadata = {**(metadata or {}), "request_context": request_context} 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 # Create and save error
app_error = ApplicationError.objects.create( app_error = ApplicationError.objects.create(**create_kwargs)
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 {},
)
logger.info( logger.info(
f"Captured error {app_error.short_error_id}: {error_type} from {source}" 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 django.core.files.uploadedfile import UploadedFile
from PIL import ExifTags, Image from PIL import ExifTags, Image
from apps.core.utils import capture_and_log
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -193,5 +195,5 @@ class MediaService:
"available_space": "unknown", "available_space": "unknown",
} }
except Exception as e: 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)} return {"error": str(e)}

View File

@@ -496,9 +496,10 @@ class TransitionCallbackRegistry:
failures.append((callback, None)) failures.append((callback, None))
overall_success = False overall_success = False
if not callback.continue_on_error: if not callback.continue_on_error:
logger.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 break
@@ -509,7 +510,8 @@ class TransitionCallbackRegistry:
if not callback.continue_on_error: if not callback.continue_on_error:
logger.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 break

View File

@@ -84,7 +84,8 @@ def with_callbacks(
if not pre_success and pre_failures: if not pre_success and pre_failures:
for callback, exc in pre_failures: for callback, exc in pre_failures:
if not callback.continue_on_error: 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: if exc:
raise exc raise exc
raise RuntimeError(f"Pre-transition callback {callback.name} failed") raise RuntimeError(f"Pre-transition callback {callback.name} failed")

View File

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