Goal
Add practical monitoring and observability for the course management platform without self-hosting Grafana, prioritizing free or low-cost hosted options.
We want to know what is happening around releases and catch application health problems early:
- unhandled errors and exceptions
- release/deploy health and regressions
- important student/staff events
- page/API availability
- submission/scoring/email pipeline health
Current app inventory
The app is a Django project deployed to AWS ECS from GitHub Actions:
- GitHub Actions builds Docker images, pushes to ECR, and updates ECS task definitions in
.github/workflows/deploy-dev.yaml, .github/workflows/deploy-prod.yaml, and deploy/deploy_*.sh.
deploy/update_task_def.py writes the Docker tag into the ECS VERSION environment variable.
/api/health/ returns {"status": "ok", "version": settings.VERSION} from api/views/health.py.
- Django already emits JSON logs to stdout in non-local environments through
course_management/settings.py.
- No Sentry, OpenTelemetry, AWS X-Ray, Datadog, New Relic, Honeybadger, or hosted Grafana dependency was found in
pyproject.toml.
- Datamailer has useful domain-level health data already stored in
data.models: DatamailerOutboxEvent, DatamailerOutboxDispatchRun, DatamailerContactEvent, and DatamailerSendAudit.
- There are management commands that already summarize Datamailer health:
datamailer_outbox_status, datamailer_send_status, and datamailer_callback_status.
Important events to monitor
Authentication and identity
- Login page views:
accounts/views/login.py.
- Successful/failed OAuth login and account linking:
accounts/auth.py via ConsolidatingSocialAccountAdapter.pre_social_login.
- New users created from social login:
ConsolidatingSocialAccountAdapter._create_user_for_email.
- Multiple existing users found for the same social email: currently logged as warning in
accounts/auth.py.
- Token-auth API failures:
accounts/auth.py::token_required.
- Staff impersonation start/stop:
django-loginas under /admin/ and accounts/views/impersonation.py::stop_impersonating.
Privacy note: current OAuth logging includes full provider response JSON. Before expanding log retention, review this for PII/token exposure and either remove it or redact it.
Page/API traffic and availability
- Public course list/course/detail pages from
courses/urls.py.
- Student pages: course dashboard, homework, project, leaderboard, enrollment.
- Staff admin pages under
cadmin/urls.py.
- API requests under
api/urls.py, especially staff-token mutation endpoints.
/api/health/ uptime and version checks.
- 4xx/5xx counts, request latency, and response sizes from ALB/ECS/gunicorn access logs if available.
Student lifecycle events
- Registration campaign submitted:
courses/views/registration.py::registration_campaign_view.
- Enrollment created/updated/toggles changed:
courses/views/course_enrollment.py.
- Account preference/toggle/timezone/email-preference changes in
accounts/views/*.
- Leaderboard complaint submitted/resolved:
courses.models.course.LeaderboardComplaint and cadmin/views/enrollment.py.
Homework events
- Homework viewed and submitted:
courses/views/homework.py and courses/views/homework_submission.py.
- Submission update versus first submission:
homework_submission_for_user.
- Homework validation errors:
courses/views/homework_post_preview.py.
- Homework scoring started/completed/failed:
courses/scoring.py and API/staff routes.
- Staff edits to submissions and correct answers:
cadmin/views/homework.py, cadmin/views/homework_submission_edit.py.
- Score notification sends: Datamailer outbox/send audit events.
Suggested event fields: event_name, course_slug, homework_slug, homework_id, submission_id, user_id, enrollment_id, is_update, question_count, duration_ms, status, error_code.
Do not log answer text or free-form comments.
Project and peer review events
- Project viewed/submitted/deleted:
courses/views/project.py and courses/views/project_submission_edit.py.
- Project validation errors.
- Peer reviews assigned:
courses/project_assignment.py.
- Peer review submitted:
courses/views/project_eval_submit.py and courses/views/project_eval_submit_save.py.
- Optional peer review add/delete:
courses/views/project_eval_actions.py.
- Project votes:
courses/views/project_eval_submit.py::project_eval_vote_response.
- Project scoring started/completed/failed:
courses/project_scoring.py and API/staff routes.
- Staff submission edits:
cadmin/views/project_submission_edit.py.
Suggested event fields: event_name, course_slug, project_slug, project_id, submission_id, review_id, reviewer_enrollment_id, user_id, assigned_count, submitted_count, passed_count, duration_ms, status, error_code.
Do not log repository contents, notes to peer, or free-form comments. GitHub repo URLs and commit IDs are probably acceptable as structured fields, but avoid over-indexing them in metric dimensions.
Staff/API mutations
- Course/homework/project create/update/delete through
api/views/*.
- Homework question create/update/delete through
api/views/questions.py.
- Registration campaign create/update through
api/views/registration_campaigns.py.
- Certificate/graduates imports and bulk certificate update endpoints.
- Staff-triggered homework/project scoring and review assignment.
- Unauthorized/invalid token attempts.
Suggested event fields: event_name, actor_user_id, course_slug, object_type, object_id, method, path_name, status, error_code.
Datamailer and background-like work
- Outbox enqueue/dispatch/ack/retry/fail/dead from
course_management/datamailer_outbox*.py.
- Outbox run success/failure from
DatamailerOutboxDispatchRun.
- Send audit success/failure from
DatamailerSendAudit.
- Webhook callback accepted/duplicate/rejected from
api/views/webhooks.py and validation helpers.
- Registration confirmation, homework/project confirmation, score notifications, certificates, peer-review notifications.
- Scheduled
process_datamailer_outbox execution should have heartbeat/check-in monitoring.
Recommended monitoring architecture
Phase 1: cheapest useful baseline
Use CloudWatch for infra/log retention plus Sentry or Honeybadger for application errors and release visibility.
- Keep structured JSON stdout logs and ship ECS task logs to CloudWatch Logs.
- Add log fields consistently:
event, release, environment, request_id, user_id, course_slug, object_type, object_id, status.
- Add Sentry SDK or Honeybadger for Django exceptions.
- Set
release=settings.VERSION and environment=dev|prod, matching the ECS VERSION value already set by deployment.
- Add GitHub Actions release/deploy notification to the error tracker after deploy.
- Add an external uptime monitor for
/api/health/ and one or two key public pages.
- Add CloudWatch alarms for ECS service health, 5xx rate, task restarts, memory/CPU, and log error patterns.
- Add a daily/hourly scheduled health script that runs existing Datamailer status commands and emits one compact JSON log line or CloudWatch metric.
This gives immediate error tracking and release correlation without running Grafana.
Phase 2: app-level events
Add a tiny internal event helper, for example monitoring/events.py, that logs a structured event and optionally increments metrics later.
Initial events:
auth.login_success, auth.login_failed, auth.user_created, auth.social_account_linked
registration.submitted
enrollment.created, enrollment.updated
homework.submitted, homework.validation_failed, homework.scored
project.submitted, project.deleted, project.peer_reviews_assigned, project.review_submitted, project.scored
api.mutation, api.auth_failed
datamailer.outbox_failed, datamailer.outbox_backlog, datamailer.send_failed, datamailer.callback_received
Important: do not emit high-cardinality dimensions as CloudWatch custom metrics. Keep detailed IDs in logs/error context. Use metrics only for low-cardinality counters such as event name, status, environment, and object type.
Phase 3: dashboards and alerts
Build a small number of hosted dashboards/alerts:
- Release overview: deploy version, error count since deploy, 5xx count, health check status.
- Student activity: registrations, enrollments, homework submissions, project submissions, peer reviews.
- Course operations: scoring runs, review assignment, leaderboard updates.
- Datamailer: outbox backlog, failed events, last successful run, send audit failures, callback freshness.
- API/admin: mutation counts, unauthorized requests, staff-triggered destructive actions.
Option matrix
| Option |
Covers |
Likely low-volume cost |
Pros |
Cons |
| AWS CloudWatch Logs + alarms |
ECS logs, error log patterns, 5xx metrics, CPU/memory, dashboards |
Often near free if logs stay under free/low GB volumes; official pricing includes paid ingestion/storage after free usage |
Already AWS-native; no new vendor; easy ECS integration |
Weak error grouping; custom metrics can get expensive with high-cardinality labels |
| AWS X-Ray / CloudWatch Application Signals |
Traces, latency, service map |
Cheap with sampling at low volume; AWS example shows 148,800 sampled traces costing $0.24 recorded after free tier |
Native AWS tracing; good for slow requests and DB/API latency |
More setup; trace volume needs sampling discipline |
| Sentry hosted |
Django exceptions, release health, deploy correlation, traces, uptime/cron monitors, alerting |
Free Developer plan for one user and small volume; Team starts at $26/mo annually |
Best fit for “what broke after release?”; Django integration is straightforward; release/deploy support |
Free plan is one user and limited events; not ideal as the only event analytics store |
| Honeybadger hosted |
Exceptions, deploy tracking, logs/performance, uptime, check-ins |
Free Developer plan for one user; paid Team starts around $26/mo |
Simple all-in-one app health product; includes check-ins and uptime |
Smaller ecosystem than Sentry; less common if future contributors expect Sentry |
| PostHog |
Product/app events, page views, funnels, lightweight audit trail, optional session replay/error tracking |
Free tier includes 1M analytics events and 100K exceptions/month |
Best fit for “what are students/staff doing?” without turning CloudWatch into an analytics store; billing limits available |
Not infra monitoring; must be careful with PII and free-form submission data |
| Better Stack |
Uptime, logs/traces, incidents, status page, web events |
Free tier includes small telemetry/monitoring allowances; responder/on-call is paid |
Strong hosted uptime/logs/status-page workflow; no self-hosting |
Broader platform; costs depend on telemetry volume and responders |
| Grafana Cloud hosted |
Hosted Grafana/Loki/Tempo/Prometheus-style metrics/logs/traces |
Free hosted tier exists; Pro starts at $19/mo plus usage |
Satisfies “no self-hosted Grafana” while still using Grafana dashboards |
More instrumentation/ops concepts than Sentry/Honeybadger; watch cardinality |
| UptimeRobot |
External uptime checks |
Free plan: 50 monitors with 5-minute interval; paid Solo starts at $7/mo annually |
Very fast way to monitor /api/health/ and public pages |
Uptime only; no app errors or release correlation |
| Healthchecks.io |
Cron/job heartbeats |
Free for 20 checks; paid Business is $20/mo or $16/mo annually |
Very cheap Datamailer outbox/scheduled job heartbeat monitoring |
Does not cover exceptions, product events, or request traffic |
| StatusCake |
External uptime, SSL/domain/page-speed checks |
Free plan has 10 uptime monitors at 5-minute intervals |
Simple hosted uptime alternative to UptimeRobot |
Less generous free monitor count than UptimeRobot |
| New Relic |
All-in-one APM/logs/metrics/synthetics/errors |
Free tier includes 100 GB/month data ingest and one full platform user |
Large free ingest allowance and broad platform |
Heavier than needed; costs can jump with additional full users or ingest |
| Honeycomb |
OpenTelemetry traces/events and high-cardinality debugging |
Free tier up to 20M events/month; Pro starts at $150/mo |
Strong if tracing and high-cardinality backend debugging become central |
Less direct than Sentry/Honeybadger for Django exception triage; paid tier is a bigger step up |
| EventBridge Scheduler + ECS task/management command |
Scheduled health checks/Datamailer status emitters |
EventBridge Scheduler has 14M free invocations/month |
Cheap way to run status commands and heartbeat checks |
Need to wire output into logs/metrics and alerts |
Suggested decision
Recommended starting stack:
- Sentry hosted for Django error tracking and release/deploy health.
- PostHog for product/audit-style events such as login, registrations, page views, submissions, peer-review actions, and staff mutations.
- CloudWatch Logs/alarms for ECS infra, log retention, and low-cardinality app counters.
- UptimeRobot, StatusCake, or Sentry Uptime for
/api/health/ and key page checks.
- Healthchecks.io or Sentry Cron Monitoring for periodic
datamailer_*_status and process_datamailer_outbox heartbeat/check-in monitoring.
Why this combination:
- It avoids self-hosting Grafana.
- It gives release-aware error tracking quickly.
- It reuses the existing health endpoint and
VERSION deployment flow.
- It keeps CloudWatch custom metrics disciplined and cheap.
- It puts high-volume user/application events into a product-event tool with a generous free tier instead of into expensive high-cardinality CloudWatch custom metrics.
If the team wants the cheapest possible single external vendor, Honeybadger is a reasonable alternative to Sentry because it combines error tracking, deploy tracking, uptime, and check-ins. If product analytics becomes the primary need, PostHog should be the second integration after error tracking. If dashboards/log analytics become the primary pain, consider Better Stack or hosted Grafana Cloud next. New Relic has a generous free ingest tier but is probably more platform than this app needs initially.
Implementation checklist
Sources checked
Goal
Add practical monitoring and observability for the course management platform without self-hosting Grafana, prioritizing free or low-cost hosted options.
We want to know what is happening around releases and catch application health problems early:
Current app inventory
The app is a Django project deployed to AWS ECS from GitHub Actions:
.github/workflows/deploy-dev.yaml,.github/workflows/deploy-prod.yaml, anddeploy/deploy_*.sh.deploy/update_task_def.pywrites the Docker tag into the ECSVERSIONenvironment variable./api/health/returns{"status": "ok", "version": settings.VERSION}fromapi/views/health.py.course_management/settings.py.pyproject.toml.data.models:DatamailerOutboxEvent,DatamailerOutboxDispatchRun,DatamailerContactEvent, andDatamailerSendAudit.datamailer_outbox_status,datamailer_send_status, anddatamailer_callback_status.Important events to monitor
Authentication and identity
accounts/views/login.py.accounts/auth.pyviaConsolidatingSocialAccountAdapter.pre_social_login.ConsolidatingSocialAccountAdapter._create_user_for_email.accounts/auth.py.accounts/auth.py::token_required.django-loginasunder/admin/andaccounts/views/impersonation.py::stop_impersonating.Privacy note: current OAuth logging includes full provider response JSON. Before expanding log retention, review this for PII/token exposure and either remove it or redact it.
Page/API traffic and availability
courses/urls.py.cadmin/urls.py.api/urls.py, especially staff-token mutation endpoints./api/health/uptime and version checks.Student lifecycle events
courses/views/registration.py::registration_campaign_view.courses/views/course_enrollment.py.accounts/views/*.courses.models.course.LeaderboardComplaintandcadmin/views/enrollment.py.Homework events
courses/views/homework.pyandcourses/views/homework_submission.py.homework_submission_for_user.courses/views/homework_post_preview.py.courses/scoring.pyand API/staff routes.cadmin/views/homework.py,cadmin/views/homework_submission_edit.py.Suggested event fields:
event_name,course_slug,homework_slug,homework_id,submission_id,user_id,enrollment_id,is_update,question_count,duration_ms,status,error_code.Do not log answer text or free-form comments.
Project and peer review events
courses/views/project.pyandcourses/views/project_submission_edit.py.courses/project_assignment.py.courses/views/project_eval_submit.pyandcourses/views/project_eval_submit_save.py.courses/views/project_eval_actions.py.courses/views/project_eval_submit.py::project_eval_vote_response.courses/project_scoring.pyand API/staff routes.cadmin/views/project_submission_edit.py.Suggested event fields:
event_name,course_slug,project_slug,project_id,submission_id,review_id,reviewer_enrollment_id,user_id,assigned_count,submitted_count,passed_count,duration_ms,status,error_code.Do not log repository contents, notes to peer, or free-form comments. GitHub repo URLs and commit IDs are probably acceptable as structured fields, but avoid over-indexing them in metric dimensions.
Staff/API mutations
api/views/*.api/views/questions.py.api/views/registration_campaigns.py.Suggested event fields:
event_name,actor_user_id,course_slug,object_type,object_id,method,path_name,status,error_code.Datamailer and background-like work
course_management/datamailer_outbox*.py.DatamailerOutboxDispatchRun.DatamailerSendAudit.api/views/webhooks.pyand validation helpers.process_datamailer_outboxexecution should have heartbeat/check-in monitoring.Recommended monitoring architecture
Phase 1: cheapest useful baseline
Use CloudWatch for infra/log retention plus Sentry or Honeybadger for application errors and release visibility.
event,release,environment,request_id,user_id,course_slug,object_type,object_id,status.release=settings.VERSIONandenvironment=dev|prod, matching the ECSVERSIONvalue already set by deployment./api/health/and one or two key public pages.This gives immediate error tracking and release correlation without running Grafana.
Phase 2: app-level events
Add a tiny internal event helper, for example
monitoring/events.py, that logs a structured event and optionally increments metrics later.Initial events:
auth.login_success,auth.login_failed,auth.user_created,auth.social_account_linkedregistration.submittedenrollment.created,enrollment.updatedhomework.submitted,homework.validation_failed,homework.scoredproject.submitted,project.deleted,project.peer_reviews_assigned,project.review_submitted,project.scoredapi.mutation,api.auth_faileddatamailer.outbox_failed,datamailer.outbox_backlog,datamailer.send_failed,datamailer.callback_receivedImportant: do not emit high-cardinality dimensions as CloudWatch custom metrics. Keep detailed IDs in logs/error context. Use metrics only for low-cardinality counters such as event name, status, environment, and object type.
Phase 3: dashboards and alerts
Build a small number of hosted dashboards/alerts:
Option matrix
/api/health/and public pagesSuggested decision
Recommended starting stack:
/api/health/and key page checks.datamailer_*_statusandprocess_datamailer_outboxheartbeat/check-in monitoring.Why this combination:
VERSIONdeployment flow.If the team wants the cheapest possible single external vendor, Honeybadger is a reasonable alternative to Sentry because it combines error tracking, deploy tracking, uptime, and check-ins. If product analytics becomes the primary need, PostHog should be the second integration after error tracking. If dashboards/log analytics become the primary pain, consider Better Stack or hosted Grafana Cloud next. New Relic has a generous free ingest tier but is probably more platform than this app needs initially.
Implementation checklist
sentry-sdk[django]or Honeybadger equivalent) with DSN/env vars disabled by default locally.settings.VERSION./api/health/and at least one public course page.Sources checked