From 74783fd7381d594869f9918f7e57ca3820beac52 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 24 Jun 2026 10:53:03 -0700 Subject: [PATCH] clean up logger --- server/src/api/middleware/handleErrors.ts | 4 +-- server/src/app.ts | 2 +- server/src/config/envValidation.ts | 28 +++++++++++++++---- server/src/db/db.mongo.ts | 2 +- .../0003_cleanupDuplicateMonitorStats.ts | 4 +-- .../0004_fixInfrastructureThresholds.ts | 4 +-- .../0005_migrateStatusPageTypeToArray.ts | 4 +-- ...anupDuplicateMonitorStatsForUniqueIndex.ts | 4 +-- ...igrateMaintenanceWindowMonitorIdToArray.ts | 4 +-- .../0008_backfillMonitorLastEvaluatedAt.ts | 4 +-- server/src/db/migration/index.ts | 16 +++++------ server/src/domain/monitors/monitor.model.ts | 2 +- server/src/index.ts | 7 +++-- server/src/shutdown.ts | 4 ++- server/src/utils/logger.ts | 19 +++++-------- 15 files changed, 61 insertions(+), 47 deletions(-) diff --git a/server/src/api/middleware/handleErrors.ts b/server/src/api/middleware/handleErrors.ts index 239349b90..77817f12e 100755 --- a/server/src/api/middleware/handleErrors.ts +++ b/server/src/api/middleware/handleErrors.ts @@ -1,12 +1,12 @@ import type { NextFunction, Request, Response } from "express"; import { MulterError } from "multer"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; import { AppError } from "@/utils/AppError.js"; // Multer throws MulterError, send 413 Payload Too Large / 400 const multerErrorStatus = (error: MulterError): number => (error.code === "LIMIT_FILE_SIZE" ? 413 : 400); -const handleErrors = (error: unknown, req: Request, res: Response, _next: NextFunction) => { +const handleErrors = (logger: ILogger) => (error: unknown, req: Request, res: Response, _next: NextFunction) => { const status = error instanceof MulterError ? multerErrorStatus(error) : error instanceof AppError ? error.status || 500 : 500; const message = error instanceof AppError ? error.message : error instanceof Error ? error.message : "Server error"; const service = error instanceof AppError ? error.service : error instanceof MulterError ? "uploadMiddleware" : "unknownService"; diff --git a/server/src/app.ts b/server/src/app.ts index d559edb1f..94a122137 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -112,6 +112,6 @@ export const createApp = ({ app.get("*", (req, res) => { res.sendFile(path.join(frontendPath, "index.html")); }); - app.use(handleErrors); + app.use(handleErrors(services.logger)); return app; }; diff --git a/server/src/config/envValidation.ts b/server/src/config/envValidation.ts index b65ef5165..bdfacf906 100644 --- a/server/src/config/envValidation.ts +++ b/server/src/config/envValidation.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { DbTypes, QueueModes } from "@/domain/app-settings/app-settings.type.js"; import { booleanCoercion } from "@/api/validation/shared.js"; +import { ILogger } from "@/utils/logger.js"; const envSchema = z.object({ // Server Configuration @@ -31,24 +32,39 @@ const envSchema = z.object({ export type ValidatedEnv = z.infer; -export const validateEnv = (): ValidatedEnv => { +export const validateEnv = (logger: ILogger): ValidatedEnv => { const result = envSchema.safeParse(process.env); if (!result.success) { - console.error("Environment validation failed:"); - console.error(""); + logger.error({ + message: "Env validation failed", + method: "validateEnv", + service: "Server", + }); const errors = result.error.format(); for (const [key, value] of Object.entries(errors)) { if (key !== "_errors" && value && typeof value === "object" && "_errors" in value) { - console.error(` ${key}: ${value._errors.join(", ")}`); + logger.error({ + message: `${key}: ${value._errors.join(", ")}`, + method: "validateEnv", + service: "Server", + }); } } - console.error("Please check your .env file and ensure all required variables are set."); + logger.error({ + message: "Please check your .env file and ensure all required variables are set.", + method: "validateEnv", + service: "Server", + }); process.exit(1); } - console.log("Environment variables validated successfully"); + logger.info({ + message: "Environment variables validated successfully", + method: "validateEnv", + service: "Server", + }); return result.data; }; diff --git a/server/src/db/db.mongo.ts b/server/src/db/db.mongo.ts index 2585eb58b..5aca5550a 100755 --- a/server/src/db/db.mongo.ts +++ b/server/src/db/db.mongo.ts @@ -42,7 +42,7 @@ class MongoDB implements IDb { // the unique index on MonitorStats.monitorId is enforced, otherwise databases // with stray duplicates (caused by the pre-fix race in updateRunningStats) // will crash at startup when the unique index fails to build. - await runMigrations(); + await runMigrations(this.logger); // Sync indexes const models = mongoose.modelNames(); diff --git a/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts b/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts index f772b5229..39a67d91e 100644 --- a/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts +++ b/server/src/db/migration/0003_cleanupDuplicateMonitorStats.ts @@ -1,12 +1,12 @@ import { MonitorStatsModel } from "../../domain/monitor-stats/monitor-stats.model.js"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; /** * Cleanup duplicate MonitorStats documents * Keeps the most recent document (by updatedAt) for each monitorId * and deletes all older duplicates */ -export async function cleanupDuplicateMonitorStats(): Promise { +export async function cleanupDuplicateMonitorStats(logger: ILogger): Promise { const SERVICE_NAME = "Migration:CleanupDuplicateMonitorStats"; try { diff --git a/server/src/db/migration/0004_fixInfrastructureThresholds.ts b/server/src/db/migration/0004_fixInfrastructureThresholds.ts index dfc45c4fe..c21d552a6 100644 --- a/server/src/db/migration/0004_fixInfrastructureThresholds.ts +++ b/server/src/db/migration/0004_fixInfrastructureThresholds.ts @@ -1,11 +1,11 @@ import { MonitorModel } from "../../domain/monitors/monitor.model.js"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; /** * Fix infrastructure monitors that had thresholds incorrectly set to 0 or 5 * due to the old default value. Updates them to 100 (disabled). */ -export async function fixInfrastructureThresholds(): Promise { +export async function fixInfrastructureThresholds(logger: ILogger): Promise { const SERVICE_NAME = "Migration:FixInfrastructureThresholds"; try { diff --git a/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts index d5dcafbd1..d75b069e0 100644 --- a/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts +++ b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts @@ -1,8 +1,8 @@ import mongoose from "mongoose"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; import StatusPageModel from "../../domain/status-pages/status-page.model.js"; -export async function migrateStatusPageTypeToArray(): Promise { +export async function migrateStatusPageTypeToArray(logger: ILogger): Promise { const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray"; try { diff --git a/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts b/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts index 4a34e9926..2479f83c6 100644 --- a/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts +++ b/server/src/db/migration/0006_cleanupDuplicateMonitorStatsForUniqueIndex.ts @@ -1,5 +1,5 @@ import { MonitorStatsModel } from "../../domain/monitor-stats/monitor-stats.model.js"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; /** * Second-pass dedupe of MonitorStats before the unique index on monitorId is enforced. @@ -11,7 +11,7 @@ import { logger } from "@/utils/logger.js"; * Keeps the document with the highest totalChecks for each monitorId (most data) and * deletes the rest. Falls back to most recent updatedAt when totalChecks tie. */ -export async function cleanupDuplicateMonitorStatsForUniqueIndex(): Promise { +export async function cleanupDuplicateMonitorStatsForUniqueIndex(logger: ILogger): Promise { const SERVICE_NAME = "Migration:CleanupDuplicateMonitorStatsForUniqueIndex"; try { diff --git a/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts b/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts index bf5766000..437c66704 100644 --- a/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts +++ b/server/src/db/migration/0007_migrateMaintenanceWindowMonitorIdToArray.ts @@ -1,6 +1,6 @@ import mongoose from "mongoose"; import type { AnyBulkWriteOperation, Document } from "mongodb"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; import MaintenanceWindowModel from "../../domain/maintenance-windows/maintenance-window.model.js"; type GroupResult = { @@ -18,7 +18,7 @@ type GroupResult = { monitorIds: mongoose.Types.ObjectId[]; }; -export async function migrateMaintenanceWindowMonitorIdToArray(): Promise { +export async function migrateMaintenanceWindowMonitorIdToArray(logger: ILogger): Promise { const SERVICE_NAME = "Migration:MigrateMaintenanceWindowMonitorIdToArray"; try { diff --git a/server/src/db/migration/0008_backfillMonitorLastEvaluatedAt.ts b/server/src/db/migration/0008_backfillMonitorLastEvaluatedAt.ts index deb7cb02e..148c4b74d 100644 --- a/server/src/db/migration/0008_backfillMonitorLastEvaluatedAt.ts +++ b/server/src/db/migration/0008_backfillMonitorLastEvaluatedAt.ts @@ -1,6 +1,6 @@ import mongoose from "mongoose"; import { MonitorModel } from "../../domain/monitors/monitor.model.js"; -import { logger } from "@/utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; const JOBS_COLLECTION = "jobs"; @@ -9,7 +9,7 @@ const JOBS_COLLECTION = "jobs"; * * Stamp the upgrade moment, not 0. If set to 0, all checks will be replayed */ -export async function backfillMonitorLastEvaluatedAt(): Promise { +export async function backfillMonitorLastEvaluatedAt(logger: ILogger): Promise { const SERVICE_NAME = "Migration:BackfillMonitorLastEvaluatedAt"; try { diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts index d68a2468e..0b0745d19 100644 --- a/server/src/db/migration/index.ts +++ b/server/src/db/migration/index.ts @@ -11,7 +11,7 @@ import type { ILogger } from "@/utils/logger.js"; type MigrationEntry = { name: string; - execute: () => Promise; + execute: (logger: ILogger) => Promise; }; const migrations: MigrationEntry[] = [ @@ -25,34 +25,34 @@ const migrations: MigrationEntry[] = [ { name: "0008_backfillMonitorLastEvaluatedAt", execute: backfillMonitorLastEvaluatedAt }, ]; -const runMigrations = async (logger?: ILogger) => { +const runMigrations = async (logger: ILogger) => { try { - logger?.info({ message: "Running migrations", service: "Migrations" }); + logger.info({ message: "Running migrations", service: "Migrations" }); for (const migration of migrations) { const exists = await MigrationModel.findOne({ name: migration.name, status: "completed" }); if (exists) { - logger?.info({ message: `Skipping ${migration.name}`, service: "Migrations" }); + logger.info({ message: `Skipping ${migration.name}`, service: "Migrations" }); continue; } try { - await migration.execute(); + await migration.execute(logger); await MigrationModel.findOneAndUpdate( { name: migration.name }, { status: "completed", completedAt: new Date(), error: undefined }, { upsert: true } ); - logger?.info({ message: `Completed ${migration.name}`, service: "Migrations" }); + logger.info({ message: `Completed ${migration.name}`, service: "Migrations" }); } catch (error) { const err = error as Error; await MigrationModel.findOneAndUpdate({ name: migration.name }, { status: "failed", error: err?.message }, { upsert: true }); throw error; } } - logger?.info({ message: "Migrations completed", service: "Migrations" }); + logger.info({ message: "Migrations completed", service: "Migrations" }); } catch (error) { const err = error as Error; - logger?.error({ message: "Migration failed", service: "Migrations", details: { error: err?.message }, stack: err?.stack }); + logger.error({ message: "Migration failed", service: "Migrations", details: { error: err?.message }, stack: err?.stack }); throw error; } }; diff --git a/server/src/domain/monitors/monitor.model.ts b/server/src/domain/monitors/monitor.model.ts index 8a095658c..47aa70b5e 100644 --- a/server/src/domain/monitors/monitor.model.ts +++ b/server/src/domain/monitors/monitor.model.ts @@ -197,7 +197,7 @@ const checkSnapshotSchema = new Schema( audits: { type: snapshotAuditsSchema }, createdAt: { type: Date, required: true }, }, - { _id: false } + { _id: false, suppressReservedKeysWarning: true } ); const MonitorSchema = new Schema( diff --git a/server/src/index.ts b/server/src/index.ts index d121d7b25..3b0ab6ba4 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -22,10 +22,11 @@ const startApp = async () => { // Basic setup // *********************** - const env = validateEnv(); + logger = new Logger(); + const env = validateEnv(logger); + logger.setLogLevel(env.LOG_LEVEL); const settingsService = new SettingsService(env); const envSettings = settingsService.loadSettings(); - logger = new Logger({ envSettings }); const { queueMode, queuePrimaryProcesses } = envSettings; logger.info({ @@ -51,7 +52,7 @@ const startApp = async () => { if (queueMode === "worker") { const { worker } = await buildWorker(shared, envSettings); logger.info({ message: "Worker instance started. API will not be started", service: SERVICE_NAME }); - initShutdownListener(null, { worker, db: shared.db }); + initShutdownListener(null, { worker, db: shared.db, logger }); return; } diff --git a/server/src/shutdown.ts b/server/src/shutdown.ts index 584cfea39..625547e85 100644 --- a/server/src/shutdown.ts +++ b/server/src/shutdown.ts @@ -1,5 +1,5 @@ import { IDb } from "@/db/db.interface.js"; -import { logger } from "./utils/logger.js"; +import type { ILogger } from "@/utils/logger.js"; import type { Server } from "http"; import { Mongoose } from "mongoose"; import { IJobScheduler } from "@/worker/worker.interface.js"; @@ -7,9 +7,11 @@ import { IJobScheduler } from "@/worker/worker.interface.js"; type ShutdownTargets = { worker: IJobScheduler; db: IDb; + logger: ILogger; }; export const initShutdownListener = (server: Server | null, services: ShutdownTargets) => { const SERVICE_NAME = "Server"; + const logger = services.logger; let isShuttingDown = false; diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index c53e7112c..41142fd92 100755 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,7 +1,6 @@ import { createLogger, format, transports, Logger as WinstonLogger } from "winston"; import type { Logform } from "winston"; import dotenv from "dotenv"; -import { EnvConfig } from "@/domain/app-settings/app-settings.service.js"; dotenv.config(); const SERVICE_NAME = "Logger"; @@ -28,18 +27,17 @@ export interface ILogger { cacheLog(entry: LogEntry): void; getLogs(): LogEntry[]; buildLogEntry(level: string, config: LogConfig): LogEntry; + setLogLevel(level: string): void; } class Logger implements ILogger { static SERVICE_NAME = SERVICE_NAME; private logger: WinstonLogger; - private envSettings: Partial; private logCache: LogEntry[]; private maxCacheSize: number; - constructor({ envSettings }: { envSettings: Partial }) { - this.envSettings = envSettings; + constructor() { this.logCache = []; this.maxCacheSize = 1000; const consoleFormat = format.printf((info: Logform.TransformableInfo) => { @@ -84,10 +82,8 @@ class Logger implements ILogger { return msg; }); - const logLevel = this.envSettings.logLevel || "info"; - this.logger = createLogger({ - level: logLevel, + level: "info", format: format.combine(format.timestamp()), transports: [ new transports.Console({ @@ -151,11 +147,10 @@ class Logger implements ILogger { timestamp: new Date().toISOString(), }; } + + setLogLevel(level: string): void { + this.logger.level = level || "info"; + } } export default Logger; - -// Legacy logger -const logger = new Logger({ envSettings: { logLevel: "debug" } }); -export { logger }; -export type { LogConfig, LogEntry };