From 3f9e631f8105b3ae8f062d3b971925af66fdc074 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:13:03 -0400 Subject: [PATCH 1/7] Add requests dependency --- backend/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index 1c686fa9..7e75db99 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,6 +5,8 @@ billiard==4.2.1 boto3==1.39.14 botocore==1.39.14 celery==5.5.3 +certifi==2026.6.17 +charset-normalizer==3.4.9 click==8.2.1 click-didyoumean==0.3.1 click-plugins==1.1.1.2 @@ -16,6 +18,7 @@ django-environ==0.12.0 django-ses==4.4.0 djangorestframework==3.16.0 gunicorn==23.0.0 +idna==3.18 isort==8.0.1 jmespath==1.0.1 kombu==5.5.4 @@ -25,6 +28,7 @@ psycopg==3.2.7 psycopg2-binary==2.9.10 python-dateutil==2.9.0.post0 redis==6.2.0 +requests==2.34.2 s3transfer==0.13.1 six==1.17.0 sqlparse==0.5.3 From 5ceae7ed47bd914d2289aac01b53d3217f228560 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:14:02 -0400 Subject: [PATCH 2/7] Add Discord Webhook error logging --- backend/api/settings.py | 51 ++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/backend/api/settings.py b/backend/api/settings.py index 56105b46..45b5288d 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -6,6 +6,7 @@ from urllib.parse import urlparse import environ +import requests from celery.schedules import crontab from django.core.mail import send_mail from rest_framework.response import Response @@ -159,6 +160,10 @@ class ThrottleScopes: SEND_EMAILS = env.bool("SEND_EMAILS", default=False) CRITICAL_EMAIL_INTERVAL_SECONDS = 1800 # 30 minutes +# Discord Webhook URL +# Errors will only be sent if this is set. +DISCORD_WEBHOOK_URL = env("DISCORD_WEBHOOK_URL", default=None) + # Automated tasks CELERY_BEAT_SCHEDULE = { "daily_duties": { @@ -218,30 +223,40 @@ class ThrottleScopes: # Custom logger just to add some of my own custom logging functions # We love DRY!!! class PlancakeLogger(logging.Logger): - _last_email_time = 0 + def execute_webhook(self, msg, color): + if DISCORD_WEBHOOK_URL: + try: + requests.post( + DISCORD_WEBHOOK_URL, + json={ + "embeds": [ + { + "title": "Error", + "description": msg, + "color": color, + "timestamp": time.strftime( + "%Y-%m-%dT%H:%M:%SZ", time.gmtime() + ), + } + ] + }, + ) + except Exception as e: + self.warning( + "Failed to execute Discord Webhook for error logging: %s\nIf you don't have Webhooks set up, leave the environment variable empty.", + e, + ) def db_error(self, msg, *args, **kwargs): self.error("Database error: %s", msg, *args, **kwargs) + def error(self, msg, *args, **kwargs): + super().error(msg, *args, **kwargs) + self.execute_webhook(msg, 0xFF0000) + def critical(self, msg, *args, **kwargs): super().critical(msg, *args, **kwargs) - - # Send an email to admins - if SEND_EMAILS: - now = time.time() - if now - self._last_email_time > CRITICAL_EMAIL_INTERVAL_SECONDS: - stack_trace = "".join(traceback.format_stack()) - try: - send_mail( - subject=f"Plancake - Critical Error", - message=f"A critical error occurred in the application: {msg}\n\nStack Trace:\n{stack_trace}", - from_email=DEFAULT_FROM_EMAIL, - recipient_list=ADMIN_EMAILS, - fail_silently=False, - ) - self._last_email_time = now - except Exception as e: - self.error("Failed to send critical error email: %s", e) + self.execute_webhook(msg, 0xFF00FF) # Now any logger in the project will have access to this class From a281fae9e99c773f2f8dbb551ad5f4484964626c Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:19:31 -0400 Subject: [PATCH 3/7] Add Discord Webhook URL to .env.example --- backend/.env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/.env.example b/backend/.env.example index bc5e2948..48d63ea0 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -13,6 +13,10 @@ AWS_SES_REGION_NAME=region_name AWS_SES_REGION_ENDPOINT=region_endpoint DEFAULT_FROM_EMAIL=email +# Discord Webhook URL for error logging. Leave empty for development. +# To set up: https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks +DISCORD_WEBHOOK_URL=webhook-url + # The URL of the main site, used for generating links (no trailing slash) BASE_URL=https://example.com From 9ca5ef9af781483406a27dbf0b8b0b2eebc0bd1d Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:28:38 -0400 Subject: [PATCH 4/7] Remove admin emails --- backend/.env.example | 3 --- backend/api/settings.py | 1 - 2 files changed, 4 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 48d63ea0..735a29a1 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -30,9 +30,6 @@ COOKIE_DOMAIN=.example.com # The folder where logs will be stored LOG_DIR=filepath -# Comma-separated list of admin email addresses in case anything goes wrong -ADMIN_EMAILS=email,email - # Secret key for Django data signing, can stay a placeholder for development SECRET_KEY=django-secret-key diff --git a/backend/api/settings.py b/backend/api/settings.py index 45b5288d..f022c7a6 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -156,7 +156,6 @@ class ThrottleScopes: AWS_SES_REGION_NAME = env("AWS_SES_REGION_NAME") AWS_SES_REGION_ENDPOINT = env("AWS_SES_REGION_ENDPOINT") DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL") -ADMIN_EMAILS = env.list("ADMIN_EMAILS", default=[]) SEND_EMAILS = env.bool("SEND_EMAILS", default=False) CRITICAL_EMAIL_INTERVAL_SECONDS = 1800 # 30 minutes From d6c4d5c1fed8812862cb66c8f19acb7503f007fb Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:50:47 -0400 Subject: [PATCH 5/7] Add timeout to Discord Webhook call --- backend/api/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/api/settings.py b/backend/api/settings.py index f022c7a6..6863831a 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -239,6 +239,7 @@ def execute_webhook(self, msg, color): } ] }, + timeout=5, ) except Exception as e: self.warning( From 6af3cb82981954df8495543a49b3233ada480bd0 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 15 Jul 2026 14:52:41 -0400 Subject: [PATCH 6/7] Add string interpolation and conversion --- backend/api/settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/api/settings.py b/backend/api/settings.py index 6863831a..50d101ee 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -252,11 +252,13 @@ def db_error(self, msg, *args, **kwargs): def error(self, msg, *args, **kwargs): super().error(msg, *args, **kwargs) - self.execute_webhook(msg, 0xFF0000) + formatted_msg = str(msg) % args if args else str(msg) + self.execute_webhook(formatted_msg, 0xFF0000) def critical(self, msg, *args, **kwargs): super().critical(msg, *args, **kwargs) - self.execute_webhook(msg, 0xFF00FF) + formatted_msg = str(msg) % args if args else str(msg) + self.execute_webhook(formatted_msg, 0xFF00FF) # Now any logger in the project will have access to this class From 33632eaa1d56bfbffed64cdf82f30c7ecb2d40d6 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 17 Jul 2026 20:43:04 -0400 Subject: [PATCH 7/7] Remove unused settings constant --- backend/api/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/api/settings.py b/backend/api/settings.py index 50d101ee..2ad06d01 100644 --- a/backend/api/settings.py +++ b/backend/api/settings.py @@ -157,7 +157,6 @@ class ThrottleScopes: AWS_SES_REGION_ENDPOINT = env("AWS_SES_REGION_ENDPOINT") DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL") SEND_EMAILS = env.bool("SEND_EMAILS", default=False) -CRITICAL_EMAIL_INTERVAL_SECONDS = 1800 # 30 minutes # Discord Webhook URL # Errors will only be sent if this is set.