Skip to content

Commit

Permalink
Merge pull request #529 from gowon-bot/supernova-support
Browse files Browse the repository at this point in the history
Report errors to Supernova
  • Loading branch information
abbyfour authored Nov 7, 2024
2 parents 22a9699 + a1cc2af commit d1f4bf4
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 19 deletions.
5 changes: 4 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@
"lilacWebsocket": "ws://localhost:4000/socket",
"lilacPassword": "this needs to match the 'password' config in Lilac",

"redisHost": "localhost"
"redisHost": "localhost",

"supernovaURL": "http://localhost:8082/",
"supernovaPassword": "this needs to match the 'password' config in Supernova"
}
23 changes: 23 additions & 0 deletions src/errors/lilac.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { ApolloError, ServerError } from "@apollo/client";
import { ErrorWithSupernovaID } from "../services/analytics/ReportingService";
import { ClientError } from "./errors";

export function parseLilacError(error: Error): Error {
if (error.message === "User is already being indexed or updated!") {
return new AlreadyBeingUpdatedOrIndexedError();
} else if (
error instanceof ApolloError &&
isServerError(error.networkError)
) {
if (error.networkError.result.supernova_id) {
return new LilacError(
error.message,
error.networkError.result.supernova_id
);
}
}

return error;
Expand All @@ -15,3 +27,14 @@ export class AlreadyBeingUpdatedOrIndexedError extends ClientError {
);
}
}

export class LilacError extends Error implements ErrorWithSupernovaID {
constructor(message: string, public readonly supernovaID: string) {
super(message);
this.name = "LilacError";
}
}

export function isServerError(error: any | null): error is ServerError {
return !!(error as ServerError)?.response;
}
4 changes: 0 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { GowonContext } from "./lib/context/Context";
import { Payload } from "./lib/context/Payload";
import { displayUserTag } from "./lib/ui/displays";
import { MockMessage } from "./mocks/discord";
import { ReportingService } from "./services/analytics/ReportingService";
import { UsersService } from "./services/dbservices/UsersService";
import {
client,
Expand All @@ -38,9 +37,6 @@ async function start() {

const analyticsCollector = ServiceRegistry.get(AnalyticsCollector);
const usersService = ServiceRegistry.get(UsersService);
const reportingService = ServiceRegistry.get(ReportingService);

reportingService.init();

client.client.on("ready", () => {
console.log(
Expand Down
19 changes: 11 additions & 8 deletions src/lib/command/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { GowonService } from "../../services/GowonService";
import { NowPlayingEmbedParsingService } from "../../services/NowPlayingEmbedParsingService";
import { ServiceRegistry } from "../../services/ServicesRegistry";
import { TrackingService } from "../../services/TrackingService";
import { ReportingService } from "../../services/analytics/ReportingService";
import { ErrorReportingService } from "../../services/analytics/ReportingService";
import { ArgumentParsingService } from "../../services/arguments/ArgumentsParsingService";
import { MentionsService } from "../../services/arguments/mentions/MentionsService";
import {
Expand Down Expand Up @@ -204,7 +204,7 @@ export abstract class Command<ArgumentsType extends ArgumentsMap = {}> {
discordService = ServiceRegistry.get(DiscordService);
settingsService = ServiceRegistry.get(SettingsService);
mentionsService = ServiceRegistry.get(MentionsService);
reportingService = ServiceRegistry.get(ReportingService);
reportingService = ServiceRegistry.get(ErrorReportingService);
mirrorballService = ServiceRegistry.get(MirrorballService);
lilacUsersService = ServiceRegistry.get(LilacUsersService);
lilacGuildsService = ServiceRegistry.get(LilacGuildsService);
Expand Down Expand Up @@ -347,14 +347,12 @@ export abstract class Command<ArgumentsType extends ArgumentsMap = {}> {
protected async handleRunError(e: any) {
this.logger.logError(e);
this.analyticsCollector.metrics.commandErrors.inc();
this.reportingService.reportError(this.ctx, e);

console.log(e);
const errorID = await this.reportingService.reportError(this.ctx, e);

if (e.isClientFacing && !e.silent) {
await this.sendError(e);
} else if (!e.isClientFacing) {
await this.sendError(new UnknownError());
await this.sendError(new UnknownError(), errorID);
}
}

Expand Down Expand Up @@ -462,11 +460,16 @@ export abstract class Command<ArgumentsType extends ArgumentsMap = {}> {
.filter(filter);
}

protected async sendError(error: Error | string): Promise<void> {
protected async sendError(
error: Error | string,
errorID?: string
): Promise<void> {
const errorInstance =
typeof error === "string" ? new ClientError(error) : error;

const embed = new ErrorEmbed().setError(errorInstance);
const embed = new ErrorEmbed()
.setError(errorInstance)
.setErrorCode(errorID);

await this.reply(embed);
}
Expand Down
1 change: 0 additions & 1 deletion src/lib/ui/embeds/AquariumEmbed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ There be **${displayNumber(this.aquarium.size)} total fishy** in your aquarium.
"Your fishy are hard at work doing something",
"The fishy have become aware they are in a Discord bot. your time is limited.",
"Your fishy have hacked the mainframe",
"Your fishy are holding a prayer circle to get Leoni a legendary",
"Your fishy are having a heated debate about food... again",
`The fishy are starting a multilevel marketing scheme ${emDash} want to buy a pyramidfish?`,
"The fishy are pondering the existence of the 'other side' of the glass.",
Expand Down
8 changes: 8 additions & 0 deletions src/lib/ui/embeds/ErrorEmbed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const errorColour = "#F1759A";

export class ErrorEmbed extends EmbedView {
error?: Error;
errorCode?: string;

asDiscordSendable(): EmbedView {
const footer = this.error instanceof ClientError ? this.error.footer : "";
Expand All @@ -21,6 +22,8 @@ export class ErrorEmbed extends EmbedView {

if (footer) {
embed.setFooter(footer);
} else if (this.errorCode) {
embed.setFooter(`Error ID: ${this.errorCode}`);
}

return isWarning ? embed.convert(WarningEmbed) : embed;
Expand All @@ -31,6 +34,11 @@ export class ErrorEmbed extends EmbedView {
return this;
}

setErrorCode(code: string | undefined): this {
this.errorCode = code;
return this;
}

protected description(): string | undefined {
if (this.error) {
return uppercaseFirstLetter(this.error.message);
Expand Down
4 changes: 2 additions & 2 deletions src/services/ServicesRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { SpotifyService } from "./Spotify/SpotifyService";
import { TimeAndDateService } from "./TimeAndDateService";
import { TrackingService } from "./TrackingService";
import { WordBlacklistService } from "./WordBlacklistService";
import { ReportingService } from "./analytics/ReportingService";
import { ErrorReportingService } from "./analytics/ReportingService";
import { ArgumentParsingService } from "./arguments/ArgumentsParsingService";
import { MentionsService } from "./arguments/mentions/MentionsService";
import { BotStatsService } from "./dbservices/BotStatsService";
Expand Down Expand Up @@ -108,7 +108,7 @@ const services: Service[] = [
RedirectsService,
RedisService,
RedisInteractionService,
ReportingService,
ErrorReportingService,
SettingsService,
SpotifyService,
SpotifyArguments,
Expand Down
99 changes: 96 additions & 3 deletions src/services/analytics/ReportingService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,101 @@
import { supernovaPassword, supernovaURL } from "../../../config.json";
import { ClientError, ClientWarning } from "../../errors/errors";
import { GowonContext } from "../../lib/context/Context";
import { BaseService } from "../BaseService";
import {
ErrorReportPayload,
GowonErrorSeverity,
} from "../supernova/supernovaTypes";

export class ReportingService extends BaseService {
async init() {}
export type ErrorWithSupernovaID = Error & {
supernovaID: string;
};

public reportError(_ctx: GowonContext, _e: Error): void {}
export class ErrorReportingService extends BaseService {
public async reportError(
ctx: GowonContext,
e: Error
): Promise<string | undefined> {
if (this.alreadyReportedToSupernova(e)) {
await this.fetch(
`${e.supernovaID}/modify`,
"POST",
this.generateModifyPayload(ctx)
);

return e.supernovaID;
}

try {
const response = await this.fetch(
"report",
"POST",
this.generatePayload(ctx, e)
);

const { error } = await response.json();

return error.id;
} catch {
return undefined;
}
}

private generatePayload(ctx: GowonContext, e: Error): ErrorReportPayload {
return {
kind: e.name,
message: e.message,
application: "Gowon",
userID: ctx.author.id,
severity: this.getSeverity(e),
stack: e.stack ?? "(no stack)",
tags: this.generateTags(ctx),
};
}

private generateModifyPayload(
ctx: GowonContext
): Partial<ErrorReportPayload> {
return {
userID: ctx.author.id,
tags: this.generateTags(ctx),
};
}

private generateTags(ctx: GowonContext): ErrorReportPayload["tags"] {
return [
{ key: "command", value: ctx.command.name },
{ key: "guild", value: ctx.guild?.id ?? "DM" },
{ key: "runas", value: ctx.extract.matched },
];
}

private getSeverity(e: Error): GowonErrorSeverity {
return e instanceof ClientWarning
? GowonErrorSeverity.WARNING
: e instanceof ClientError
? GowonErrorSeverity.EXCEPTION
: GowonErrorSeverity.ERROR;
}

private getURL(path: string): string {
return supernovaURL + "api/errors/" + path;
}

private async fetch(path: string, method: "GET" | "POST", body?: any) {
return fetch(this.getURL(path), {
method,
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization: `Password ${supernovaPassword}`,
},
});
}

private alreadyReportedToSupernova(
error: Error
): error is ErrorWithSupernovaID {
return "supernovaID" in error;
}
}
20 changes: 20 additions & 0 deletions src/services/supernova/supernovaTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface ErrorReportPayload {
message: string;
kind: string;
application: string;
userID: string;
severity: GowonErrorSeverity;
stack: string;
tags: ErrorReportTagPayload[];
}

export interface ErrorReportTagPayload {
key: string;
value: string;
}

export enum GowonErrorSeverity {
WARNING = "warning",
ERROR = "error",
EXCEPTION = "exception",
}

0 comments on commit d1f4bf4

Please sign in to comment.