From 0fa3e6ac7cb650b89bf74db14ea1bae551ad9ce3 Mon Sep 17 00:00:00 2001 From: MrUnknownDE Date: Sat, 29 Mar 2025 17:38:52 +0100 Subject: [PATCH] enable sentry logging --- backend/Dockerfile | 4 + backend/package-lock.json | 996 ++++++++++++++++++++++++++++++++++++++ backend/package.json | 2 + backend/server.js | 227 ++++++++- compose.yml | 4 + 5 files changed, 1208 insertions(+), 25 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index da5c0f8..381512e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -63,6 +63,10 @@ ENV GEOIP_ASN_DB=./data/GeoLite2-ASN.mmdb ARG GIT_COMMIT_SHA=unknown ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA} +# Define build argument and environment variable for Sentry DSN +ARG SENTRY_DSN +ENV SENTRY_DSN=${SENTRY_DSN} + # Run the app when the container launches CMD ["node", "server.js"] \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 980f426..3c92a08 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "@maxmind/geoip2-node": "^6.0.0", + "@sentry/node": "^8.0.0", + "@sentry/profiling-node": "^8.0.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", @@ -28,6 +30,761 @@ "maxmind": "^4.2.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.46.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz", + "integrity": "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.0.tgz", + "integrity": "sha512-Q57JGpH6T4dkYHo9tKXONgLtxzsh1ZEW5M9A/OwKrZFyEpLqWgjhcZ3hIuVvDlhb426iDF1f9FPToV/mi5rpeA==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.36" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.0.tgz", + "integrity": "sha512-88+qCHZC02up8PwKHk0UQKLLqGGURzS3hFQBZC7PnGwReuoKjHXS1o29H58S+QkXJpkTr2GACbx8j6mUoGjNPA==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.0.tgz", + "integrity": "sha512-XFWVx6k0XlU8lu6cBlCa29ONtVt6ADEjmxtyAyeF2+rifk8uBJbk1La0yIVfI0DoKURGbaEDTNelaXG9l/lNNQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.44.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.44.1.tgz", + "integrity": "sha512-RoVeMGKcNttNfXMSl6W4fsYoCAYP1vi6ZAWIGhBY+o7R9Y0afA7f9JJL0j8LHbyb0P0QhSYk+6O56OwI2k4iRQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.0.tgz", + "integrity": "sha512-JGwmHhBkRT2G/BYNV1aGI+bBjJu4fJUD/5/Jat0EWZa2ftrLV3YE8z84Fiij/wK32oMZ88eS8DI4ecLGZhpqsQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.0.tgz", + "integrity": "sha512-at8GceTtNxD1NfFKGAuwtqM41ot/TpcLh+YsGe4dhf7gvv1HW/ZWdq6nfRtS6UjIvZJOokViqLPJ3GVtZItAnQ==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.0.tgz", + "integrity": "sha512-Cc8SMf+nLqp0fi8oAnooNEfwZWFnzMiBHCGmDFYqmgjPylyLmi83b+NiTns/rKGwlErpW0AGPt0sMpkbNlzn8w==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.1.tgz", + "integrity": "sha512-VH6mU3YqAKTePPfUPwfq4/xr049774qWtfTuJqVHoVspCLiT3bW+fCQ1toZxt6cxRPYASoYaBsMA3CWo8B8rcw==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.1.tgz", + "integrity": "sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/instrumentation": "0.57.1", + "@opentelemetry/semantic-conventions": "1.28.0", + "forwarded-parse": "2.1.2", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/api-logs": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.1.tgz", + "integrity": "sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/instrumentation": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.1.tgz", + "integrity": "sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==", + "dependencies": { + "@opentelemetry/api-logs": "0.57.1", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.0.tgz", + "integrity": "sha512-4HqP9IBC8e7pW9p90P3q4ox0XlbLGme65YTrA3UTLvqvo4Z6b0puqZQP203YFu8m9rE/luLfaG7/xrwwqMUpJw==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.0.tgz", + "integrity": "sha512-LB+3xiNzc034zHfCtgs4ITWhq6Xvdo8bsq7amR058jZlf2aXXDrN9SV4si4z2ya9QX4tz6r4eZJwDkXOp14/AQ==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.0.tgz", + "integrity": "sha512-SlT0+bLA0Lg3VthGje+bSZatlGHw/vwgQywx0R/5u9QC59FddTQSPJeWNw29M6f8ScORMeUOOTwihlQAn4GkJQ==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.0.tgz", + "integrity": "sha512-HFdvqf2+w8sWOuwtEXayGzdZ2vWpCKEQv5F7+2DSA74Te/Cv4rvb2E5So5/lh+ok4/RAIPuvCbCb/SHQFzMmbw==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.0.tgz", + "integrity": "sha512-Tn7emHAlvYDFik3vGU0mdwvWJDwtITtkJ+5eT2cUquct6nIs+H8M47sqMJkCpyPe5QIBJoTOHxmc6mj9lz6zDw==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.51.0.tgz", + "integrity": "sha512-cMKASxCX4aFxesoj3WK8uoQ0YUrRvnfxaO72QWI2xLu5ZtgX/QvdGBlU3Ehdond5eb74c2s1cqRQUIptBnKz1g==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.0.tgz", + "integrity": "sha512-mtVv6UeaaSaWTeZtLo4cx4P5/ING2obSqfWGItIFSunQBrYROfhuVe7wdIrFUs2RH1tn2YYpAJyMaRe/bnTTIQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.0.tgz", + "integrity": "sha512-tWWyymgwYcTwZ4t8/rLDfPYbOTF3oYB8SxnYMtIQ1zEf5uDm90Ku3i6U/vhaMyfHNlIHvDhvJh+qx5Nc4Z3Acg==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.0.tgz", + "integrity": "sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-nestjs-core": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.44.0.tgz", + "integrity": "sha512-t16pQ7A4WYu1yyQJZhRKIfUNvl5PAaF2pEteLvgJb/BWdd1oNuU1rOYt4S825kMy+0q4ngiX281Ss9qiwHfxFQ==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.50.0.tgz", + "integrity": "sha512-TtLxDdYZmBhFswm8UIsrDjh/HFBeDXd4BLmE8h2MxirNHewLJ0VS9UUddKKEverb5Sm2qFVjqRjcU+8Iw4FJ3w==", + "dependencies": { + "@opentelemetry/core": "^1.26.0", + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", + "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.0.tgz", + "integrity": "sha512-aTUWbzbFMFeRODn3720TZO0tsh/49T8H3h8vVnVKJ+yE36AeW38Uj/8zykQ/9nO8Vrtjr5yKuX3uMiG/W8FKNw==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.0.tgz", + "integrity": "sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.57.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.0.tgz", + "integrity": "sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.57.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.30.0.tgz", + "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.22.0.tgz", + "integrity": "sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==", + "dependencies": { + "@opentelemetry/api": "^1.8", + "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0", + "@opentelemetry/sdk-trace-base": "^1.22" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@sentry/core": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.55.0.tgz", + "integrity": "sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/node": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.55.0.tgz", + "integrity": "sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/instrumentation-amqplib": "^0.46.0", + "@opentelemetry/instrumentation-connect": "0.43.0", + "@opentelemetry/instrumentation-dataloader": "0.16.0", + "@opentelemetry/instrumentation-express": "0.47.0", + "@opentelemetry/instrumentation-fastify": "0.44.1", + "@opentelemetry/instrumentation-fs": "0.19.0", + "@opentelemetry/instrumentation-generic-pool": "0.43.0", + "@opentelemetry/instrumentation-graphql": "0.47.0", + "@opentelemetry/instrumentation-hapi": "0.45.1", + "@opentelemetry/instrumentation-http": "0.57.1", + "@opentelemetry/instrumentation-ioredis": "0.47.0", + "@opentelemetry/instrumentation-kafkajs": "0.7.0", + "@opentelemetry/instrumentation-knex": "0.44.0", + "@opentelemetry/instrumentation-koa": "0.47.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.44.0", + "@opentelemetry/instrumentation-mongodb": "0.51.0", + "@opentelemetry/instrumentation-mongoose": "0.46.0", + "@opentelemetry/instrumentation-mysql": "0.45.0", + "@opentelemetry/instrumentation-mysql2": "0.45.0", + "@opentelemetry/instrumentation-nestjs-core": "0.44.0", + "@opentelemetry/instrumentation-pg": "0.50.0", + "@opentelemetry/instrumentation-redis-4": "0.46.0", + "@opentelemetry/instrumentation-tedious": "0.18.0", + "@opentelemetry/instrumentation-undici": "0.10.0", + "@opentelemetry/resources": "^1.30.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.28.0", + "@prisma/instrumentation": "5.22.0", + "@sentry/core": "8.55.0", + "@sentry/opentelemetry": "8.55.0", + "import-in-the-middle": "^1.11.2" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.55.0.tgz", + "integrity": "sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ==", + "dependencies": { + "@sentry/core": "8.55.0" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1", + "@opentelemetry/core": "^1.30.1", + "@opentelemetry/instrumentation": "^0.57.1", + "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/semantic-conventions": "^1.28.0" + } + }, + "node_modules/@sentry/profiling-node": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@sentry/profiling-node/-/profiling-node-8.55.0.tgz", + "integrity": "sha512-rYrlxbMlfQLHhkBUEC7bviuja1rojCb4+TtXi4NGnB4PppZeveGeuVTdJDWt3Ed6IBd20EEYoXv4+0aETbEnpw==", + "hasInstallScript": true, + "dependencies": { + "@sentry/core": "8.55.0", + "@sentry/node": "8.55.0", + "detect-libc": "^2.0.2", + "node-abi": "^3.61.0" + }, + "bin": { + "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -40,6 +797,25 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -175,6 +951,11 @@ "upper-case-first": "^1.1.0" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==" + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -305,6 +1086,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/dot-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", @@ -517,6 +1306,11 @@ "node": ">= 0.6" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -654,6 +1448,17 @@ "node": ">=0.10.0" } }, + "node_modules/import-in-the-middle": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", + "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -679,6 +1484,20 @@ "node": ">= 0.10" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -837,6 +1656,11 @@ "npm": ">=6" } }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -858,6 +1682,17 @@ "lower-case": "^1.1.1" } }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -978,11 +1813,44 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", + "integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/pino": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.6.0.tgz", @@ -1040,6 +1908,41 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/process-warning": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", @@ -1141,11 +2044,64 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1183,6 +2139,17 @@ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1252,6 +2219,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1414,6 +2386,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/swap-case": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", @@ -1473,6 +2456,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1558,6 +2546,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/backend/package.json b/backend/package.json index 28dc974..d20ce63 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,8 @@ "license": "ISC", "dependencies": { "@maxmind/geoip2-node": "^6.0.0", + "@sentry/node": "^8.0.0", + "@sentry/profiling-node": "^8.0.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", diff --git a/backend/server.js b/backend/server.js index 55f578b..5ec53fc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,5 +1,31 @@ // server.js require('dotenv').config(); // Lädt Variablen aus .env in process.env + +// --- Sentry Initialisierung (GANZ OBEN!) --- +const Sentry = require("@sentry/node"); +const { ProfilingIntegration } = require("@sentry/profiling-node"); + +Sentry.init({ + // Ersetzen Sie dies durch Ihren echten Sentry DSN + dsn: process.env.SENTRY_DSN || "YOUR_PLACEHOLDER_SENTRY_DSN", + integrations: [ + // enable HTTP calls tracing + new Sentry.Integrations.Http({ tracing: true }), + // enable Express.js middleware tracing + new Sentry.Integrations.Express({ app: require('express')() }), // Temporäre App für Integration + new ProfilingIntegration(), + ], + // Performance Monitoring + tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, // 10% in Produktion, 100% sonst + // Set sampling rate for profiling - this is relative to tracesSampleRate + profilesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, // 10% in Produktion, 100% sonst + environment: process.env.NODE_ENV || 'development', + // Optional: Release-Version setzen (z.B. über GIT_COMMIT_SHA) + release: process.env.GIT_COMMIT_SHA || undefined, +}); +// --- Ende Sentry Initialisierung --- + + const express = require('express'); const cors = require('cors'); const geoip = require('@maxmind/geoip2-node'); @@ -23,6 +49,15 @@ const logger = pino({ const app = express(); const PORT = process.env.PORT || 3000; +// --- Sentry Request Handler (ALS ERSTE MIDDLEWARE!) --- +app.use(Sentry.Handlers.requestHandler()); +// --- Ende Sentry Request Handler --- + +// --- Sentry Tracing Handler (NACH CORS/JSON, VOR ROUTEN) --- +app.use(Sentry.Handlers.tracingHandler()); +// --- Ende Sentry Tracing Handler --- + + // --- Globale Variablen für MaxMind Reader --- let cityReader; let asnReader; @@ -118,8 +153,10 @@ function executeCommand(command, args) { return new Promise((resolve, reject) => { args.forEach(arg => { if (typeof arg === 'string' && /[;&|`$()<>]/.test(arg)) { + const error = new Error(`Invalid character detected in command argument.`); logger.error({ command, arg }, "Potential command injection attempt detected in argument"); - return reject(new Error(`Invalid character detected in command argument.`)); + Sentry.captureException(error); // An Sentry senden + return reject(error); } }); @@ -130,13 +167,17 @@ function executeCommand(command, args) { proc.stdout.on('data', (data) => { stdout += data.toString(); }); proc.stderr.on('data', (data) => { stderr += data.toString(); }); proc.on('error', (err) => { + const error = new Error(`Failed to start command ${command}: ${err.message}`); logger.error({ command, args, error: err.message }, `Failed to start command`); - reject(new Error(`Failed to start command ${command}: ${err.message}`)); + Sentry.captureException(error); // An Sentry senden + reject(error); }); proc.on('close', (code) => { if (code !== 0) { + const error = new Error(`Command ${command} failed with code ${code}: ${stderr || 'No stderr output'}`); logger.error({ command, args, exitCode: code, stderr: stderr.trim(), stdout: stdout.trim() }, `Command failed`); - reject(new Error(`Command ${command} failed with code ${code}: ${stderr || 'No stderr output'}`)); + Sentry.captureException(error, { extra: { stdout: stdout.trim(), stderr: stderr.trim() } }); // An Sentry senden + reject(error); } else { resolve(stdout); } @@ -196,6 +237,7 @@ function parsePingOutput(pingOutput) { } catch (parseError) { logger.error({ error: parseError.message, output: pingOutput }, "Failed to parse ping output"); + Sentry.captureException(parseError, { extra: { pingOutput } }); // An Sentry senden result.error = "Failed to parse ping output."; } return result; @@ -260,6 +302,7 @@ async function initialize() { } catch (error) { logger.fatal({ error: error.message, stack: error.stack }, 'Could not initialize databases. Exiting.'); + Sentry.captureException(error); // An Sentry senden process.exit(1); } } @@ -279,6 +322,11 @@ const generalLimiter = rateLimit({ keyGenerator: (req, res) => req.ip || req.socket.remoteAddress, handler: (req, res, next, options) => { logger.warn({ ip: req.ip || req.socket.remoteAddress, route: req.originalUrl }, 'Rate limit exceeded'); + // Optional: Rate Limit Info an Sentry senden + Sentry.captureMessage('Rate limit exceeded', { + level: 'warning', + extra: { ip: req.ip || req.socket.remoteAddress, route: req.originalUrl } + }); res.status(options.statusCode).send(options.message); } }); @@ -295,7 +343,7 @@ app.use('/api/whois-lookup', generalLimiter); // --- Routen --- // Haupt-Endpunkt: Liefert alle Infos zur IP des Clients -app.get('/api/ipinfo', async (req, res) => { +app.get('/api/ipinfo', async (req, res, next) => { // next hinzugefügt für Sentry Error Handler const requestIp = req.ip || req.socket.remoteAddress; logger.info({ ip: requestIp, method: req.method, url: req.originalUrl }, 'ipinfo request received'); const clientIp = getCleanIp(requestIp); @@ -312,6 +360,11 @@ app.get('/api/ipinfo', async (req, res) => { }); } logger.error({ rawIp: requestIp, cleanedIp: clientIp }, 'Could not determine a valid client IP'); + // Fehler an Sentry senden, bevor die Antwort gesendet wird + Sentry.captureMessage('Could not determine a valid client IP', { + level: 'error', + extra: { rawIp: requestIp, cleanedIp: clientIp } + }); return res.status(400).json({ error: 'Could not determine a valid client IP address.', rawIp: requestIp, cleanedIp: clientIp }); } @@ -333,6 +386,8 @@ app.get('/api/ipinfo', async (req, res) => { logger.debug({ ip: clientIp, geo }, 'GeoIP lookup successful'); } catch (e) { logger.warn({ ip: clientIp, error: e.message }, `MaxMind City lookup failed`); + // Optional: GeoIP Fehler an Sentry senden (kann viel Lärm verursachen) + // Sentry.captureException(e, { level: 'warning', extra: { ip: clientIp } }); geo = { error: 'GeoIP lookup failed (IP not found in database or private range).' }; } @@ -347,6 +402,8 @@ app.get('/api/ipinfo', async (req, res) => { logger.debug({ ip: clientIp, asn }, 'ASN lookup successful'); } catch (e) { logger.warn({ ip: clientIp, error: e.message }, `MaxMind ASN lookup failed`); + // Optional: ASN Fehler an Sentry senden + // Sentry.captureException(e, { level: 'warning', extra: { ip: clientIp } }); asn = { error: 'ASN lookup failed (IP not found in database or private range).' }; } @@ -358,6 +415,8 @@ app.get('/api/ipinfo', async (req, res) => { } catch (e) { if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') { logger.warn({ ip: clientIp, error: e.message, code: e.code }, `rDNS lookup error`); + // Optional: rDNS Fehler an Sentry senden + // Sentry.captureException(e, { level: 'warning', extra: { ip: clientIp } }); } else { logger.debug({ ip: clientIp, code: e.code }, 'rDNS lookup failed (No record)'); } @@ -373,12 +432,13 @@ app.get('/api/ipinfo', async (req, res) => { } catch (error) { logger.error({ ip: clientIp, error: error.message, stack: error.stack }, 'Error processing ipinfo'); - res.status(500).json({ error: 'Internal server error while processing IP information.' }); + Sentry.captureException(error, { extra: { ip: clientIp } }); // An Sentry senden + next(error); // Fehler an Sentry Error Handler weiterleiten } }); // Ping Endpunkt -app.get('/api/ping', async (req, res) => { +app.get('/api/ping', async (req, res, next) => { // next hinzugefügt const targetIpRaw = req.query.targetIp; const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; const requestIp = req.ip || req.socket.remoteAddress; @@ -409,17 +469,20 @@ app.get('/api/ping', async (req, res) => { } catch (error) { logger.error({ requestIp, targetIp, error: error.message }, 'Ping command failed'); - const parsedError = parsePingOutput(error.message); + Sentry.captureException(error, { extra: { requestIp, targetIp } }); // An Sentry senden + const parsedError = parsePingOutput(error.message); // Versuch, Fehler aus der Ausgabe zu parsen + // Sende 500, aber mit Fehlerdetails im Body res.status(500).json({ success: false, error: `Ping command failed: ${parsedError.error || error.message}`, rawOutput: parsedError.rawOutput || error.message }); + // next(error); // Optional: Fehler auch an Sentry Error Handler weiterleiten } }); // Traceroute Endpunkt (Server-Sent Events) -app.get('/api/traceroute', (req, res) => { +app.get('/api/traceroute', (req, res) => { // Kein next hier, da SSE anders behandelt wird const targetIpRaw = req.query.targetIp; const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; const requestIp = req.ip || req.socket.remoteAddress; @@ -435,6 +498,17 @@ app.get('/api/traceroute', (req, res) => { return res.status(403).json({ error: 'Operations on private or local IP addresses are not allowed.' }); } + // Sentry Transaction für den Stream starten + const transaction = Sentry.startTransaction({ + op: "traceroute.stream", + name: `/api/traceroute?targetIp=${targetIp}`, + }); + // Scope für diese Anfrage setzen, damit Fehler/Events der Transaktion zugeordnet werden + Sentry.configureScope(scope => { + scope.setSpan(transaction); + scope.setContext("request", { ip: requestIp, targetIp }); + }); + try { logger.info({ requestIp, targetIp }, `Starting traceroute stream...`); res.setHeader('Content-Type', 'text/event-stream'); @@ -452,11 +526,16 @@ app.get('/api/traceroute', (req, res) => { const sendEvent = (event, data) => { try { - res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); + if (!res.writableEnded) { + res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); + } } catch (e) { logger.error({ requestIp, targetIp, event, error: e.message }, "Error writing to SSE stream (client likely disconnected)"); - proc.kill(); + Sentry.captureException(e, { level: 'warning', extra: { requestIp, targetIp, event } }); + if (!proc.killed) proc.kill(); if (!res.writableEnded) res.end(); + transaction.setStatus('internal_error'); + transaction.finish(); } }; @@ -479,13 +558,17 @@ app.get('/api/traceroute', (req, res) => { proc.stderr.on('data', (data) => { const errorMsg = data.toString().trim(); logger.warn({ requestIp, targetIp, stderr: errorMsg }, 'Traceroute stderr output'); + Sentry.captureMessage('Traceroute stderr output', { level: 'warning', extra: { requestIp, targetIp, stderr: errorMsg } }); sendEvent('error', { error: errorMsg }); }); proc.on('error', (err) => { logger.error({ requestIp, targetIp, error: err.message }, `Failed to start traceroute command`); + Sentry.captureException(err, { extra: { requestIp, targetIp } }); sendEvent('error', { error: `Failed to start traceroute: ${err.message}` }); if (!res.writableEnded) res.end(); + transaction.setStatus('internal_error'); + transaction.finish(); }); proc.on('close', (code) => { @@ -496,22 +579,31 @@ app.get('/api/traceroute', (req, res) => { } if (code !== 0) { logger.error({ requestIp, targetIp, exitCode: code }, `Traceroute command finished with error code ${code}`); + Sentry.captureMessage('Traceroute command failed', { level: 'error', extra: { requestIp, targetIp, exitCode: code } }); sendEvent('error', { error: `Traceroute command failed with exit code ${code}` }); + transaction.setStatus('unknown_error'); // Oder spezifischer, falls möglich } else { logger.info({ requestIp, targetIp }, `Traceroute stream completed successfully.`); + transaction.setStatus('ok'); } sendEvent('end', { exitCode: code }); if (!res.writableEnded) res.end(); + transaction.finish(); }); req.on('close', () => { logger.info({ requestIp, targetIp }, 'Client disconnected from traceroute stream, killing process.'); if (!proc.killed) proc.kill(); if (!res.writableEnded) res.end(); + transaction.setStatus('cancelled'); // Client hat abgebrochen + transaction.finish(); }); } catch (error) { logger.error({ requestIp, targetIp, error: error.message, stack: error.stack }, 'Error setting up traceroute stream'); + Sentry.captureException(error, { extra: { requestIp, targetIp } }); + transaction.setStatus('internal_error'); + transaction.finish(); if (!res.headersSent) { res.status(500).json({ success: false, error: `Failed to initiate traceroute: ${error.message}` }); } else { @@ -527,7 +619,7 @@ app.get('/api/traceroute', (req, res) => { // Lookup Endpunkt für beliebige IP (GeoIP, ASN, rDNS) -app.get('/api/lookup', async (req, res) => { +app.get('/api/lookup', async (req, res, next) => { // next hinzugefügt const targetIpRaw = req.query.targetIp; const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; const requestIp = req.ip || req.socket.remoteAddress; @@ -557,6 +649,7 @@ app.get('/api/lookup', async (req, res) => { logger.debug({ targetIp, geo }, 'GeoIP lookup successful for lookup'); } catch (e) { logger.warn({ targetIp, error: e.message }, `MaxMind City lookup failed for lookup`); + // Optional: Sentry.captureException(e, { level: 'warning', extra: { targetIp } }); geo = { error: 'GeoIP lookup failed (IP not found in database or private range).' }; } @@ -568,6 +661,7 @@ app.get('/api/lookup', async (req, res) => { logger.debug({ targetIp, asn }, 'ASN lookup successful for lookup'); } catch (e) { logger.warn({ targetIp, error: e.message }, `MaxMind ASN lookup failed for lookup`); + // Optional: Sentry.captureException(e, { level: 'warning', extra: { targetIp } }); asn = { error: 'ASN lookup failed (IP not found in database or private range).' }; } @@ -577,8 +671,12 @@ app.get('/api/lookup', async (req, res) => { rdns = hostnames; logger.debug({ targetIp, rdns }, 'rDNS lookup successful for lookup'); } catch (e) { - if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') logger.warn({ targetIp, error: e.message, code: e.code }, `rDNS lookup error for lookup`); - else logger.debug({ targetIp, code: e.code }, 'rDNS lookup failed (No record) for lookup'); + if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') { + logger.warn({ targetIp, error: e.message, code: e.code }, `rDNS lookup error for lookup`); + // Optional: Sentry.captureException(e, { level: 'warning', extra: { targetIp } }); + } else { + logger.debug({ targetIp, code: e.code }, 'rDNS lookup failed (No record) for lookup'); + } rdns = { error: `rDNS lookup failed (${e.code || 'Unknown error'})` }; } @@ -591,14 +689,15 @@ app.get('/api/lookup', async (req, res) => { } catch (error) { logger.error({ targetIp, error: error.message, stack: error.stack }, 'Error processing lookup'); - res.status(500).json({ error: 'Internal server error while processing lookup.' }); + Sentry.captureException(error, { extra: { targetIp, requestIp } }); // An Sentry senden + next(error); // An Sentry Error Handler weiterleiten } }); // --- NEUE ENDPUNKTE --- // DNS Lookup Endpunkt -app.get('/api/dns-lookup', async (req, res) => { +app.get('/api/dns-lookup', async (req, res, next) => { // next hinzugefügt const domainRaw = req.query.domain; const domain = typeof domainRaw === 'string' ? domainRaw.trim() : domainRaw; const typeRaw = req.query.type; @@ -621,15 +720,29 @@ app.get('/api/dns-lookup', async (req, res) => { try { let records; if (type === 'ANY') { + // Führe Lookups parallel aus, fange Fehler einzeln ab const promises = [ dns.resolve(domain, 'A').catch(() => []), dns.resolve(domain, 'AAAA').catch(() => []), dns.resolve(domain, 'MX').catch(() => []), dns.resolve(domain, 'TXT').catch(() => []), dns.resolve(domain, 'NS').catch(() => []), dns.resolve(domain, 'CNAME').catch(() => []), dns.resolve(domain, 'SOA').catch(() => []), ]; - const results = await Promise.all(promises); - records = { A: results[0], AAAA: results[1], MX: results[2], TXT: results[3], NS: results[4], CNAME: results[5], SOA: results[6] }; - records = Object.fromEntries(Object.entries(records).filter(([_, v]) => Array.isArray(v) ? v.length > 0 : v)); + // Warte auf alle Promises, auch wenn einige fehlschlagen + const results = await Promise.allSettled(promises); + + // Verarbeite die Ergebnisse + records = { + A: results[0].status === 'fulfilled' ? results[0].value : { error: results[0].reason?.message || 'Lookup failed' }, + AAAA: results[1].status === 'fulfilled' ? results[1].value : { error: results[1].reason?.message || 'Lookup failed' }, + MX: results[2].status === 'fulfilled' ? results[2].value : { error: results[2].reason?.message || 'Lookup failed' }, + TXT: results[3].status === 'fulfilled' ? results[3].value : { error: results[3].reason?.message || 'Lookup failed' }, + NS: results[4].status === 'fulfilled' ? results[4].value : { error: results[4].reason?.message || 'Lookup failed' }, + CNAME: results[5].status === 'fulfilled' ? results[5].value : { error: results[5].reason?.message || 'Lookup failed' }, + SOA: results[6].status === 'fulfilled' ? results[6].value : { error: results[6].reason?.message || 'Lookup failed' }, + }; + // Entferne leere Arrays oder Fehlerobjekte, wenn keine Daten vorhanden sind + records = Object.fromEntries(Object.entries(records).filter(([_, v]) => (Array.isArray(v) && v.length > 0) || (typeof v === 'object' && v !== null && !Array.isArray(v) && !v.error))); + } else { records = await dns.resolve(domain, type); } @@ -638,13 +751,17 @@ app.get('/api/dns-lookup', async (req, res) => { res.json({ success: true, domain, type, records }); } catch (error) { + // Dieser Catch-Block wird nur für den spezifischen Typ-Lookup oder bei Fehlern in Promise.allSettled erreicht logger.error({ requestIp, domain, type, error: error.message, code: error.code }, 'DNS lookup failed'); + Sentry.captureException(error, { extra: { requestIp, domain, type } }); // An Sentry senden + // Sende 500, aber mit Fehlerdetails im Body res.status(500).json({ success: false, error: `DNS lookup failed: ${error.message} (Code: ${error.code})` }); + // next(error); // Optional: Fehler auch an Sentry Error Handler weiterleiten } }); // WHOIS Lookup Endpunkt -app.get('/api/whois-lookup', async (req, res) => { +app.get('/api/whois-lookup', async (req, res, next) => { // next hinzugefügt const queryRaw = req.query.query; const query = typeof queryRaw === 'string' ? queryRaw.trim() : queryRaw; const requestIp = req.ip || req.socket.remoteAddress; @@ -657,16 +774,19 @@ app.get('/api/whois-lookup', async (req, res) => { } try { - const result = await whois(query, { timeout: 10000 }); + const result = await whois(query, { timeout: 10000 }); // Timeout hinzugefügt logger.info({ requestIp, query }, 'WHOIS lookup successful'); res.json({ success: true, query, result }); } catch (error) { logger.error({ requestIp, query, error: error.message }, 'WHOIS lookup failed'); + Sentry.captureException(error, { extra: { requestIp, query } }); // An Sentry senden let errorMessage = error.message; if (error.message.includes('ETIMEDOUT') || error.message.includes('ESOCKETTIMEDOUT')) errorMessage = 'WHOIS server timed out.'; else if (error.message.includes('ENOTFOUND')) errorMessage = 'Domain or IP not found or WHOIS server unavailable.'; + // Sende 500, aber mit Fehlerdetails im Body res.status(500).json({ success: false, error: `WHOIS lookup failed: ${errorMessage}` }); + // next(error); // Optional: Fehler auch an Sentry Error Handler weiterleiten } }); @@ -680,9 +800,35 @@ app.get('/api/version', (req, res) => { }); +// --- Sentry Error Handler (NACH ALLEN ROUTEN, VOR ANDEREN ERROR HANDLERN) --- +// Wichtig: Der Error Handler muss 4 Argumente haben, damit Express ihn als Error Handler erkennt. +app.use(Sentry.Handlers.errorHandler({ + shouldHandleError(error) { + // Hier können Sie entscheiden, ob ein Fehler an Sentry gesendet werden soll + // z.B. keine 404-Fehler senden + if (error.status === 404) { + return false; + } + return true; + }, +})); +// --- Ende Sentry Error Handler --- + +// Optional: Ein generischer Fallback-Error-Handler nach Sentry +app.use((err, req, res, next) => { + // Dieser Handler wird nur aufgerufen, wenn Sentry den Fehler nicht behandelt hat + // oder wenn Sie `next(err)` im Sentry-Handler aufrufen. + logger.error({ error: err.message, stack: err.stack, url: req.originalUrl }, 'Unhandled error caught by fallback handler'); + res.statusCode = err.status || 500; + res.end(res.sentry + "\n" + (err.message || 'Internal Server Error') + "\n"); // res.sentry wird vom Sentry Handler gesetzt +}); + + // --- Server starten --- +let server; // Variable für den HTTP-Server + initialize().then(() => { - app.listen(PORT, () => { + server = app.listen(PORT, () => { // Server-Instanz speichern logger.info({ port: PORT, node_env: process.env.NODE_ENV || 'development' }, `Server listening`); logger.info(`API endpoints available at:`); logger.info(` http://localhost:${PORT}/api/ipinfo`); @@ -696,14 +842,45 @@ initialize().then(() => { }); }).catch(error => { logger.fatal("Server could not start due to initialization errors."); + Sentry.captureException(error); // Fehler beim Starten an Sentry senden process.exit(1); }); // Graceful Shutdown Handling const signals = { 'SIGINT': 2, 'SIGTERM': 15 }; -Object.keys(signals).forEach((signal) => { - process.on(signal, () => { + +async function gracefulShutdown(signal) { logger.info(`Received ${signal}, shutting down gracefully...`); - process.exit(128 + signals[signal]); - }); + if (server) { + server.close(() => { + logger.info('HTTP server closed.'); + // Sentry schließen, um sicherzustellen, dass alle Events gesendet werden + Sentry.close(2000).then(() => { // Timeout von 2 Sekunden + logger.info('Sentry closed.'); + process.exit(128 + signals[signal]); + }).catch(e => { + logger.error({ error: e.message }, 'Error closing Sentry'); + process.exit(1); // Trotzdem beenden + }); + }); + } else { + // Wenn der Server nie gestartet ist, Sentry trotzdem schließen + Sentry.close(2000).then(() => { + logger.info('Sentry closed (server never started).'); + process.exit(128 + signals[signal]); + }).catch(e => { + logger.error({ error: e.message }, 'Error closing Sentry (server never started)'); + process.exit(1); + }); + } + + // Fallback-Timeout, falls das Schließen hängt + setTimeout(() => { + logger.warn('Graceful shutdown timed out, forcing exit.'); + process.exit(1); + }, 5000); // 5 Sekunden Timeout +} + +Object.keys(signals).forEach((signal) => { + process.on(signal, () => gracefulShutdown(signal)); }); \ No newline at end of file diff --git a/compose.yml b/compose.yml index 2c44431..52c5bf8 100644 --- a/compose.yml +++ b/compose.yml @@ -7,6 +7,8 @@ services: # Übergibt den Git Commit Hash als Build-Argument. # Erwartet, dass GIT_COMMIT_SHA in der Shell-Umgebung gesetzt ist (z.B. export GIT_COMMIT_SHA=$(git rev-parse --short HEAD)) - GIT_COMMIT_SHA=${GIT_COMMIT_SHA:-unknown} + # Übergibt den Sentry DSN als Build-Argument (optional, falls im Code benötigt) + - SENTRY_DSN=https://7ea70caba68f548fb96482a573006a7b@o447623.ingest.us.sentry.io/4509062020333568 container_name: utools_backend # Eindeutiger Name für den Container restart: unless-stopped environment: @@ -19,6 +21,8 @@ services: # könnten hier aber überschrieben werden, falls nötig. # GEOIP_CITY_DB: ./data/GeoLite2-City.mmdb # GEOIP_ASN_DB: ./data/GeoLite2-ASN.mmdb + # Sentry DSN aus der Umgebung/ .env Datei übernehmen + SENTRY_DSN: ${SENTRY_DSN:-} # Wichtig für die Laufzeit dns: - 1.1.1.1 # Cloudflare DNS - 1.0.0.1 # Cloudflare DNS