diff --git a/.gitignore b/.gitignore index a54047237..2f2607277 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ api/fetch_cache api/postgres_db api/meilisearch_db api/nodemon.json +api/logs # web bundle diff --git a/api/package.json b/api/package.json index 5ef9ecf5b..c06b51913 100644 --- a/api/package.json +++ b/api/package.json @@ -10,6 +10,7 @@ "@dzcode.io/data": "*", "@dzcode.io/models": "*", "@dzcode.io/utils": "*", + "@omdxp/jslog": "^1.7.1", "@sentry/node": "^8.28.0", "@sentry/profiling-node": "^8.28.0", "@types/make-fetch-happen": "^10.0.4", @@ -31,8 +32,7 @@ "postgres": "^3.4.4", "reflect-metadata": "^0.2.2", "routing-controllers": "^0.10.4", - "typedi": "^0.10.0", - "winston": "^3.3.3" + "typedi": "^0.10.0" }, "devDependencies": { "@dzcode.io/tooling": "*", diff --git a/api/src/ai/service.ts b/api/src/ai/service.ts index c1a720bf4..37a2af52e 100644 --- a/api/src/ai/service.ts +++ b/api/src/ai/service.ts @@ -18,7 +18,7 @@ type OpenAIResponse = { export class AIService { constructor( private readonly configService: ConfigService, - private readonly logger: LoggerService, + private readonly loggerService: LoggerService, private readonly fetchService: FetchService, private readonly aiPromptRepository: AiPromptRepository, ) {} @@ -44,7 +44,7 @@ export class AIService { const body = { model: "gpt-4o", messages: payloadWithValidationPrompt }; - this.logger.info({ message: "Cached response not found, querying AI..." }); + this.loggerService.logger.info("Cached response not found, querying AI..."); // todo-zm: change to captureEvent captureException("AI Query", { tags: { type: "CRON" }, extra: { body } }); diff --git a/api/src/app/index.ts b/api/src/app/index.ts index b3c1da8e2..bd68e6ed5 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -55,13 +55,36 @@ useContainer(Container); // eslint-disable-line react-hooks/rules-of-hooks }; const app: Application = createExpressServer(routingControllersOptions); - const logger = Container.get(LoggerService); + const loggerService = Container.get(LoggerService); Sentry.setupExpressErrorHandler(app); + // Graceful shutdown handler for logger file streams + const shutdown = (signal: string, server?: ReturnType) => { + loggerService.logger.info("Received signal, closing logger streams", "signal", signal); + if (server) { + server.close(() => { + loggerService.close(); + process.exit(0); + }); + // Force shutdown after timeout + setTimeout(() => { + loggerService.logger.error("Forced shutdown after timeout"); + loggerService.close(); + process.exit(1); + }, 10000); + } else { + loggerService.close(); + process.exit(0); + } + }; + // Start it - app.listen(PORT, () => { + const server = app.listen(PORT, () => { const commonConfig = fsConfig(NODE_ENV); - logger.info({ message: `API Server up on: ${commonConfig.api.url}/` }); + loggerService.logger.info("API Server started", "url", commonConfig.api.url); }); + + process.on("SIGTERM", () => shutdown("SIGTERM", server)); + process.on("SIGINT", () => shutdown("SIGINT", server)); })(); diff --git a/api/src/app/middlewares/logger.ts b/api/src/app/middlewares/logger.ts index e758a807c..f95f5184f 100644 --- a/api/src/app/middlewares/logger.ts +++ b/api/src/app/middlewares/logger.ts @@ -1,7 +1,8 @@ import { RequestHandler } from "express"; import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; -import { LoggerService, LogLevel } from "src/logger/service"; +import { LoggerService } from "src/logger/service"; import { Service } from "typedi"; +import { HttpReq, HttpRes, Level } from "@omdxp/jslog"; @Service() @Middleware({ type: "after" }) @@ -9,15 +10,35 @@ export class LoggerMiddleware implements ExpressMiddlewareInterface { constructor(private loggerService: LoggerService) {} use: RequestHandler = (req, res, next) => { - let logLevel: LogLevel = "info"; + let logLevel = Level.INFO; const { statusCode } = res; - if (statusCode < 100 && statusCode >= 400) { - logLevel = "error"; + + if (statusCode >= 500) { + logLevel = Level.ERROR; + } else if (statusCode >= 400) { + logLevel = Level.WARN; + } else if (statusCode >= 300) { + logLevel = Level.DEBUG; } - this.loggerService.log(logLevel, { - message: `${res.statusCode} ${req.method} ${req.url}`, + const logger = this.loggerService.logger; + const message = `${req.method} ${req.url}`; + + const requestAttrs = HttpReq({ + method: req.method, + url: req.url, + ip: req.ip, + userAgent: req.headers["user-agent"], + }); + + const responseAttrs = HttpRes({ + status: statusCode, }); + + const logAttrs = [...requestAttrs, ...responseAttrs]; + + logger.log(logLevel, message, ...logAttrs); + next(); }; } diff --git a/api/src/digest/cron.ts b/api/src/digest/cron.ts index a57aa7dd8..d8abfc0bc 100644 --- a/api/src/digest/cron.ts +++ b/api/src/digest/cron.ts @@ -46,7 +46,7 @@ export class DigestCron { private isRunning = false; constructor( - private readonly logger: LoggerService, + private readonly loggerService: LoggerService, private readonly dataService: DataService, private readonly githubService: GithubService, private readonly projectsRepository: ProjectRepository, @@ -64,7 +64,7 @@ export class DigestCron { this.schedule, async () => { if (this.isRunning) { - logger.warn({ message: "Digest cron already running" }); + loggerService.logger.warn("Digest cron already running"); return; } @@ -75,10 +75,7 @@ export class DigestCron { this.isRunning = false; console.error(error); captureException(error, { tags: { type: "CRON" } }); - logger.error({ - message: `Digest cron failed: ${error}`, - meta: { error }, - }); + loggerService.logger.error("Digest cron failed", "error", error); } this.isRunning = false; }, @@ -90,7 +87,7 @@ export class DigestCron { undefined, true, ); - logger.info({ message: "Digest cron initialized" }); + loggerService.logger.info("Digest cron initialized"); } /** @@ -98,12 +95,12 @@ export class DigestCron { */ private async run() { const runId = Math.random().toString(36).slice(2); - this.logger.info({ message: `Digest cron started, runId: ${runId}` }); + this.loggerService.logger.info("Digest cron started", "runId", runId); let projectsFromDataFolder = await this.dataService.listProjects(); if (this.configService.env().NODE_ENV === "development") { - this.logger.info({ message: `Running in development mode, filtering projects` }); + this.loggerService.logger.info("Running in development mode, filtering projects"); projectsFromDataFolder = projectsFromDataFolder.filter((p) => ["Open-listings", "dzcode.io website", "Mishkal", "System Monitor"].includes(p.name), ); @@ -326,7 +323,7 @@ it may contain non-translatable parts like acronyms, keep them as is.`; captureException(error, { tags: { type: "CRON" } }); } - this.logger.info({ message: `Digest cron finished, runId: ${runId}` }); + this.loggerService.logger.info("Digest cron finished", "runId", runId); } private async getRepoInfo( diff --git a/api/src/fetch/service.ts b/api/src/fetch/service.ts index c3de752f3..91746e89e 100644 --- a/api/src/fetch/service.ts +++ b/api/src/fetch/service.ts @@ -9,7 +9,7 @@ import { FetchConfig } from "./types"; export class FetchService { constructor( private readonly configService: ConfigService, - private readonly logger: LoggerService, + private readonly loggerService: LoggerService, ) { const { FETCH_CACHE_PATH } = this.configService.env(); @@ -46,10 +46,10 @@ export class FetchService { private makeFetchHappenInstance; private async fetch(url: string, options: FetchOptions) { - this.logger.info({ message: `Fetching ${url}` }); + this.loggerService.logger.info("Fetching URL", "url", url); const response = await this.makeFetchHappenInstance(url, options); if (!response.ok) { - this.logger.error({ message: `Failed to fetch ${url}`, meta: { status: response.status } }); + this.loggerService.logger.error("Failed to fetch URL", "url", url, "status", response.status); throw new Error(`Failed to fetch ${url}: ${response.statusText}`); } const jsonResponse = (await response.json()) as T; diff --git a/api/src/logger/service.ts b/api/src/logger/service.ts index b29d18ab1..63873fed5 100644 --- a/api/src/logger/service.ts +++ b/api/src/logger/service.ts @@ -1,45 +1,66 @@ import { Service } from "typedi"; -import winston from "winston"; +import { + Logger, + New, + MultiHandler, + PrettyHandler, + ColorHandler, + JSONHandler, + FileHandler, + Level, +} from "@omdxp/jslog"; +import * as path from "path"; +import * as fs from "fs"; @Service() export class LoggerService { + private _logger: Logger; + private fileHandler?: FileHandler; + constructor() { - this.logger = winston.createLogger({ - level: "info", - format: winston.format.json(), - transports: [ - new winston.transports.Console({ - format: winston.format.combine(winston.format.colorize(), winston.format.simple()), - }), - ], - }); - } + const isDev = process.env.NODE_ENV === "development"; + const logDir = path.join(process.cwd(), "logs"); - public log(level: LogLevel, logInfo: LogObject) { - this.logger.log(level, logInfo.message, logInfo.meta); - } + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } - public info(logInfo: LogObject) { - this.log("info", logInfo); - } + if (isDev) { + const devHandler = new PrettyHandler({ + handler: new ColorHandler({ + level: Level.DEBUG, + addSource: true, + }), + indent: 2, + compactArrays: true, + }); + this._logger = New(devHandler); + } else { + this.fileHandler = new FileHandler({ + filepath: path.join(logDir, "api.log"), + maxSize: 50 * 1024 * 1024, + maxFiles: 10, + format: "json", + level: Level.INFO, + addSource: false, + }); - public error(logInfo: LogObject) { - this.log("error", logInfo); - } + const productionHandler = new MultiHandler([ + new JSONHandler({ level: Level.INFO }), + this.fileHandler, + ]); - public debug(logInfo: LogObject) { - this.log("debug", logInfo); + this._logger = New(productionHandler); + } } - public warn(logInfo: LogObject) { - this.log("warn", logInfo); + public get logger(): Logger { + return this._logger; } - private logger; + public close(): void { + if (this.fileHandler) { + this.fileHandler.close(); + } + } } - -export type LogLevel = "info" | "error" | "debug" | "warn"; -type LogObject = { - message: string; - meta?: unknown; -}; diff --git a/api/src/postgres/service.ts b/api/src/postgres/service.ts index c270849a5..053d0f75c 100644 --- a/api/src/postgres/service.ts +++ b/api/src/postgres/service.ts @@ -22,20 +22,20 @@ export class PostgresService { private readonly configService: ConfigService, private readonly loggerService: LoggerService, ) { - this.loggerService.info({ message: "Initializing Postgres database" }); + this.loggerService.logger.info("Initializing Postgres database"); const { POSTGRES_URI } = this.configService.env(); const queryClient = postgres(POSTGRES_URI); this.drizzleDB = drizzle(queryClient); - this.loggerService.info({ message: "Database migration started" }); + this.loggerService.logger.info("Database migration started"); } public async migrate() { if (this.isReady) throw new Error("Database is already ready"); - this.loggerService.info({ message: "Database migration started" }); + this.loggerService.logger.info("Database migration started"); await migrate(this.drizzleDB, { migrationsFolder: join(__dirname, "../../db/migrations") }); - this.loggerService.info({ message: "Database migration complete" }); + this.loggerService.logger.info("Database migration complete"); this.isReady = true; } diff --git a/api/src/search/service.ts b/api/src/search/service.ts index dd7626039..86346e21a 100644 --- a/api/src/search/service.ts +++ b/api/src/search/service.ts @@ -12,23 +12,21 @@ export class SearchService { private readonly meilisearch: MeiliSearch; constructor( private readonly configService: ConfigService, - private readonly logger: LoggerService, + private readonly loggerService: LoggerService, ) { - this.logger.info({ message: "Initializing MeiliSearch client" }); + this.loggerService.logger.info("Initializing MeiliSearch client"); const { MEILISEARCH_URL, MEILISEARCH_MASTER_KEY } = this.configService.env(); this.meilisearch = new MeiliSearch({ host: MEILISEARCH_URL, apiKey: MEILISEARCH_MASTER_KEY, }); - this.logger.info({ - message: `MeiliSearch client initialized with url ${MEILISEARCH_URL}`, - }); + this.loggerService.logger.info("MeiliSearch client initialized", "url", MEILISEARCH_URL); } public search = async (q: string, lang: LanguageCode, limit?: number): Promise => { // TODO-ZM: only fetch Ids from search db, then query actually entities from their respective repositories - this.logger.info({ message: `Searching for "${q}" in all indexes` }); + this.loggerService.logger.info("Searching in all indexes", "query", q); const searchResults = await this.meilisearch.multiSearch({ queries: [ { indexUid: "project", q, limit, attributesToRetrieve: ["id", `name_${lang}`] }, @@ -65,23 +63,23 @@ export class SearchService { }; public upsert = async (index: SearchType, data: T): Promise => { - this.logger.info({ - message: `Upserting "${data.id}" item to ${index}`, - }); + this.loggerService.logger.info("Upserting item to index", "id", data.id, "index", index); await this.meilisearch.index(index).updateDocuments([data]); - this.logger.info({ message: `Upserted "${data.id}" item to ${index}` }); + this.loggerService.logger.info("Item upserted to index", "id", data.id, "index", index); }; public deleteAllButWithRunId = async (index: SearchType, runId: string): Promise => { - this.logger.info({ - message: `Deleting all ${index} but with runId ${runId}`, - }); + this.loggerService.logger.info( + "Deleting all items except runId", + "index", + index, + "runId", + runId, + ); await this.meilisearch.index(index).deleteDocuments({ filter: `NOT runId=${runId}`, }); - this.logger.info({ - message: `Deleted all ${index} but with runId ${runId}`, - }); + this.loggerService.logger.info("Items deleted except runId", "index", index, "runId", runId); }; public setupSearch = async (): Promise => { @@ -98,12 +96,12 @@ export class SearchService { private async upsertIndex(index: SearchType): Promise { try { await this.meilisearch.getIndex(index); - this.logger.info({ message: `${index} index already exists` }); + this.loggerService.logger.info("Index already exists", "index", index); } catch { await this.meilisearch.createIndex(index, { primaryKey: "id", }); - this.logger.info({ message: `${index} index created` }); + this.loggerService.logger.info("Index created", "index", index); } } diff --git a/package-lock.json b/package-lock.json index 46ca1fa6d..a3a55adbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "@dzcode.io/data": "*", "@dzcode.io/models": "*", "@dzcode.io/utils": "*", + "@omdxp/jslog": "^1.7.1", "@sentry/node": "^8.28.0", "@sentry/profiling-node": "^8.28.0", "@types/make-fetch-happen": "^10.0.4", @@ -88,8 +89,7 @@ "postgres": "^3.4.4", "reflect-metadata": "^0.2.2", "routing-controllers": "^0.10.4", - "typedi": "^0.10.0", - "winston": "^3.3.3" + "typedi": "^0.10.0" }, "devDependencies": { "@dzcode.io/tooling": "*", @@ -879,17 +879,6 @@ "ms": "^2.1.1" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "license": "MIT", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, "node_modules/@drizzle-team/brocli": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.1.tgz", @@ -4230,6 +4219,14 @@ "@octokit/openapi-types": "^18.0.0" } }, + "node_modules/@omdxp/jslog": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@omdxp/jslog/-/jslog-1.7.1.tgz", + "integrity": "sha512-QGCUIQCukYNH4No0RlHEonSvqXrzGPoLCSlLz+CiPfCznD2OZCWCy9uwOkI8TqbcwgzxT0p7e6OuWszqZ2ZEJQ==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -6247,12 +6244,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT" - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -6811,18 +6802,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7302,6 +7281,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, "license": "MIT" }, "node_modules/asynckit": { @@ -7561,6 +7541,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -8779,16 +8760,6 @@ "dev": true, "license": "MIT" }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8807,16 +8778,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -8827,21 +8788,6 @@ "color-support": "bin.js" } }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -8856,16 +8802,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "license": "MIT", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, "node_modules/columnify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", @@ -11806,12 +11742,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -12482,15 +12412,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventemitter2": { "version": "6.4.7", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", @@ -12509,7 +12430,10 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.x" } @@ -12947,12 +12871,6 @@ "pend": "~1.2.0" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", - "license": "MIT" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -13115,12 +13033,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT" - }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -14637,6 +14549,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -15526,6 +15439,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16933,12 +16847,6 @@ "node": ">= 0.6" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" - }, "node_modules/lazy-ass": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", @@ -17909,32 +17817,6 @@ "node": ">=8" } }, - "node_modules/logform": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", - "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", - "license": "MIT", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/logform/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -20305,15 +20187,6 @@ "wrappy": "1" } }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "license": "MIT", - "dependencies": { - "fn.name": "1.x.x" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -22071,6 +21944,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -22916,6 +22790,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -23563,15 +23438,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -23919,21 +23785,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -24246,15 +24097,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -24320,6 +24162,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -25550,12 +25393,6 @@ "node": ">=0.10" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "license": "MIT" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -25790,15 +25627,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -26661,6 +26489,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "devOptional": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -27152,91 +26981,6 @@ "node": ">=8" } }, - "node_modules/winston": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", - "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", - "license": "MIT", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.6.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.7.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.8.0.tgz", - "integrity": "sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==", - "license": "MIT", - "dependencies": { - "logform": "^2.6.1", - "readable-stream": "^4.5.2", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/winston/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",