diff --git a/frontend/bun.lock b/frontend/bun.lock index c7ddb71..050dd0e 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -4,6 +4,7 @@ "": { "name": "frontend", "dependencies": { + "@sentry/react": "^10.17.0", "@t3-oss/env-core": "^0.13.8", "@tanstack/react-query": "^5.89.0", "@tanstack/react-router": "^1.131.44", @@ -296,6 +297,20 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg=="], + "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.17.0", "", { "dependencies": { "@sentry/core": "10.17.0" } }, "sha512-jXC7dtItZYNGP+K9Lo+3MWaWaGVI6uDIPGB9BAZkZntc/1lGfhMPm7Fo2qb1N1bUP0vOTJ2TmSUA8GNxyxgekQ=="], + + "@sentry-internal/feedback": ["@sentry-internal/feedback@10.17.0", "", { "dependencies": { "@sentry/core": "10.17.0" } }, "sha512-KIIF/dDQqYENbx4vn6B0evy/qx1QtEZsSZRvXNX6tUm14CCyrZeDymBMyEzu8RQ5PeZXibbPEkz7xOXiG3q+eQ=="], + + "@sentry-internal/replay": ["@sentry-internal/replay@10.17.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.17.0", "@sentry/core": "10.17.0" } }, "sha512-9kirOPp3wbf+TMyHmA8iStKAysklZPcrPlB0v2zh0qRj1zNFY0xAD2WSgxuCvD9rEo5qKhmAKcaT7Ujin64uSw=="], + + "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.17.0", "", { "dependencies": { "@sentry-internal/replay": "10.17.0", "@sentry/core": "10.17.0" } }, "sha512-GXKZIraXrboP03+XS+KwkkKVJO+cSlM0HrfjePSfFqiNbbnjRhOLekoLuDvvH/ZEXPUoUJD1We5IPBg+sZZQfQ=="], + + "@sentry/browser": ["@sentry/browser@10.17.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.17.0", "@sentry-internal/feedback": "10.17.0", "@sentry-internal/replay": "10.17.0", "@sentry-internal/replay-canvas": "10.17.0", "@sentry/core": "10.17.0" } }, "sha512-X4OiGECzkp6tIyAKXB/9beBC2oX1xKOEkDo4v/phIKGPzrmQ4o55j2a6/V20jSfSN7w+kfZ56ILE71SzC9w1aQ=="], + + "@sentry/core": ["@sentry/core@10.17.0", "", {}, "sha512-UVIvxSzS0n5QbIDPyFf0WX9I77Of1bcr6a0sCEKfjhJGmGQ8mFWoWgR2gF4wcPw60XUrzbryCr79eOsIHLQ5cw=="], + + "@sentry/react": ["@sentry/react@10.17.0", "", { "dependencies": { "@sentry/browser": "10.17.0", "@sentry/core": "10.17.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-vSJ1+HruWBoQtWlK8r/SSTUyA6cQ2Xc+NNRzIdsVHWUCSo/lAA4UvxqLXyIkEtftqS1+N/+WrMOCf09XuHWpqg=="], + "@swc/core": ["@swc/core@1.13.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", "@swc/core-linux-arm-gnueabihf": "1.13.5", "@swc/core-linux-arm64-gnu": "1.13.5", "@swc/core-linux-arm64-musl": "1.13.5", "@swc/core-linux-x64-gnu": "1.13.5", "@swc/core-linux-x64-musl": "1.13.5", "@swc/core-win32-arm64-msvc": "1.13.5", "@swc/core-win32-ia32-msvc": "1.13.5", "@swc/core-win32-x64-msvc": "1.13.5" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ=="], "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ=="], @@ -590,6 +605,8 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -906,6 +923,8 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/frontend/package.json b/frontend/package.json index 6749fc5..fd35ae7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "preview": "vite preview" }, "dependencies": { + "@sentry/react": "^10.17.0", "@t3-oss/env-core": "^0.13.8", "@tanstack/react-query": "^5.89.0", "@tanstack/react-router": "^1.131.44", diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d0c4134..bead7eb 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,17 @@ import {StrictMode} from 'react'; import ReactDOM from 'react-dom/client'; +import * as Sentry from '@sentry/react'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import {createRouter, RouterProvider} from '@tanstack/react-router'; import {routeTree} from './routeTree.gen'; +Sentry.init({ + dsn: 'https://82cb16514b69a48430dc945408138e0d@o1.ingest.us.sentry.io/4510076293283840', + sendDefaultPii: false, + environment: String(import.meta.env.MODE), +}); + const queryClient = new QueryClient(); const router = createRouter({ routeTree, @@ -26,7 +33,15 @@ declare module '@tanstack/react-router' { const rootElement = document.getElementById('root')!; if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); + const root = ReactDOM.createRoot(rootElement, { + onUncaughtError: Sentry.reactErrorHandler((error, errorInfo) => { + console.warn('Uncaught error', error, errorInfo.componentStack); + }), + // Callback called when React catches an error in an ErrorBoundary. + onCaughtError: Sentry.reactErrorHandler(), + // Callback called when React automatically recovers from errors. + onRecoverableError: Sentry.reactErrorHandler(), + }); root.render( diff --git a/pyproject.toml b/pyproject.toml index c1faf4b..a88b8aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "google-cloud-logging>=3.11.4", "jira>=3.5.0", "psycopg[binary]>=3.2.11", + "sentry-sdk[django]>=2.39.0", "slack-sdk>=3.31.0", ] diff --git a/src/firetower/settings.py b/src/firetower/settings.py index 484296f..d996799 100644 --- a/src/firetower/settings.py +++ b/src/firetower/settings.py @@ -13,6 +13,8 @@ import os from pathlib import Path +import sentry_sdk + def env_is_dev() -> bool: return os.environ.get("DJANGO_ENV", "dev") == "dev" @@ -29,6 +31,12 @@ def dev_default(key: str, default: str = "") -> str: ) +sentry_sdk.init( + dsn="https://ef9a24c7ef0f1a8ba7e8f821d6ab1dd9@o1.ingest.us.sentry.io/4510076289548288", + send_default_pii=False, + environment=os.environ.get("DJANGO_ENV", "dev"), +) + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/uv.lock b/uv.lock index 0e7070c..e567f2d 100644 --- a/uv.lock +++ b/uv.lock @@ -198,6 +198,7 @@ dependencies = [ { name = "google-cloud-logging" }, { name = "jira" }, { name = "psycopg", extra = ["binary"] }, + { name = "sentry-sdk", extra = ["django"] }, { name = "slack-sdk" }, ] @@ -223,6 +224,7 @@ requires-dist = [ { name = "google-cloud-logging", specifier = ">=3.11.4" }, { name = "jira", specifier = ">=3.5.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.11" }, + { name = "sentry-sdk", extras = ["django"], specifier = ">=2.39.0" }, { name = "slack-sdk", specifier = ">=3.31.0" }, ] @@ -900,6 +902,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/fd/a502ee24d8c7d12a8f749878ae0949b8eeb50aeac22dc5a613d417a256d0/slack_sdk-3.37.0-py2.py3-none-any.whl", hash = "sha256:e108a0836eafda74d8a95e76c12c2bcb010e645d504d8497451e4c7ebb229c87", size = 302751, upload-time = "2025-10-06T23:07:19.542Z" }, ] +[[package]] +name = "sentry-sdk" +version = "2.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/72/43294fa4bdd75c51610b5104a3ff834459ba653abb415150aa7826a249dd/sentry_sdk-2.39.0.tar.gz", hash = "sha256:8c185854d111f47f329ab6bc35993f28f7a6b7114db64aa426b326998cfa14e9", size = 348556, upload-time = "2025-09-25T09:15:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/44/4356cc64246ba7b2b920f7c97a85c3c52748e213e250b512ee8152eb559d/sentry_sdk-2.39.0-py2.py3-none-any.whl", hash = "sha256:ba655ca5e57b41569b18e2a5552cb3375209760a5d332cdd87c6c3f28f729602", size = 370851, upload-time = "2025-09-25T09:15:36.35Z" }, +] + +[package.optional-dependencies] +django = [ + { name = "django" }, +] + [[package]] name = "sqlparse" version = "0.5.3"