diff --git a/.env.example b/.env.example index e300445c..bb2122dd 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,12 @@ TURBO_TEAM= TURBO_TOKEN= +# Sentry Error Logging (Optional) +SENTRY_ORG=reactive-resume +SENTRY_AUTH_TOKEN=b8cc96665550460697de82011155854fc8b18ffb22aa4ce2bf649125bc96fcd8 +SERVER_SENTRY_DSN=https://aceffdbdaa544768bc85216e7c6a9c50@o4504211187564544.ingest.sentry.io/4504213380071424 +PUBLIC_CLIENT_SENTRY_DSN=https://81967dd583894f4e9b9361e752df8d45@o4504211187564544.ingest.sentry.io/4504211196084224 + # Server + Client TZ=UTC PUBLIC_URL=http://localhost:3000 diff --git a/client/.gitignore b/client/.gitignore index 6cf4f584..66e3da9d 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -40,3 +40,6 @@ __ENV.js # next-sitemap sitemap*.xml + +# sentry +.sentryclirc \ No newline at end of file diff --git a/client/next.config.js b/client/next.config.js index 33494d51..c38a6ea5 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -1,5 +1,6 @@ const { version } = require('../package.json'); const { i18n } = require('./next-i18next.config'); +const { withSentryConfig } = require('@sentry/nextjs'); /** @type {import('next').NextConfig} */ const nextConfig = { @@ -15,6 +16,11 @@ const nextConfig = { domains: ['cdn.rxresu.me', 'www.gravatar.com'], }, + sentry: { + hideSourceMaps: true, + widenClientFileUpload: true, + }, + // Hack to make Tailwind darkMode 'class' strategy with CSS Modules // Ref: https://github.com/tailwindlabs/tailwindcss/issues/3258#issuecomment-968368156 webpack: (config) => { @@ -47,4 +53,10 @@ const nextConfig = { }, }; -module.exports = nextConfig; +/** @type {import('@sentry/nextjs').SentryWebpackPluginOptions} */ +const sentryConfig = { + silent: true, + dryRun: process.env.NODE_ENV !== 'production', +}; + +module.exports = withSentryConfig(nextConfig, sentryConfig); diff --git a/client/package.json b/client/package.json index 5ab77fe6..f77a0f2d 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,7 @@ "@next/env": "^13.0.5", "@react-oauth/google": "^0.5.0", "@reduxjs/toolkit": "^1.9.0", + "@sentry/nextjs": "^7.21.1", "axios": "^1.2.0", "clsx": "^1.2.1", "dayjs": "^1.11.6", diff --git a/client/pages/_error.tsx b/client/pages/_error.tsx new file mode 100644 index 00000000..ffde1dbe --- /dev/null +++ b/client/pages/_error.tsx @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/nextjs'; +import type { NextPage } from 'next'; +import type { ErrorProps } from 'next/error'; +import NextErrorComponent from 'next/error'; + +const CustomErrorComponent: NextPage = (props) => ; + +CustomErrorComponent.getInitialProps = async (contextData) => { + await Sentry.captureUnderscoreErrorException(contextData); + + return NextErrorComponent.getInitialProps(contextData); +}; + +export default CustomErrorComponent; diff --git a/client/sentry.client.config.ts b/client/sentry.client.config.ts new file mode 100644 index 00000000..69065f8b --- /dev/null +++ b/client/sentry.client.config.ts @@ -0,0 +1,14 @@ +import env from '@beam-australia/react-env'; +import * as Sentry from '@sentry/nextjs'; + +const SENTRY_DSN = env('CLIENT_SENTRY_DSN'); + +console.log(SENTRY_DSN); + +if (SENTRY_DSN) { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1.0, + enabled: process.env.NODE_ENV === 'production', + }); +} diff --git a/client/sentry.properties b/client/sentry.properties new file mode 100644 index 00000000..eff724e6 --- /dev/null +++ b/client/sentry.properties @@ -0,0 +1,3 @@ +defaults.url=https://sentry.io/ +defaults.org=reactive-resume +defaults.project=client \ No newline at end of file diff --git a/client/sentry.server.config.ts b/client/sentry.server.config.ts new file mode 100644 index 00000000..371a4450 --- /dev/null +++ b/client/sentry.server.config.ts @@ -0,0 +1,12 @@ +import env from '@beam-australia/react-env'; +import * as Sentry from '@sentry/nextjs'; + +const SENTRY_DSN = env('CLIENT_SENTRY_DSN'); + +if (SENTRY_DSN) { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1.0, + enabled: process.env.NODE_ENV === 'production', + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd008857..4bb1f86b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,7 @@ importers: '@react-oauth/google': ^0.5.0 '@reactive-resume/schema': workspace:* '@reduxjs/toolkit': ^1.9.0 + '@sentry/nextjs': ^7.21.1 '@tailwindcss/typography': ^0.5.8 '@types/downloadjs': ^1.4.3 '@types/lodash': ^4.14.190 @@ -119,6 +120,7 @@ importers: '@next/env': 13.0.5 '@react-oauth/google': 0.5.0_biqbaboplfbrettd7655fr4n2y '@reduxjs/toolkit': 1.9.0_k4ae6lp43ej6mezo3ztvx6pykq + '@sentry/nextjs': 7.21.1_next@13.0.5+react@18.2.0 axios: 1.2.0 clsx: 1.2.1 dayjs: 1.11.6 @@ -200,6 +202,8 @@ importers: '@nestjs/terminus': ^9.1.3 '@nestjs/typeorm': ^9.0.1 '@reactive-resume/schema': workspace:* + '@sentry/node': ^7.21.1 + '@sentry/tracing': ^7.21.1 '@types/bcryptjs': ^2.4.2 '@types/cookie-parser': ^1.4.3 '@types/express': ^4.17.14 @@ -256,6 +260,8 @@ importers: '@nestjs/serve-static': 3.0.0_pf2fb646rsvo5szivgkaevcpvi '@nestjs/terminus': 9.1.3_zj2fokgzpgmxyaqhxo32q3j72y '@nestjs/typeorm': 9.0.1_2ekyp7igf6cbpgvvmiypy4gzpu + '@sentry/node': 7.21.1 + '@sentry/tracing': 7.21.1 '@types/passport': 1.0.11 bcryptjs: 2.4.3 cache-manager: 5.1.3 @@ -2782,10 +2788,182 @@ packages: reselect: 4.1.7 dev: false + /@rollup/plugin-sucrase/4.0.4_rollup@2.78.0: + resolution: {integrity: sha512-YH4J8yoJb5EVnLhAwWxYAQNh2SJOR+SdZ6XdgoKEv6Kxm33riYkM8MlMaggN87UoISP52qAFyZ5ey56wu6umGg==} + engines: {node: '>=12.0.0'} + peerDependencies: + rollup: ^2.53.1 + dependencies: + '@rollup/pluginutils': 4.2.1 + rollup: 2.78.0 + sucrase: 3.29.0 + dev: false + + /@rollup/plugin-virtual/3.0.0_rollup@2.78.0: + resolution: {integrity: sha512-K9KORe1myM62o0lKkNR4MmCxjwuAXsZEtIHpaILfv4kILXTOrXt/R2ha7PzMcCHPYdnkWPiBZK8ed4Zr3Ll5lQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + rollup: 2.78.0 + dev: false + + /@rollup/pluginutils/4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: false + /@rushstack/eslint-patch/1.1.4: resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==} dev: true + /@sentry/browser/7.21.1: + resolution: {integrity: sha512-cS2Jz2+fs9+4pJqLJPtYqGyY97ywJDWAWIR1Yla3hs1QQuH6m0Nz3ojZD1gE2eKH9mHwkGbnNAh+hHcrYrfGzw==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.21.1 + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + tslib: 1.14.1 + dev: false + + /@sentry/cli/1.74.6: + resolution: {integrity: sha512-pJ7JJgozyjKZSTjOGi86chIngZMLUlYt2HOog+OJn+WGvqEkVymu8m462j1DiXAnex9NspB4zLLNuZ/R6rTQHg==} + engines: {node: '>= 8'} + hasBin: true + requiresBuild: true + dependencies: + https-proxy-agent: 5.0.1 + mkdirp: 0.5.6 + node-fetch: 2.6.7 + npmlog: 4.1.2 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@sentry/core/7.21.1: + resolution: {integrity: sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + tslib: 1.14.1 + dev: false + + /@sentry/integrations/7.21.1: + resolution: {integrity: sha512-DbQZSdsqaD9RTy5WvLzonoJa2CIgeapnGfFOadnQGOD8A8GT9Bre/BgcQ5ksHqGnVfzYwpU26k/ue9gjXnI/Pg==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + localforage: 1.10.0 + tslib: 1.14.1 + dev: false + + /@sentry/nextjs/7.21.1_next@13.0.5+react@18.2.0: + resolution: {integrity: sha512-PNX9p8YtXkDXHAJXtngNNLnPNVPuKK3kpCItnoIqMHFrFc5acpUObIyfTZseycv7yBgflw/4206VVBCyIDBeMA==} + engines: {node: '>=8'} + peerDependencies: + next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 + react: 15.x || 16.x || 17.x || 18.x + webpack: '>= 4.0.0' + peerDependenciesMeta: + webpack: + optional: true + dependencies: + '@rollup/plugin-sucrase': 4.0.4_rollup@2.78.0 + '@rollup/plugin-virtual': 3.0.0_rollup@2.78.0 + '@sentry/core': 7.21.1 + '@sentry/integrations': 7.21.1 + '@sentry/node': 7.21.1 + '@sentry/react': 7.21.1_react@18.2.0 + '@sentry/tracing': 7.21.1 + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + '@sentry/webpack-plugin': 1.20.0 + chalk: 3.0.0 + next: 13.0.5_vgii64pd2ccbnbx2v3ro5gbin4 + react: 18.2.0 + rollup: 2.78.0 + tslib: 1.14.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@sentry/node/7.21.1: + resolution: {integrity: sha512-B+p1nQHaFWdCCRVmvqlr/+vdQCI3mGLObucNfK2YC22IQZg7+3u6tEbxJ7umITIjeSSKgf7ZoZwCxL9VfkrNXg==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.21.1 + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + cookie: 0.4.1 + https-proxy-agent: 5.0.1 + lru_map: 0.3.3 + tslib: 1.14.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@sentry/react/7.21.1_react@18.2.0: + resolution: {integrity: sha512-w91PIUyX07mErKgrBQA+7ID8zFKrYDUYSOrFSHufg5DdPq4EpHiNDe/Yngg3e9ELhtr1AbCnEvx9wlvqLi3nZQ==} + engines: {node: '>=8'} + peerDependencies: + react: 15.x || 16.x || 17.x || 18.x + dependencies: + '@sentry/browser': 7.21.1 + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + tslib: 1.14.1 + dev: false + + /@sentry/tracing/7.21.1: + resolution: {integrity: sha512-b1BTPsRaNQpohzegoz59KGuBl+To651vEq0vMS4tCzSyIdxkYso3JCrjDdEqW/2MliQYANNVrUai2bmwmU9h1g==} + engines: {node: '>=8'} + dependencies: + '@sentry/core': 7.21.1 + '@sentry/types': 7.21.1 + '@sentry/utils': 7.21.1 + tslib: 1.14.1 + dev: false + + /@sentry/types/7.21.1: + resolution: {integrity: sha512-3/IKnd52Ol21amQvI+kz+WB76s8/LR5YvFJzMgIoI2S8d82smIr253zGijRXxHPEif8kMLX4Yt+36VzrLxg6+A==} + engines: {node: '>=8'} + dev: false + + /@sentry/utils/7.21.1: + resolution: {integrity: sha512-F0W0AAi8tgtTx6ApZRI2S9HbXEA9ENX1phTZgdNNWcMFm1BNbc21XEwLqwXBNjub5nlA6CE8xnjXRgdZKx4kzQ==} + engines: {node: '>=8'} + dependencies: + '@sentry/types': 7.21.1 + tslib: 1.14.1 + dev: false + + /@sentry/webpack-plugin/1.20.0: + resolution: {integrity: sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw==} + engines: {node: '>= 8'} + dependencies: + '@sentry/cli': 1.74.6 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@sideway/address/4.1.4: resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} dependencies: @@ -3452,6 +3630,11 @@ packages: type-fest: 0.21.3 dev: true + /ansi-regex/2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + dev: false + /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3488,6 +3671,17 @@ packages: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} dev: false + /aproba/1.2.0: + resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} + dev: false + + /are-we-there-yet/1.1.7: + resolution: {integrity: sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==} + dependencies: + delegates: 1.0.0 + readable-stream: 2.3.7 + dev: false + /arg/4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3844,7 +4038,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -3964,6 +4157,11 @@ packages: engines: {node: '>=6'} dev: false + /code-point-at/1.1.0: + resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} + engines: {node: '>=0.10.0'} + dev: false + /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -4049,6 +4247,10 @@ packages: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} dev: false + /console-control-strings/1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: false + /content-disposition/0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -4459,6 +4661,10 @@ packages: engines: {node: '>=0.4.0'} dev: false + /delegates/1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false + /depd/2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -5104,6 +5310,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5419,6 +5629,19 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /gauge/2.7.4: + resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==} + dependencies: + aproba: 1.2.0 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 1.0.2 + strip-ansi: 3.0.1 + wide-align: 1.1.5 + dev: false + /gaxios/5.0.1: resolution: {integrity: sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw==} engines: {node: '>=12'} @@ -5544,6 +5767,17 @@ packages: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true + /glob/7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: false + /glob/7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} dependencies: @@ -5708,6 +5942,10 @@ packages: has-symbols: 1.0.3 dev: true + /has-unicode/2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: false + /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} @@ -5794,6 +6032,10 @@ packages: engines: {node: '>= 4'} dev: true + /immediate/3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + dev: false + /immer/9.0.16: resolution: {integrity: sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==} dev: false @@ -5952,6 +6194,13 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + /is-fullwidth-code-point/1.0.0: + resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} + engines: {node: '>=0.10.0'} + dependencies: + number-is-nan: 1.0.1 + dev: false + /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -6268,6 +6517,12 @@ packages: resolution: {integrity: sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q==} dev: false + /lie/3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + dependencies: + immediate: 3.0.6 + dev: false + /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -6291,6 +6546,12 @@ packages: engines: {node: '>=6.11.5'} dev: true + /localforage/1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + dependencies: + lie: 3.1.1 + dev: false + /locate-path/2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -6396,6 +6657,10 @@ packages: engines: {node: '>=12'} dev: false + /lru_map/0.3.3: + resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} + dev: false + /luxon/1.28.0: resolution: {integrity: sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==} dev: false @@ -7209,6 +7474,20 @@ packages: path-key: 3.1.1 dev: true + /npmlog/4.1.2: + resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} + dependencies: + are-we-there-yet: 1.1.7 + console-control-strings: 1.1.0 + gauge: 2.7.4 + set-blocking: 2.0.0 + dev: false + + /number-is-nan/1.0.1: + resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} + engines: {node: '>=0.10.0'} + dev: false + /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -7613,6 +7892,11 @@ packages: engines: {node: '>=4'} dev: true + /pirates/4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + engines: {node: '>= 6'} + dev: false + /playwright-chromium/1.28.1: resolution: {integrity: sha512-+JVgyAOaLUVN8ppAATtURmb5hEl6kaJjK5j3qh05viZvgJi9QoWkb5K02iBy99ww3q86vSnPoMmtKa1Bv+P7LQ==} engines: {node: '>=14'} @@ -7766,6 +8050,11 @@ packages: /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + /progress/2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: false + /prop-types/15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -8326,6 +8615,14 @@ packages: dependencies: glob: 7.2.3 + /rollup/2.78.0: + resolution: {integrity: sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: false + /run-async/2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -8454,6 +8751,10 @@ packages: - supports-color dev: false + /set-blocking/2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: false + /setprototypeof/1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false @@ -8522,7 +8823,6 @@ packages: /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true /simple-concat/1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -8665,6 +8965,15 @@ packages: engines: {node: '>=10.0.0'} dev: false + /string-width/1.0.2: + resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} + engines: {node: '>=0.10.0'} + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + dev: false + /string-width/4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -8720,6 +9029,13 @@ packages: resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} dev: true + /strip-ansi/3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-regex: 2.1.1 + dev: false + /strip-ansi/6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -8791,6 +9107,19 @@ packages: resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} dev: false + /sucrase/3.29.0: + resolution: {integrity: sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A==} + engines: {node: '>=8'} + hasBin: true + dependencies: + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.5 + ts-interface-checker: 0.1.13 + dev: false + /supports-color/5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -9039,6 +9368,10 @@ packages: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: false + /ts-interface-checker/0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: false + /ts-loader/9.4.1_vfotqvx6lgcbf3upbs6hgaza4q: resolution: {integrity: sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==} engines: {node: '>=12.0.0'} @@ -9566,7 +9899,6 @@ packages: /webpack-sources/3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - dev: true /webpack/5.74.0: resolution: {integrity: sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==} @@ -9679,6 +10011,12 @@ packages: dependencies: isexe: 2.0.0 + /wide-align/1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + dev: false + /widest-line/3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} diff --git a/server/package.json b/server/package.json index a6b5db7f..4dfe291c 100644 --- a/server/package.json +++ b/server/package.json @@ -21,6 +21,8 @@ "@nestjs/serve-static": "^3.0.0", "@nestjs/terminus": "^9.1.3", "@nestjs/typeorm": "^9.0.1", + "@sentry/node": "^7.21.1", + "@sentry/tracing": "^7.21.1", "@types/passport": "^1.0.11", "bcryptjs": "^2.4.3", "cache-manager": "^5.1.3", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 2382aafb..aba15855 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -11,6 +11,7 @@ import { HttpExceptionFilter } from './filters/http-exception.filter'; import { FontsModule } from './fonts/fonts.module'; import { HealthModule } from './health/health.module'; import { IntegrationsModule } from './integrations/integrations.module'; +import { SentryInterceptor } from './interceptors/sentry.interceptor'; import { MailModule } from './mail/mail.module'; import { PrinterModule } from './printer/printer.module'; import { ResumeModule } from './resume/resume.module'; @@ -40,6 +41,10 @@ import { UsersModule } from './users/users.module'; provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor, }, + { + provide: APP_INTERCEPTOR, + useClass: SentryInterceptor, + }, { provide: APP_FILTER, useClass: HttpExceptionFilter, diff --git a/server/src/config/config.module.ts b/server/src/config/config.module.ts index 4f335191..c57ebc57 100644 --- a/server/src/config/config.module.ts +++ b/server/src/config/config.module.ts @@ -7,6 +7,7 @@ import authConfig from './auth.config'; import cacheConfig from './cache.config'; import databaseConfig from './database.config'; import googleConfig from './google.config'; +import loggingConfig from './logging.config'; import mailConfig from './mail.config'; import storageConfig from './storage.config'; @@ -58,12 +59,24 @@ const validationSchema = Joi.object({ PDF_DELETION_TIME: Joi.number() .default(4 * 24 * 60 * 60 * 1000) // 4 days .allow(''), + + // Logging + SERVER_SENTRY_DSN: Joi.string().allow(''), }); @Module({ imports: [ NestConfigModule.forRoot({ - load: [appConfig, authConfig, cacheConfig, databaseConfig, googleConfig, mailConfig, storageConfig], + load: [ + appConfig, + authConfig, + cacheConfig, + databaseConfig, + googleConfig, + loggingConfig, + mailConfig, + storageConfig, + ], validationSchema: validationSchema, }), ], diff --git a/server/src/config/logging.config.ts b/server/src/config/logging.config.ts new file mode 100644 index 00000000..56523056 --- /dev/null +++ b/server/src/config/logging.config.ts @@ -0,0 +1,5 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('logging', () => ({ + sentryDSN: process.env.SERVER_SENTRY_DSN, +})); diff --git a/server/src/interceptors/sentry.interceptor.ts b/server/src/interceptors/sentry.interceptor.ts new file mode 100644 index 00000000..7b24812f --- /dev/null +++ b/server/src/interceptors/sentry.interceptor.ts @@ -0,0 +1,15 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import * as Sentry from '@sentry/node'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Injectable() +export class SentryInterceptor implements NestInterceptor { + intercept(_context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + tap(null, (exception) => { + Sentry.captureException(exception); + }) + ); + } +} diff --git a/server/src/main.ts b/server/src/main.ts index 7b91488c..97b31394 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,7 +1,10 @@ +import '@sentry/tracing'; + import { Logger, ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; +import * as Sentry from '@sentry/node'; import cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; @@ -18,6 +21,16 @@ const bootstrap = async () => { // Pipes app.useGlobalPipes(new ValidationPipe({ transform: true })); + // Sentry Error Logging + const sentryDSN = configService.get('logging.sentryDSN'); + if (sentryDSN) { + Sentry.init({ + dsn: sentryDSN, + tracesSampleRate: 1.0, + enabled: process.env.NODE_ENV === 'production', + }); + } + // Server Port const port = configService.get('app.port'); await app.listen(port);