From f353fdf2520599e09499c2379eb0e566f14f1080 Mon Sep 17 00:00:00 2001 From: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:27:12 +0300 Subject: [PATCH] chore: bridge logs api v2 <-> libraries (#20440) * chore: bridge logs api v2 <-> libraries * chore: replace tslog directly * chore: add settings handling * chore: use minLevel to stop debug logs * handle log levels * bump libraries --- apps/api/v2/.env.example | 6 + apps/api/v2/package.json | 2 +- apps/api/v2/src/lib/logger.bridge.ts | 152 +++++++++++++++++++++ packages/platform/libraries/vite.config.js | 4 +- 4 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 apps/api/v2/src/lib/logger.bridge.ts diff --git a/apps/api/v2/.env.example b/apps/api/v2/.env.example index 66b00d53f0..6b5f3b8763 100644 --- a/apps/api/v2/.env.example +++ b/apps/api/v2/.env.example @@ -32,3 +32,9 @@ DOCS_URL= # Axiom logging AXIOM_DATASET= AXIOM_TOKEN= + +# DEBUG: 0 +# INFO: 1 +# WARN: 2 +# ERROR: 3 +LOGGER_BRIDGE_LOG_LEVEL="1" \ No newline at end of file diff --git a/apps/api/v2/package.json b/apps/api/v2/package.json index 2cdb161519..257825184d 100644 --- a/apps/api/v2/package.json +++ b/apps/api/v2/package.json @@ -38,7 +38,7 @@ "@axiomhq/winston": "^1.2.0", "@calcom/platform-constants": "*", "@calcom/platform-enums": "*", - "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.167", + "@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.168", "@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2", "@calcom/platform-types": "*", "@calcom/platform-utils": "*", diff --git a/apps/api/v2/src/lib/logger.bridge.ts b/apps/api/v2/src/lib/logger.bridge.ts new file mode 100644 index 0000000000..40d1732d6b --- /dev/null +++ b/apps/api/v2/src/lib/logger.bridge.ts @@ -0,0 +1,152 @@ +import { Injectable, Logger as NestLogger, Scope } from "@nestjs/common"; + +// 1. Define an interface for the settings +interface IMyLoggerSettings { + minLevel: number; // Example: 0=debug, 1=info, 2=warn, 3=error + displayTimestamp: boolean; + logFormat: "pretty" | "json" | "simple"; + // Add any other settings you need, mimicking tslog or your own requirements + // e.g., name?: string; displayFunctionName?: boolean; etc. +} + +// Define log level constants (optional but recommended for readability) +const LogLevel = { + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, +}; + +/** + * This logger acts as a bridge between Nest.js Logger and log calls originating + * from platform libraries. It forwards logs to NestLogger, allowing centralization + * (e.g., sending to Axiom) and adds an optional prefix for context. + */ +@Injectable({ scope: Scope.TRANSIENT }) // TRANSIENT ensures getSubLogger provides truly independent instances if needed elsewhere +export class Logger { + // Use NestLogger for the actual logging output + private readonly nestLogger = new NestLogger("LoggerBridge"); + // Prefix to add to messages for this instance + private prefix = ""; + + // Add a public `settings` property, typed with the interface + public settings: IMyLoggerSettings; + + // Define default settings + private static readonly defaultSettings: IMyLoggerSettings = { + minLevel: process?.env?.LOGGER_BRIDGE_LOG_LEVEL + ? Number(process.env.LOGGER_BRIDGE_LOG_LEVEL) + : LogLevel.INFO, // Default to INFO level + displayTimestamp: true, + logFormat: "pretty", + }; + + constructor(userSettings?: Partial) { + // Merge default settings with user-provided settings + // User settings override defaults + this.settings = { + ...Logger.defaultSettings, + ...userSettings, + }; + } + + /** + * Creates a new LoggerBridge instance with a specific prefix. + * Useful for providing context from different parts of a library. + * @param options - Configuration for the sub-logger. + * @param options.prefix - An array of strings to join as the log prefix. + * @returns A new LoggerBridge instance with the specified prefix. + */ + getSubLogger(options: { prefix?: string[] }): Logger { + const newLogger = new Logger(); + // Set the prefix for the *new* instance + newLogger.prefix = options?.prefix?.join(" ") ?? ""; + return newLogger; + } + + // --- Public logging methods --- + + info(...args: any[]) { + this.settings.minLevel <= 1 && this.logInternal("log", ...args); + } + + warn(...args: any[]) { + this.settings.minLevel <= 2 && this.logInternal("warn", ...args); + } + + error(...args: any[]) { + this.settings.minLevel <= 3 && this.logInternal("error", ...args); + } + + debug(...args: any[]) { + this.settings.minLevel === 0 && this.logInternal("debug", ...args); + } + + trace(...args: any[]) { + this.settings.minLevel === 0 && this.logInternal("verbose", ...args); + } + + fatal(...args: any[]) { + // Prepend FATAL: to the message and log as error + const fatalMessage = `fatal: ${this.formatArgsAsString(args)}`; + this.settings.minLevel <= 3 && this.logInternal("error", fatalMessage); + } + + silly(...args: any[]) { + const sillyMessage = `silly: ${this.formatArgsAsString(args)}`; + this.settings.minLevel === 0 && this.logInternal("verbose", sillyMessage); + } + + // --- Internal logging implementation --- + + /** Formats arguments into a single string */ + private formatArgsAsString(args: any[]): string { + return args + .map((arg) => { + if (typeof arg === "string") { + return arg; + } + // Attempt to stringify non-string arguments + try { + return JSON.stringify(arg); + } catch (e) { + return "[Unserializable Object]"; + } + }) + .join(" "); // Use space as separator, adjust if needed + } + + /** Central method to forward logs to NestLogger */ + private logInternal(level: "log" | "warn" | "error" | "debug" | "verbose", ...args: any[]) { + try { + // Format message from potentially multiple arguments + const formattedMessage = this.formatArgsAsString(args); + + // Prepend the prefix if it exists + const message = this.prefix ? `${this.prefix} ${formattedMessage}` : formattedMessage; + + // Call the corresponding NestLogger method + switch (level) { + case "log": + this.nestLogger.log(message); + break; + case "warn": + this.nestLogger.warn(message); + break; + case "error": + this.nestLogger.error(message); + break; + case "debug": + this.nestLogger.debug(message); + break; + case "verbose": + this.nestLogger.verbose(message); + break; + } + } catch (err) { + this.nestLogger.error( + `Could not bridge log message. Error: ${err instanceof Error ? err.message : String(err)}` + ); + } + } +} diff --git a/packages/platform/libraries/vite.config.js b/packages/platform/libraries/vite.config.js index 9b1b882a73..0b56b0596c 100644 --- a/packages/platform/libraries/vite.config.js +++ b/packages/platform/libraries/vite.config.js @@ -99,7 +99,6 @@ export default defineConfig({ "dayjs/plugin/timezone.js", "dayjs/plugin/toArray.js", "dayjs/plugin/utc.js", - "tslog", "@prisma/extension-accelerate", "@ewsjs/xhr", "next-i18next/serverSideTranslations", @@ -191,7 +190,8 @@ export default defineConfig({ "@calcom/prisma/client": resolve("../../../node_modules/.prisma/client"), "@calcom/platform-constants": path.resolve(__dirname, "../constants/index.ts"), "@calcom/platform-types": path.resolve(__dirname, "../types/index.ts"), - "@calcom/platform-utils": path.resolve(__dirname, "../constants/index.ts"), + // eslint-disable-next-line prettier/prettier + tslog: path.resolve(__dirname, "../../../apps/api/v2/src/lib/logger.bridge.ts"), }, }, });