clean up logger

This commit is contained in:
Alex Holliday committed 2026-06-24 10:53:03 -07:00
1 parent 71d3e789ff
commit 74783fd738
15 files changed
+61 -47

No files matched your search

+2 -2
View File
@@ -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";
+1 -1
View File
@@ -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;
};
+22 -6
View File
@@ -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<typeof envSchema>;
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;
};
+1 -1
View File
@@ -42,7 +42,7 @@ class MongoDB implements IDb<Mongoose> {
// 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();
@@ -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<void> {
export async function cleanupDuplicateMonitorStats(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:CleanupDuplicateMonitorStats";
try {
@@ -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<void> {
export async function fixInfrastructureThresholds(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:FixInfrastructureThresholds";
try {
@@ -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<void> {
export async function migrateStatusPageTypeToArray(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray";
try {
@@ -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<void> {
export async function cleanupDuplicateMonitorStatsForUniqueIndex(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:CleanupDuplicateMonitorStatsForUniqueIndex";
try {
@@ -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<void> {
export async function migrateMaintenanceWindowMonitorIdToArray(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:MigrateMaintenanceWindowMonitorIdToArray";
try {
@@ -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<void> {
export async function backfillMonitorLastEvaluatedAt(logger: ILogger): Promise<void> {
const SERVICE_NAME = "Migration:BackfillMonitorLastEvaluatedAt";
try {
+8 -8
View File
@@ -11,7 +11,7 @@ import type { ILogger } from "@/utils/logger.js";
type MigrationEntry = {
name: string;
execute: () => Promise<void>;
execute: (logger: ILogger) => Promise<void>;
};
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;
}
};
+1 -1
View File
@@ -197,7 +197,7 @@ const checkSnapshotSchema = new Schema<CheckSnapshotDocument>(
audits: { type: snapshotAuditsSchema },
createdAt: { type: Date, required: true },
},
{ _id: false }
{ _id: false, suppressReservedKeysWarning: true }
);
const MonitorSchema = new Schema<MonitorDocument>(
+4 -3
View File
@@ -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;
}
+3 -1
View File
@@ -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<Mongoose>;
logger: ILogger;
};
export const initShutdownListener = (server: Server | null, services: ShutdownTargets) => {
const SERVICE_NAME = "Server";
const logger = services.logger;
let isShuttingDown = false;
+7 -12
View File
@@ -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<EnvConfig>;
private logCache: LogEntry[];
private maxCacheSize: number;
constructor({ envSettings }: { envSettings: Partial<EnvConfig> }) {
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 };