{"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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"}