Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 31x 31x 31x 31x 12x 12x 12x 5x 5x 5x 7x 1x 6x 6x 6x 6x 6x 2x 1x 1x 2x 2x 2x 12x 12x 2x 1x 1x 2x 10x 10x 10x 1x 12x 12x 7x 5x 5x 1x 12x 12x 12x 12x 12x 12x 1x | import type { ContentfulStatusCode } from "hono/utils/http-status";
import { notifyDeveloperOfError } from "./notifyDeveloper";
import type { Env } from "../../config/env";
import { getDb } from "../../prisma";
interface ErrorLogEntry {
message: string;
status?: number | ContentfulStatusCode;
context?: Record<string, unknown>;
stack?: string;
}
let missingDatabaseWarningShown = false;
let logtailClient: {
error: (message: string, meta?: Record<string, unknown>) => Promise<unknown>;
} | null = null;
let logtailToken: string | null = null;
let logtailInitialisationWarned = false;
async function getLogtail(env: Env) {
const token = env.LOGTAIL_SOURCE_TOKEN;
const endpoint = env.LOGTAIL_ENDPOINT;
if (!token || !endpoint) {
logtailClient = null;
logtailToken = null;
return null;
}
if (logtailClient && logtailToken === token) {
return logtailClient;
}
try {
const { Logtail } = await import("@logtail/node");
logtailClient = new Logtail(token, {
endpoint,
});
logtailToken = token;
return logtailClient;
} catch (error) {
if (!logtailInitialisationWarned) {
console.error("Failed to initialise Logtail client", error);
logtailInitialisationWarned = true;
}
logtailClient = null;
logtailToken = null;
return null;
}
}
async function persistError(env: Env, entry: ErrorLogEntry, timestamp: Date) {
const databaseUrl = env.DATABASE_URL;
if (!databaseUrl) {
if (!missingDatabaseWarningShown) {
console.warn(
"Missing DATABASE_URL configuration; skipping persisted error logs."
);
missingDatabaseWarningShown = true;
}
return;
}
try {
const db = getDb(databaseUrl);
await db.errorLog.create({
data: {
process: env.ERROR_LOG_PROCESS_NAME ?? "kick-bot",
message: entry.message,
stackTrace: entry.stack ?? "Stack trace unavailable",
timestamp,
},
});
} catch (error) {
console.error("Failed to persist error log", error);
}
}
async function forwardToLogtail(
env: Env,
entry: ErrorLogEntry,
timestamp: Date
) {
const client = await getLogtail(env);
if (!client) {
return;
}
try {
await client.error(entry.message, {
status: typeof entry.status === "number" ? entry.status : null,
process: env.ERROR_LOG_PROCESS_NAME ?? "kick-bot",
timestamp: timestamp.toISOString(),
context: entry.context ?? null,
});
} catch (error) {
console.error("Failed to send error to Logtail", error);
}
}
export async function recordError(
env: Env,
entry: ErrorLogEntry
): Promise<void> {
const timestamp = new Date();
const isoTimestamp = timestamp.toISOString();
await persistError(env, entry, timestamp);
await forwardToLogtail(env, entry, timestamp);
try {
await notifyDeveloperOfError(env, entry, isoTimestamp);
} catch (error) {
console.error("Failed to send developer notification", error);
}
}
export type { ErrorLogEntry };
|