UNPKG

@bitrix24/b24jssdk

Version:

Bitrix24 REST API JavaScript SDK

157 lines (154 loc) 6.09 kB
/** * @package @bitrix24/b24jssdk * @version 2.2.0 * @copyright (c) 2026 Bitrix24 * @license MIT * @see https://github.com/bitrix24/b24jssdk * @see https://bitrix24.github.io/b24jssdk/ */ import { LogLevel } from '../types/logger.mjs'; import { AbstractLogger } from './abstract-logger.mjs'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); class Logger extends AbstractLogger { static { __name(this, "Logger"); } channel; handlers = []; processors = []; constructor(channel) { super(); this.channel = channel; } // region static methods for creation //// static create(channel) { return new Logger(channel); } // endregion //// // region config //// pushHandler(handler) { this.handlers.push(handler); return this; } popHandler() { return this.handlers.pop() || null; } setHandlers(handlers) { this.handlers = handlers; return this; } pushProcessor(processor) { this.processors.push(processor); return this; } // endregion //// /** * **Never throws and never rejects.** Logging is a side channel: a failure in * it must degrade observability, not the operation being observed. Every * callsite in the SDK invokes this without `await` (`this.getLogger().info(…)` * as a statement), so a rejected promise would surface as an *unhandled * rejection* — which terminates the Node process by default. A handler doing * network or file I/O (Telegram, a stream, a third-party adapter) rejects for * ordinary operational reasons, so that path is reachable in normal operation, * not just in principle (#346). * * A processor or handler that fails is skipped and reported via * {@link reportLoggingFailure}; the remaining handlers still receive the * record. * * This covers failures *inside* the logger. It does not cover an exception * raised while a caller builds its log arguments — those are evaluated eagerly * at the callsite, before `log()` is reached (see `truncateForLog`, #338). * * ### Deliberately outside this guarantee * * Three gaps sit outside `log()` and were each weighed and left open on * purpose (#346). They are recorded here so they are not re-opened as * oversights: * * 1. **A third-party `LoggerInterface` is not isolated.** This guarantee * belongs to this class, not to the interface. Every SDK callsite is * written `…info(…).catch(() => {})`, which absorbs a *rejected promise*; * an implementation that throws *synchronously*, before returning one, * escapes into the caller. `setLogger(...)` warns about the shape it can * check without calling anything (see `warnOnNonPromiseLogger`); returning * promises is the implementor's side of the contract. Wrapping every * installed logger defensively was considered and rejected: it would make * the SDK responsible for code it does not own, on every one of ~94 * callsites, to cover a case TypeScript already rejects at compile time. * * 2. **A handler that fails forever is never detached.** Each failure is * reported, every time — see {@link reportLoggingFailure}. Auto-detaching * after N failures was considered and rejected: it silently changes a * configuration the application made, and "N failures" is a policy the SDK * has no basis to pick on the application's behalf. * * 3. **The synchronous half — argument construction — stays the caller's.** * Making it total would mean wrapping the argument list at every callsite, * which trades a narrow, findable failure (#338 was one expression in one * helper) for noise at every call. Individual helpers on the hot path are * made total instead, as `truncateForLog` was. * * @inheritDoc */ async log(level, message, context) { const record = { channel: this.channel, level, levelName: LogLevel[level], message, context: context ?? {}, extra: {}, timestamp: /* @__PURE__ */ new Date() }; let processedRecord = record; for (const processor of this.processors) { try { processedRecord = processor(processedRecord); } catch (error) { this.reportLoggingFailure(processor, error); } } for (const handler of this.handlers) { try { if (!handler.isHandling(level)) { continue; } const handled = await handler.handle(processedRecord); if (handled && !handler.shouldBubble()) { break; } } catch (error) { this.reportLoggingFailure(handler, error); } } } /** * Report a processor/handler that threw. * * Reported on every failure, deliberately: suppressing repeats would hide how * often a sink is failing, and a sink that has been broken for an hour looks * identical to one that failed once. The volume is the signal — if it is * noisy, the sink is failing that often. Filtering belongs to whoever reads * the output, not to the SDK. * * `console` is used rather than the logger — routing a logging failure back * through the logger that just failed is how this turns into recursion. * * The handler is **not** detached, however many times it fails. Doing so would * silently discard part of a configuration the application built, and the * threshold that would trigger it is a policy call the SDK cannot make for the * application. A sink that is broken stays wired and stays loud; whoever reads * the output decides what to do about it (#346). */ reportLoggingFailure(source, error) { const name = source?.constructor?.name ?? "processor"; console.warn( `[b24jssdk] logger channel "${this.channel}": ${name} failed; the record was skipped. Logging continues through the remaining handlers, and the operation being logged is unaffected.`, error ); } } export { Logger }; //# sourceMappingURL=logger.mjs.map