refactor: migrate discord-gateway from winston to @bete/shared/logger and utils
- Replace all local logger imports (../../shared/logger/logger.js) with @bete/shared/logger across 26 files - Remove winston dependency, add pino to discord-gateway package.json - Delete shared/logger/logger.ts (winston-based, 132 lines) and serialization.ts (109 lines) - Replace local retryWithBackoff imports with @bete/shared/utils across 6 files - Delete shared/utils/retry.ts (42 lines) - Add CustomLogger type alias to @bete/shared/logger for backwards compatibility - Remove logger param from all retryWithBackoff calls and uploadToTele interfaces - Frontend: convert entity type files to re-exports from shared/api/client.ts - Full monorepo typecheck clean (4/4 packages) 35 files changed, 42 insertions(+), 488 deletions(-)
This commit is contained in:
@@ -30,10 +30,10 @@
|
||||
"p-limit": "^7.3.0",
|
||||
"p-retry": "^8.0.0",
|
||||
"pg": "^8.21.0",
|
||||
"pino": "^9.6.0",
|
||||
"piscina": "^5.1.4",
|
||||
"prism-media": "2.0.0-alpha.0",
|
||||
"sharp": "^0.34.5",
|
||||
"winston": "^3.19.0",
|
||||
"ws": "^8.20.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "../shared/database/drizzle.js";
|
||||
import { runMigrations } from "../shared/database/migrate.js";
|
||||
import { createDiscordClientOptions } from "../shared/discord/clientOptions.js";
|
||||
import { createChildLogger } from "../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { createGracefulShutdown } from "./shutdown.js";
|
||||
|
||||
const logger = createChildLogger("discord-gateway");
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { CommandHandler } from "../modules/command-handler/commandHandler.j
|
||||
import type { EventBroadcaster } from "../modules/event-broadcaster/index.js";
|
||||
import type { VoiceController } from "../modules/voice-recording/voiceController.js";
|
||||
import type { closeDatabase } from "../shared/database/drizzle.js";
|
||||
import type { createChildLogger } from "../shared/logger/logger.js";
|
||||
import type { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
type Logger = ReturnType<typeof createChildLogger>;
|
||||
type CloseDatabase = typeof closeDatabase;
|
||||
|
||||
@@ -3,7 +3,7 @@ import "libsodium-wrappers";
|
||||
import "@snazzah/davey";
|
||||
import "dotenv/config";
|
||||
import { initializeDiscordGateway } from "./app/bootstrap.js";
|
||||
import { createChildLogger } from "./shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const logger = createChildLogger("discord-gateway");
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { Client } from "discord.js-selfbot-v13";
|
||||
import { AbortError } from "p-retry";
|
||||
import { Piscina } from "piscina";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
import type { EventBroadcaster } from "../event-broadcaster/index.js";
|
||||
import { invalidateAnalyticsCache } from "../message-capture/analyticsStore.js";
|
||||
import { isAgeRestrictedMetadata } from "../message-capture/messageMetadata.js";
|
||||
@@ -380,7 +380,6 @@ async function processIndividualFallback(
|
||||
retries: 0,
|
||||
minTimeout: 0,
|
||||
maxTimeout: 0,
|
||||
logger,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Client, PermissionString } from "discord.js-selfbot-v13";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { createModerationAction } from "../message-capture/messageStore.js";
|
||||
import type { MessageRecord } from "../message-capture/types.js";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { getCachedText, upsertCachedText } from "./textCacheStore.js";
|
||||
import { llmDetectBadwords } from "./llmClient.js";
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
import { withLlmConcurrency } from "./concurrencyLimiter.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const log = createChildLogger("llm-client");
|
||||
|
||||
@@ -136,7 +136,6 @@ export async function llmChat(
|
||||
minTimeout: 0,
|
||||
maxTimeout: 0,
|
||||
factor: 2,
|
||||
logger: log,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { ChatCompletion } from "openai/resources/chat/completions";
|
||||
import { AbortError } from "p-retry";
|
||||
import { z } from "zod";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
import { resizeImageForVision } from "../attachment-upload/imageResizer.js";
|
||||
import { extractMessageMediaEvidence } from "../message-capture/messageMetadata.js";
|
||||
import type {
|
||||
@@ -677,7 +677,6 @@ async function callModerationLLM(
|
||||
minTimeout: 3000,
|
||||
maxTimeout: 8000,
|
||||
factor: 2,
|
||||
logger: log,
|
||||
},
|
||||
);
|
||||
parsed = analysis.parsed;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Enables full audit trail and debugging of moderation decisions.
|
||||
*/
|
||||
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { AnalysisResult } from "../message-capture/types.js";
|
||||
|
||||
const logger = createChildLogger("response-logger");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { executeAll, executeGet } from "../../shared/database/drizzle.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const logger = createChildLogger("sticker-cache");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { executeAll, executeGet } from "../../shared/database/drizzle.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const logger = createChildLogger("text-cache-store");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolve } from "node:dns/promises";
|
||||
import { isIP } from "node:net";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const log = createChildLogger("urlFetcher");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { uploadToTele } from "./teleUpload.js";
|
||||
import {
|
||||
updateAttachmentAsFailedUpload,
|
||||
@@ -45,7 +45,6 @@ export async function uploadAttachmentToTele(
|
||||
uploadUrl: config.TELE_UPLOAD_URL,
|
||||
timeoutMs: config.ATTACHMENT_UPLOAD_TIMEOUT_MS,
|
||||
retries: 0,
|
||||
logger,
|
||||
});
|
||||
|
||||
return result.url;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sharp from "sharp";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const log = createChildLogger("imageResizer");
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CustomLogger } from "../../shared/logger/logger.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
|
||||
export interface TeleUploadResponse {
|
||||
download_url: string;
|
||||
@@ -37,7 +36,6 @@ export async function uploadToTele(input: {
|
||||
uploadUrl: string;
|
||||
timeoutMs?: number;
|
||||
retries: number;
|
||||
logger: CustomLogger;
|
||||
}): Promise<TeleUploadResult> {
|
||||
const {
|
||||
buffer,
|
||||
@@ -46,7 +44,6 @@ export async function uploadToTele(input: {
|
||||
uploadUrl,
|
||||
timeoutMs,
|
||||
retries,
|
||||
logger,
|
||||
} = input;
|
||||
|
||||
const response = await retryWithBackoff(
|
||||
@@ -77,7 +74,6 @@ export async function uploadToTele(input: {
|
||||
retries,
|
||||
minTimeout: 0,
|
||||
maxTimeout: 0,
|
||||
logger,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Client } from "discord.js-selfbot-v13";
|
||||
import Redis from "ioredis";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { discordPlayer } from "../voice-recording/player.js";
|
||||
import type { VoiceController } from "../voice-recording/voiceController.js";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Redis from "ioredis";
|
||||
import type { CustomLogger } from "../../shared/logger/logger.js";
|
||||
import type { CustomLogger } from "@bete/shared/logger";
|
||||
|
||||
export interface DiscordGatewayEvent {
|
||||
type: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { executeAll, executeGet } from "../../shared/database/drizzle.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { MessageRecord } from "./types.js";
|
||||
|
||||
const logger = createChildLogger("analytics-store");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { WebSocket } from "ws";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { MediaState } from "../voice-recording/mediaTypes.js";
|
||||
import type {
|
||||
AnalysisQueueStatus,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Client, Message } from "discord.js-selfbot-v13";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { queueMessageAnalysis } from "../ai-moderation/aiAnalyzer.js";
|
||||
import { processAttachmentUpload } from "../attachment-upload/attachmentUploader.js";
|
||||
import type { EventBroadcaster } from "../event-broadcaster/eventBroadcaster.js";
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
moderationActionsTable,
|
||||
retentionPoliciesTable,
|
||||
} from "../../shared/database/schema.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { decodeCursor, encodeCursor } from "../message-capture/pagination.js";
|
||||
import type {
|
||||
AttachmentRecord,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@discordjs/voice";
|
||||
import type { Client, VoiceChannel } from "discord.js-selfbot-v13";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { PacketFilter } from "./packetFilter.js";
|
||||
import { subscribeToAudioStream } from "./recorder/audioStream.js";
|
||||
import { OpusDecoder } from "./recorder/decoder.js";
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
type RecordingSession,
|
||||
} from "./recorder/sessionRecording.js";
|
||||
import { uploadRecordingSegment } from "./recorder/uploader.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
import type { PcmBroadcaster } from "../message-capture/types.js";
|
||||
|
||||
const logger = createChildLogger("recorder");
|
||||
@@ -95,7 +95,6 @@ export async function startRecording(
|
||||
retries: 0,
|
||||
minTimeout: 0,
|
||||
maxTimeout: 0,
|
||||
logger,
|
||||
},
|
||||
);
|
||||
logger.info("Connected to voice channel. Recording started");
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
updateVoiceRecordingAsFailed,
|
||||
updateVoiceRecordingAsUploaded,
|
||||
} from "../../../shared/database/voiceRecordingRepo.js";
|
||||
import { createChildLogger } from "../../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { uploadToTele } from "../teleUpload.js";
|
||||
|
||||
const logger = createChildLogger("recording-uploader");
|
||||
@@ -61,7 +61,6 @@ export async function uploadRecordingSegment(input: {
|
||||
contentType: "audio/ogg",
|
||||
uploadUrl: config.TELE_UPLOAD_URL,
|
||||
retries: 0,
|
||||
logger,
|
||||
});
|
||||
const downloadUrl = uploadResult.url;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CustomLogger } from "../../shared/logger/logger.js";
|
||||
import { retryWithBackoff } from "../../shared/utils/retry.js";
|
||||
import { retryWithBackoff } from "@bete/shared/utils";
|
||||
|
||||
export interface TeleUploadResponse {
|
||||
download_url: string;
|
||||
@@ -37,7 +36,6 @@ export async function uploadToTele(input: {
|
||||
uploadUrl: string;
|
||||
timeoutMs?: number;
|
||||
retries: number;
|
||||
logger: CustomLogger;
|
||||
}): Promise<TeleUploadResult> {
|
||||
const {
|
||||
buffer,
|
||||
@@ -46,7 +44,6 @@ export async function uploadToTele(input: {
|
||||
uploadUrl,
|
||||
timeoutMs,
|
||||
retries,
|
||||
logger,
|
||||
} = input;
|
||||
|
||||
const response = await retryWithBackoff(
|
||||
@@ -77,7 +74,6 @@ export async function uploadToTele(input: {
|
||||
retries,
|
||||
minTimeout: 0,
|
||||
maxTimeout: 0,
|
||||
logger,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getVoiceConnection, type VoiceConnection } from "@discordjs/voice";
|
||||
import type { Client, Guild, VoiceChannel } from "discord.js-selfbot-v13";
|
||||
import { AppError } from "@bete/shared/errors";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { discordPlayer } from "./player.js";
|
||||
import { startRecording, stopRecording } from "./recorder.js";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import type { PoolClient } from "pg";
|
||||
import { Pool } from "pg";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import * as schema from "./schema.js";
|
||||
|
||||
const logger = createChildLogger("drizzle");
|
||||
|
||||
@@ -2,7 +2,7 @@ import "dotenv/config";
|
||||
import type { PoolClient } from "pg";
|
||||
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
|
||||
import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import {
|
||||
closeDatabase,
|
||||
initializeDatabase,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { runMigrations } from "./migrate.js";
|
||||
|
||||
const logger = createChildLogger("migrate-cli");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { createChildLogger } from "../../shared/logger/logger.js";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { getDatabase } from "./drizzle.js";
|
||||
import {
|
||||
type VoiceRecording,
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import winston from "winston";
|
||||
import { formatLogMetadata, serializeLogValue } from "./serialization.js";
|
||||
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const logLevel = process.env.LOG_LEVEL || (isDev ? "debug" : "info");
|
||||
const logsDir = path.resolve(process.cwd(), "logs");
|
||||
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
|
||||
const metadataFormat = winston.format((info) => {
|
||||
const {
|
||||
level: _level,
|
||||
message: _message,
|
||||
timestamp: _timestamp,
|
||||
...metadata
|
||||
} = info;
|
||||
|
||||
for (const key of Object.keys(metadata)) {
|
||||
delete info[key];
|
||||
}
|
||||
|
||||
Object.assign(info, formatLogMetadata(metadata));
|
||||
return info;
|
||||
});
|
||||
|
||||
const consoleFormat = winston.format.printf((info) => {
|
||||
const { level, message, timestamp, context, ...metadata } = info;
|
||||
const contextLabel = context ? ` [${String(context)}]` : "";
|
||||
const metadataText = Object.keys(metadata).length
|
||||
? ` ${JSON.stringify(formatLogMetadata(metadata))}`
|
||||
: "";
|
||||
|
||||
return `${timestamp} ${level}${contextLabel}: ${message}${metadataText}`;
|
||||
});
|
||||
|
||||
export interface CustomLogger {
|
||||
error: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
warn: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
info: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
debug: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
trace: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
fatal: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
silent: (msgOrObj: any, msgOrArgs?: any, ...args: any[]) => void;
|
||||
child(options: { context: string } & Record<string, any>): CustomLogger;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const winstonLogger = winston.createLogger({
|
||||
level: logLevel,
|
||||
levels: winston.config.npm.levels,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
metadataFormat(),
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.timestamp(),
|
||||
metadataFormat(),
|
||||
consoleFormat,
|
||||
),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, "app.log"),
|
||||
format: winston.format.json(),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(logsDir, "error.log"),
|
||||
level: "error",
|
||||
format: winston.format.json(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
function wrapLogger(wLogger: winston.Logger): CustomLogger {
|
||||
const logAtLevel = (level: string) => {
|
||||
return (arg1: any, arg2?: any) => {
|
||||
if (arg1 instanceof Error) {
|
||||
wLogger.log(level, arg1.message, { error: arg1 });
|
||||
} else if (typeof arg1 === "object" && arg1 !== null) {
|
||||
const message = typeof arg2 === "string" ? arg2 : "";
|
||||
wLogger.log(level, message, { ...arg1 });
|
||||
} else {
|
||||
const message = typeof arg1 === "string" ? arg1 : String(arg1);
|
||||
const metadata = typeof arg2 === "object" && arg2 !== null ? arg2 : {};
|
||||
wLogger.log(level, message, metadata);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const wrapped: CustomLogger = {
|
||||
error: logAtLevel("error"),
|
||||
warn: logAtLevel("warn"),
|
||||
info: logAtLevel("info"),
|
||||
debug: logAtLevel("debug"),
|
||||
trace: logAtLevel("debug"),
|
||||
fatal: logAtLevel("error"),
|
||||
silent: () => {},
|
||||
child: (options: any) => {
|
||||
const childWinston = wLogger.child(options);
|
||||
return wrapLogger(childWinston);
|
||||
},
|
||||
};
|
||||
|
||||
const proxy = new Proxy(wrapped, {
|
||||
get(target, prop) {
|
||||
if (prop in target) {
|
||||
return (target as any)[prop];
|
||||
}
|
||||
const val = (wLogger as any)[prop];
|
||||
if (typeof val === "function") {
|
||||
return val.bind(wLogger);
|
||||
}
|
||||
return val;
|
||||
},
|
||||
});
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
export const logger: CustomLogger = wrapLogger(winstonLogger);
|
||||
|
||||
export const createChildLogger = (context: string): CustomLogger => {
|
||||
return logger.child({ context });
|
||||
};
|
||||
|
||||
export const serializeLogValueForTest = serializeLogValue;
|
||||
export const formatLogMetadataForTest = formatLogMetadata;
|
||||
@@ -1,109 +0,0 @@
|
||||
export type LogMetadata = Record<string, unknown>;
|
||||
|
||||
type SerializedError = {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
code?: unknown;
|
||||
statusCode?: unknown;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
const serializeError = (error: Error): SerializedError => {
|
||||
const serialized: SerializedError = {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
};
|
||||
|
||||
if (error.stack) {
|
||||
serialized.stack = error.stack;
|
||||
}
|
||||
|
||||
const errorWithFields = error as Error & {
|
||||
code?: unknown;
|
||||
statusCode?: unknown;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
if (errorWithFields.code !== undefined) {
|
||||
serialized.code = errorWithFields.code;
|
||||
}
|
||||
|
||||
if (errorWithFields.statusCode !== undefined) {
|
||||
serialized.statusCode = errorWithFields.statusCode;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(errorWithFields)) {
|
||||
if (serialized[key] === undefined) {
|
||||
serialized[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return serialized;
|
||||
};
|
||||
|
||||
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return prototype === Object.prototype || prototype === null;
|
||||
};
|
||||
|
||||
export const serializeLogValue = (
|
||||
value: unknown,
|
||||
_seen: WeakSet<object> = new WeakSet(),
|
||||
): unknown => {
|
||||
if (value === null || value === undefined) return value;
|
||||
|
||||
if (value instanceof Error) {
|
||||
return serializeError(value);
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
return value.toISOString();
|
||||
}
|
||||
|
||||
if (value instanceof RegExp) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
if (_seen.has(value as object)) {
|
||||
return "[Circular]";
|
||||
}
|
||||
_seen.add(value as object);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => serializeLogValue(item, _seen));
|
||||
}
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, nestedValue]) => [
|
||||
key,
|
||||
serializeLogValue(nestedValue, _seen),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
try {
|
||||
return `[Object ${(value as any)?.constructor?.name ?? "unknown"}]`;
|
||||
} catch {
|
||||
return "[Object]";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const formatLogMetadata = (metadata: LogMetadata): LogMetadata => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(metadata).map(([key, value]) => [
|
||||
key,
|
||||
serializeLogValue(value),
|
||||
]),
|
||||
);
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
import pRetry from "p-retry";
|
||||
import type { CustomLogger } from "../../shared/logger/logger.js";
|
||||
|
||||
export interface RetryOptions {
|
||||
retries?: number;
|
||||
minTimeout?: number;
|
||||
maxTimeout?: number;
|
||||
factor?: number;
|
||||
logger?: CustomLogger;
|
||||
}
|
||||
|
||||
export async function retryWithBackoff<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: RetryOptions = {},
|
||||
): Promise<T> {
|
||||
const {
|
||||
retries = 3,
|
||||
minTimeout = 0,
|
||||
maxTimeout = 0,
|
||||
factor = 1,
|
||||
logger,
|
||||
} = options;
|
||||
|
||||
return pRetry(fn, {
|
||||
retries,
|
||||
minTimeout,
|
||||
maxTimeout,
|
||||
factor,
|
||||
onFailedAttempt: (error) => {
|
||||
if (logger) {
|
||||
logger.warn(
|
||||
{
|
||||
attempt: error.attemptNumber,
|
||||
retriesLeft: error.retriesLeft,
|
||||
error: error.error,
|
||||
},
|
||||
"Retry attempt",
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user