diff --git a/package.json b/package.json index 7f638cc..1550099 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lint:check": "eslint . --ext .ts", "build:check": "tsc -p tsconfig.json", "build": "rimraf dist && node build.js", - "start": "node ./dist/index.js" + "start": "node ./dist/loader.js" }, "devDependencies": { "@sentry/cli": "^2.31.2", diff --git a/src/core/DiscordClient.ts b/src/core/DiscordClient.ts index 7f9f016..e2accb6 100644 --- a/src/core/DiscordClient.ts +++ b/src/core/DiscordClient.ts @@ -1,5 +1,4 @@ -import { ActivityType, Client, GatewayIntentBits, Partials, DiscordAPIError, DefaultWebSocketManagerOptions } from "discord.js"; -import { APIErrors } from "../utils/discordErrorCode"; +import { ActivityType, Client, GatewayIntentBits, Partials, DefaultWebSocketManagerOptions } from "discord.js"; import config from '../config.json'; import { PrismaClient } from "@prisma/client"; import Redis from "ioredis"; @@ -7,22 +6,13 @@ import { connect, Connection } from "amqplib"; import { eventLogger } from "./helper/eventLogger"; import { DiscordEvents, RedisEvents, AMQPEvents } from "../events"; -import { init as sentryInit, flush, extraErrorDataIntegration, rewriteFramesIntegration, expressIntegration, prismaIntegration } from "@sentry/node"; -import { Prisma } from "@prisma/client"; -import path from "path"; import { ReactRoleLoader } from "../services/ReactRoleHandler"; import { baseClient } from "./baseClient"; import { DiscordCommandHandler } from "../events/helper/DiscordCommandHandler"; import { TwitchClient } from "./TwitchClient"; import { YoutubeClient } from "./YoutubeClient"; -import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { PrismaInstrumentation } from '@prisma/instrumentation'; -import { Resource } from '@opentelemetry/resources'; + /** @@ -87,12 +77,6 @@ export class DiscordClient extends Client implements baseClient { new RedisEvents(this) .registerEvents(); - // Enable Telemetry Systems - if(process.env["SENTRY_DSN"]) { - this.configureOpenTelemetry(); - this.initSentry(); - } - // Initialize Twitch Client if(!process.env["TWITCH_TOKEN"] || !process.env["TWITCH_USERNAME"]) throw new Error("No twitch username/token provided"); @@ -137,13 +121,12 @@ export class DiscordClient extends Client implements baseClient { public async dispose() { // Close all connections await this.prisma.$disconnect(); - await this.redis.disconnect(); + await this.redis.quit(); if(this.amqp) { this.events.amqp.noAutoReconnect = true; await this.amqp.close(); } await this.destroy(); - await flush(); } public updateStatus() { @@ -164,89 +147,5 @@ export class DiscordClient extends Client implements baseClient { private async loadServices() { await ReactRoleLoader(this); } - - - /* Telemetry Systems Below */ - - private initSentry() { - sentryInit({ - dsn: process.env["SENTRY_DSN"], - maxValueLength: 1000, - tracesSampleRate: 0.1, - - integrations: [ - extraErrorDataIntegration({ - depth: 5 - }), - rewriteFramesIntegration({ - iteratee: (frame) => { - const absPath = frame.filename; - if(!absPath) return frame; - // Set the base path as the dist output to match the naming artifact on sentry - frame.filename = `/${path.relative(__dirname, absPath).replace(/\\/g, "/")}`; - return frame; - } - }), - prismaIntegration(), - expressIntegration(), - ], - - beforeBreadcrumb: (breadcrumb) => { - // List of urls to ignore - const ignoreUrl = [ - "https://api.twitch.tv", - "https://discord.com", - "https://cdn.discordapp.com" - ]; - - // Ignore Http Breadcrumbs from the blacklisted url - if(breadcrumb.category === "http" && - ignoreUrl.filter(url=>breadcrumb.data?.url.startsWith(url)).length > 0) return null; - return breadcrumb; - }, - - ignoreErrors: [ - "ETIMEDOUT", - "EADDRINUSE", - "ENOTFOUND", - "TimeoutError", - "AbortError", - "NetworkError", - "ECONNREFUSED", - "ECONNRESET", - ], - - beforeSend : (evnt, hint) => { - const ex = hint.originalException; - if(ex instanceof DiscordAPIError && ex.code === APIErrors.UNKNOWN_INTERACTION) return null; - // Somehow prisma bugged and threw this error :/ - if(ex instanceof Prisma.PrismaClientKnownRequestError && ex.code === "P1017") return null; - return evnt; - }, - - release: process.env['COMMITHASH'], - environment: process.env["ENVIRONMENT"] - }); - } - - private configureOpenTelemetry() { - // Default Prisma Configuration - - const provider = new NodeTracerProvider({ - resource: new Resource({ - [SEMRESATTRS_SERVICE_NAME]: 'example application', - }), - }); - - provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter())); - - registerInstrumentations({ - tracerProvider: provider, - instrumentations: [new PrismaInstrumentation()], - }); - - // Register the provider globally - provider.register(); - } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 654c362..d0ad947 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ import { DiscordClient } from "./core/DiscordClient"; -import { existsSync, readFileSync, writeFileSync } from "fs"; -import {config as dotenv} from "dotenv"; +import { writeFileSync } from "fs"; +import { flush } from "@sentry/node"; /** * .env persistance setup for docker */ -export function saveEnv() { +function saveEnv() { const envToWrite = process.env["WRITE_ENV"]; if(envToWrite) { const envs = envToWrite.replaceAll(' ', '').split(","); @@ -18,7 +18,6 @@ export function saveEnv() { } } -dotenv(); if(process.env['ISDOCKER']) saveEnv(); @@ -29,14 +28,6 @@ if(process.env['ISDOCKER']) if(!process.env["BOTTOKEN"]) throw new Error("No token provided"); -if(!process.env["COMMITHASH"]) { - // Try to load the commit hash via file - if(existsSync("commitHash")) - process.env["COMMITHASH"] = readFileSync("commitHash").toString(); - else - console.warn("No commit hash found!"); -} - /** * Setup our beloved client stuff and start it */ @@ -52,10 +43,10 @@ async function quitSignalHandler() { await CoreClient.dispose(); await CoreClient.twitch.dispose(); await CoreClient.youtube.dispose(); + await flush(15000); process.exit(0); } process.on("SIGINT", quitSignalHandler); process.on("SIGTERM", quitSignalHandler); -process.on("SIGQUIT", quitSignalHandler); - \ No newline at end of file +process.on("SIGQUIT", quitSignalHandler); \ No newline at end of file diff --git a/src/loader.ts b/src/loader.ts new file mode 100644 index 0000000..1ba4b1e --- /dev/null +++ b/src/loader.ts @@ -0,0 +1,110 @@ +/* Software Loader */ + + +// Load Env Variable +import {config as dotenv} from "dotenv"; +dotenv(); + + +// Load Commit Hash +import {existsSync, readFileSync} from "fs"; +if(!process.env["COMMITHASH"]) { + // Try to load the commit hash via file + if(existsSync("commitHash")) + process.env["COMMITHASH"] = readFileSync("commitHash").toString(); + else + console.warn("No commit hash found!"); +} + + +// Run Sentry first as required by the docs +import { expressIntegration, extraErrorDataIntegration, prismaIntegration, rewriteFramesIntegration, init as sentryInit } from "@sentry/node"; +import { DiscordAPIError } from "discord.js"; +import { relative } from "path"; +import { APIErrors } from "./utils/discordErrorCode"; +import { Prisma } from "@prisma/client"; + +sentryInit({ + dsn: process.env["SENTRY_DSN"], + maxValueLength: 1000, + tracesSampleRate: 0.1, + + integrations: [ + extraErrorDataIntegration({ + depth: 5 + }), + rewriteFramesIntegration({ + iteratee: (frame) => { + const absPath = frame.filename; + if(!absPath) return frame; + // Set the base path as the dist output to match the naming artifact on sentry + frame.filename = `/${relative(__dirname, absPath).replace(/\\/g, "/")}`; + return frame; + } + }), + prismaIntegration(), + expressIntegration(), + ], + + beforeBreadcrumb: (breadcrumb) => { + // List of urls to ignore + const ignoreUrl = [ + "https://api.twitch.tv", + "https://discord.com", + "https://cdn.discordapp.com" + ]; + + // Ignore Http Breadcrumbs from the blacklisted url + if(breadcrumb.category === "http" && + ignoreUrl.filter(url=>breadcrumb.data?.url.startsWith(url)).length > 0) return null; + return breadcrumb; + }, + + ignoreErrors: [ + "ETIMEDOUT", + "EADDRINUSE", + "ENOTFOUND", + "TimeoutError", + "AbortError", + "NetworkError", + "ECONNREFUSED", + "ECONNRESET", + ], + + beforeSend : (evnt, hint) => { + const ex = hint.originalException; + if(ex instanceof DiscordAPIError && ex.code === APIErrors.UNKNOWN_INTERACTION) return null; + // Somehow prisma bugged and threw this error :/ + if(ex instanceof Prisma.PrismaClientKnownRequestError && ex.code === "P1017") return null; + return evnt; + }, + + release: process.env['COMMITHASH'], + environment: process.env["ENVIRONMENT"] +}); + + +// Load OpenTelemetry Config +import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { PrismaInstrumentation } from '@prisma/instrumentation'; +import { Resource } from '@opentelemetry/resources'; + +const provider = new NodeTracerProvider({ + resource: new Resource({ + [SEMRESATTRS_SERVICE_NAME]: 'example application', + }), +}); +provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter())); + +registerInstrumentations({ + tracerProvider: provider, + instrumentations: [new PrismaInstrumentation()], +}); +provider.register(); + +// Start the main software +import('./index'); \ No newline at end of file