Add production observability: Sentry, pino, and sharp badge encoding.
Local Build and Deploy / deploy (push) Successful in 1m9s
Local Build and Deploy / deploy (push) Successful in 1m9s
Sentry is opt-in via DSN env vars; logger uses structured pino JSON in prod; badge uploads are normalized to GIF with sharp. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
6c81af69ea
commit
09f1bc2bd6
16 files changed
+2206
-121
No files matched your search
+42
-17
@@ -1,5 +1,6 @@
|
||||
import { Cron } from "croner";
|
||||
import { env } from "../src/env";
|
||||
import { logger } from "../src/lib/logger";
|
||||
import { prisma } from "../src/lib/prisma";
|
||||
|
||||
async function backupEmulatorJar(): Promise<void> {
|
||||
@@ -21,9 +22,11 @@ async function backupEmulatorJar(): Promise<void> {
|
||||
|
||||
try {
|
||||
copyFileSync(env.EMULATOR_JAR_PATH, backupFile);
|
||||
console.log(`[jobs] Backed up emulator JAR to ${backupFile}`);
|
||||
logger.info("Backed up emulator JAR", {
|
||||
module: "jobs",
|
||||
backupFile,
|
||||
});
|
||||
|
||||
// Rotate: keep only the N newest
|
||||
const keep = env.EMULATOR_BACKUP_KEEP ?? 7;
|
||||
const files = readdirSync(env.EMULATOR_BACKUP_DIR)
|
||||
.filter((f) => f.startsWith("emulator-") && f.endsWith(".jar"))
|
||||
@@ -32,10 +35,16 @@ async function backupEmulatorJar(): Promise<void> {
|
||||
|
||||
for (let i = keep; i < files.length; i++) {
|
||||
unlinkSync(resolve(env.EMULATOR_BACKUP_DIR, files[i]));
|
||||
console.log(`[jobs] Rotated out old backup: ${files[i]}`);
|
||||
logger.info("Rotated out old backup", {
|
||||
module: "jobs",
|
||||
file: files[i],
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[jobs] JAR backup failed:", err);
|
||||
logger.error("JAR backup failed", {
|
||||
module: "jobs",
|
||||
err: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +54,12 @@ async function cleanupOldLogs(): Promise<void> {
|
||||
await prisma.websiteLoginLogs.deleteMany({
|
||||
where: { createdAt: { lt: cutoff } },
|
||||
});
|
||||
console.log("[jobs] Cleaned up login logs older than 30 days");
|
||||
logger.info("Cleaned up login logs older than 30 days", { module: "jobs" });
|
||||
} catch (err) {
|
||||
console.error("[jobs] Log cleanup failed:", err);
|
||||
logger.error("Log cleanup failed", {
|
||||
module: "jobs",
|
||||
err: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,34 +69,44 @@ async function cleanupOldSessions(): Promise<void> {
|
||||
await prisma.passwordReset.deleteMany({
|
||||
where: { createdAt: { lt: cutoff } },
|
||||
});
|
||||
console.log("[jobs] Cleaned up expired password reset tokens");
|
||||
logger.info("Cleaned up expired password reset tokens", {
|
||||
module: "jobs",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[jobs] Session cleanup failed:", err);
|
||||
logger.error("Session cleanup failed", {
|
||||
module: "jobs",
|
||||
err: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("[jobs] Worker started");
|
||||
logger.info("Worker started", { module: "jobs" });
|
||||
|
||||
// JAR backup — daily at 03:00
|
||||
if (env.EMULATOR_JAR_PATH && env.EMULATOR_BACKUP_DIR) {
|
||||
new Cron("0 3 * * *", () => {
|
||||
backupEmulatorJar().catch((e) =>
|
||||
console.error("[jobs] Backup error:", e),
|
||||
logger.error("Backup error", {
|
||||
module: "jobs",
|
||||
err: e instanceof Error ? e.message : String(e),
|
||||
}),
|
||||
);
|
||||
});
|
||||
console.log("[jobs] Scheduled: emulator JAR backup (daily 03:00)");
|
||||
logger.info("Scheduled: emulator JAR backup (daily 03:00)", {
|
||||
module: "jobs",
|
||||
});
|
||||
}
|
||||
|
||||
// Log cleanup — daily at 04:00
|
||||
new Cron("0 4 * * *", () => {
|
||||
Promise.all([cleanupOldLogs(), cleanupOldSessions()]).catch((e) =>
|
||||
console.error("[jobs] Cleanup error:", e),
|
||||
logger.error("Cleanup error", {
|
||||
module: "jobs",
|
||||
err: e instanceof Error ? e.message : String(e),
|
||||
}),
|
||||
);
|
||||
});
|
||||
console.log("[jobs] Scheduled: old data cleanup (daily 04:00)");
|
||||
logger.info("Scheduled: old data cleanup (daily 04:00)", { module: "jobs" });
|
||||
|
||||
// Run once on startup
|
||||
await Promise.all([
|
||||
backupEmulatorJar(),
|
||||
cleanupOldLogs(),
|
||||
@@ -93,6 +115,9 @@ async function main() {
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("[jobs] Fatal:", err);
|
||||
logger.error("Fatal", {
|
||||
module: "jobs",
|
||||
err: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user