diff --git a/package-lock.json b/package-lock.json index c348345..f06cb01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@popperjs/core": "^2.11.7", "@prisma/client": "^4.13.0", + "@sentry/node": "^7.52.1", + "@sentry/tracing": "^7.52.1", "body-parser": "^1.20.2", "bootstrap": "^5.3.0-alpha3", "bootstrap-icons": "^1.10.5", @@ -631,6 +633,90 @@ "integrity": "sha512-kEYSUa3XT1Oiu/MbdUkyjfVtAOQmZz69KGKFH/GWoQNLvkscrqy4J4XewEY80BrVuyC3vbV7un4kea0xklWhpA==", "dev": true }, + "node_modules/@sentry-internal/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-6N99rE+Ek0LgbqSzI/XpsKSLUyJjQ9nychViy+MP60p1x+hllukfTsDbNtUNrPlW0Bx+vqUrWKkAqmTFad94TQ==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.52.1.tgz", + "integrity": "sha512-36clugQu5z/9jrit1gzI7KfKbAUimjRab39JeR0mJ6pMuKLTTK7PhbpUAD4AQBs9qVeXN2c7h9SVZiSA0UDvkg==", + "dependencies": { + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.52.1.tgz", + "integrity": "sha512-n3frjYbkY/+eZ5RTQMaipv6Hh9w3ia40GDeRK6KJQit7OLKLmXisD+FsdYzm8Jc784csSvb6HGGVgqLpO1p9Og==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1", + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@sentry/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-1afFeb0X6YcMK8mcsGXpO9rNFEF4Kd3mAUF22hXyFNWVoPNQsvdh/WxG2t3U+hLhehQ1ps3iJ0jxFRGF5zSboA==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.52.1.tgz", + "integrity": "sha512-OMbGBPrJsw0iEXwZ2bJUYxewI1IEAU2e1aQGc0O6QW5+6hhCh+8HO8Xl4EymqwejjztuwStkl6G1qhK+Q0/Row==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.52.1.tgz", + "integrity": "sha512-MPt1Xu/jluulknW8CmZ2naJ53jEdtdwCBSo6fXJvOTI0SDqwIPbXDVrsnqLAhVJuIN7xbkj96nuY/VBR6S5sWg==", + "dependencies": { + "@sentry/types": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -840,7 +926,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -852,7 +937,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -868,8 +952,7 @@ "node_modules/agent-base/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/aggregate-error": { "version": "3.1.0", @@ -2637,7 +2720,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -2650,7 +2732,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2666,8 +2747,7 @@ "node_modules/https-proxy-agent/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/human-signals": { "version": "2.1.0", @@ -3227,6 +3307,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5443,6 +5528,11 @@ "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", "dev": true }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/tsparticles-confetti": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/tsparticles-confetti/-/tsparticles-confetti-2.9.3.tgz", diff --git a/package.json b/package.json index 36422db..c2696c1 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "dependencies": { "@popperjs/core": "^2.11.7", "@prisma/client": "^4.13.0", + "@sentry/node": "^7.52.1", + "@sentry/tracing": "^7.52.1", "body-parser": "^1.20.2", "bootstrap": "^5.3.0-alpha3", "bootstrap-icons": "^1.10.5", diff --git a/src/frontend/partials/head.eta.html b/src/frontend/partials/head.eta.html index 6171995..bcff16e 100644 --- a/src/frontend/partials/head.eta.html +++ b/src/frontend/partials/head.eta.html @@ -44,3 +44,4 @@ + diff --git a/src/index.ts b/src/index.ts index 9717894..c88dd3c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,15 @@ import { Signale } from 'signale'; import ConfigHandler from './assets/configHandler'; -import express, { Request, Response } from 'express'; +import express, { NextFunction, Request, Response } from 'express'; import fileUpload from 'express-fileupload'; import { PrismaClient } from '@prisma/client'; import * as eta from 'eta'; import bodyParser from 'body-parser'; +// Sentry +import * as Sentry from '@sentry/node'; +import * as Tracing from '@sentry/tracing'; + import routes from './routes/index.js'; // Get app directory. @@ -31,6 +35,7 @@ export const config = new ConfigHandler(__path + '/config.json', { db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE', http_listen_address: '127.0.0.1', http_port: 3000, + sentry_dsn: 'https://ID@sentry.example.com/PROJECTID', debug: false }); @@ -43,6 +48,27 @@ export const prisma = new PrismaClient({ }); export const app = express(); +Sentry.init({ + dsn: config.global.sentry_dsn, + integrations: [ + // enable HTTP calls tracing + new Sentry.Integrations.Http({ tracing: true }), + // enable Express.js middleware tracing + new Tracing.Integrations.Express({ app }) + ], + + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0 +}); + +// RequestHandler creates a separate execution context using domains, so that every +// transaction/span/breadcrumb is attached to its own Hub instance +app.use(Sentry.Handlers.requestHandler()); +// TracingHandler creates a trace for every incoming request +app.use(Sentry.Handlers.tracingHandler()); + app.set('x-powered-by', false); app.engine('html', eta.renderFile); @@ -57,6 +83,18 @@ app.use(express.static(__path + '/static')); app.use(routes); +// The error handler must be before any other error middleware and after all controllers +app.use(Sentry.Handlers.errorHandler()); + +// Optional fallthrough error handler +app.use(function onError(err: Error, req: Request, res: Response, next: NextFunction) { + // The error id is attached to `res.sentry` to be returned + // and optionally displayed to the user for support. + res.statusCode = 500; + // @ts-ignore + res.end(res.sentry + '\n'); +}); + app.listen(config.global.http_port, config.global.http_listen_address, () => { log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`); }); diff --git a/src/routes/index.ts b/src/routes/index.ts index 822817b..046f6c2 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -12,6 +12,9 @@ Router.use('/static', static_routes); Router.use('/api', api_routes); Router.use('/', frontend_routes); +Router.get('/debug-sentry', function mainHandler(req, res) { + throw new Error('My first Sentry error!'); +}); // Default route. Router.all('*', function (req, res) { // TODO: Respond based on content-type (with req.is('application/json'))