@bitrix24/b24jssdk
Version:
Bitrix24 REST API JavaScript SDK
1 lines • 10.3 kB
Source Map (JSON)
{"version":3,"file":"logger.cjs","sources":["../../../src/logger/logger.ts"],"sourcesContent":["import type { Handler, Processor, LogRecord, LogLevelName, LoggerInterface } from '../types/logger'\nimport { LogLevel } from '../types/logger'\nimport { AbstractLogger } from './abstract-logger'\n\n/**\n * A logger created according to the principles of `Monolog`\n *\n * @link https://github.com/Seldaek/monolog\n */\nexport class Logger extends AbstractLogger implements LoggerInterface {\n private readonly channel: string\n private handlers: Handler[] = []\n private processors: Processor[] = []\n\n constructor(channel: string) {\n super()\n this.channel = channel\n }\n\n // region static methods for creation ////\n static create(channel: string): Logger {\n return new Logger(channel)\n }\n // endregion ////\n\n // region config ////\n public pushHandler(handler: Handler): this {\n this.handlers.push(handler)\n return this\n }\n\n public popHandler(): Handler | null {\n return this.handlers.pop() || null\n }\n\n public setHandlers(handlers: Handler[]): this {\n this.handlers = handlers\n return this\n }\n\n public pushProcessor(processor: Processor): this {\n this.processors.push(processor)\n return this\n }\n // endregion ////\n\n /**\n * **Never throws and never rejects.** Logging is a side channel: a failure in\n * it must degrade observability, not the operation being observed. Every\n * callsite in the SDK invokes this without `await` (`this.getLogger().info(…)`\n * as a statement), so a rejected promise would surface as an *unhandled\n * rejection* — which terminates the Node process by default. A handler doing\n * network or file I/O (Telegram, a stream, a third-party adapter) rejects for\n * ordinary operational reasons, so that path is reachable in normal operation,\n * not just in principle (#346).\n *\n * A processor or handler that fails is skipped and reported via\n * {@link reportLoggingFailure}; the remaining handlers still receive the\n * record.\n *\n * This covers failures *inside* the logger. It does not cover an exception\n * raised while a caller builds its log arguments — those are evaluated eagerly\n * at the callsite, before `log()` is reached (see `truncateForLog`, #338).\n *\n * ### Deliberately outside this guarantee\n *\n * Three gaps sit outside `log()` and were each weighed and left open on\n * purpose (#346). They are recorded here so they are not re-opened as\n * oversights:\n *\n * 1. **A third-party `LoggerInterface` is not isolated.** This guarantee\n * belongs to this class, not to the interface. Every SDK callsite is\n * written `…info(…).catch(() => {})`, which absorbs a *rejected promise*;\n * an implementation that throws *synchronously*, before returning one,\n * escapes into the caller. `setLogger(...)` warns about the shape it can\n * check without calling anything (see `warnOnNonPromiseLogger`); returning\n * promises is the implementor's side of the contract. Wrapping every\n * installed logger defensively was considered and rejected: it would make\n * the SDK responsible for code it does not own, on every one of ~94\n * callsites, to cover a case TypeScript already rejects at compile time.\n *\n * 2. **A handler that fails forever is never detached.** Each failure is\n * reported, every time — see {@link reportLoggingFailure}. Auto-detaching\n * after N failures was considered and rejected: it silently changes a\n * configuration the application made, and \"N failures\" is a policy the SDK\n * has no basis to pick on the application's behalf.\n *\n * 3. **The synchronous half — argument construction — stays the caller's.**\n * Making it total would mean wrapping the argument list at every callsite,\n * which trades a narrow, findable failure (#338 was one expression in one\n * helper) for noise at every call. Individual helpers on the hot path are\n * made total instead, as `truncateForLog` was.\n *\n * @inheritDoc\n */\n public async log(level: LogLevel, message: string, context?: Record<string, any>): Promise<void> {\n const record: LogRecord = {\n channel: this.channel,\n level,\n levelName: LogLevel[level] as LogLevelName,\n message,\n context: context ?? {},\n extra: {},\n timestamp: new Date()\n }\n\n // Using processors. A processor that throws is skipped rather than allowed\n // to abort the record: it is an enrichment step (pid, memory usage), so\n // losing its contribution is strictly better than losing the log line — and,\n // per the isolation note on `log()`, better than taking the caller down. The\n // record keeps whatever the processors before it already added.\n let processedRecord = record\n for (const processor of this.processors) {\n try {\n processedRecord = processor(processedRecord)\n } catch (error) {\n this.reportLoggingFailure(processor, error)\n }\n }\n\n // Pass the record to the handlers. The whole interaction with a handler is\n // inside the `try`, not just `handle()`: `isHandling()` and `shouldBubble()`\n // are trivial predicates in every handler the SDK ships (`AbstractHandler`\n // compares two numbers), but they are interface methods a third party\n // implements, and the guarantee on `log()` is unconditional. Guarding only\n // the method that happens to fail today would make that guarantee depend on\n // which part of someone else's handler misbehaves.\n for (const handler of this.handlers) {\n try {\n if (!handler.isHandling(level)) {\n continue\n }\n\n // The handler returns a boolean indicating whether it was processed successfully.\n // `await` covers both a synchronous throw and a rejected promise — a\n // handler doing real I/O (Telegram, a stream, a third-party adapter)\n // fails for ordinary operational reasons, and neither form may escape.\n const handled = await handler.handle(processedRecord)\n\n // If the handler has processed the record and should NOT proceed further (bubble: false)\n // break the chain of handlers\n if (handled && !handler.shouldBubble()) {\n break\n }\n } catch (error) {\n // Isolate this handler only: a broken sink must not stop the ones\n // after it from receiving the record.\n this.reportLoggingFailure(handler, error)\n }\n }\n }\n\n /**\n * Report a processor/handler that threw.\n *\n * Reported on every failure, deliberately: suppressing repeats would hide how\n * often a sink is failing, and a sink that has been broken for an hour looks\n * identical to one that failed once. The volume is the signal — if it is\n * noisy, the sink is failing that often. Filtering belongs to whoever reads\n * the output, not to the SDK.\n *\n * `console` is used rather than the logger — routing a logging failure back\n * through the logger that just failed is how this turns into recursion.\n *\n * The handler is **not** detached, however many times it fails. Doing so would\n * silently discard part of a configuration the application built, and the\n * threshold that would trigger it is a policy call the SDK cannot make for the\n * application. A sink that is broken stays wired and stays loud; whoever reads\n * the output decides what to do about it (#346).\n */\n private reportLoggingFailure(source: Processor | Handler, error: unknown): void {\n const name = (source as { constructor?: { name?: string } })?.constructor?.name ?? 'processor'\n console.warn(\n `[b24jssdk] logger channel \"${this.channel}\": ${name} failed; the record was skipped. `\n + `Logging continues through the remaining handlers, and the operation being logged is unaffected.`,\n error\n )\n }\n}\n"],"names":["AbstractLogger","LogLevel"],"mappings":"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"}