{"version":3,"file":"index.cjs","names":["defaultColors","createLogger","createRequestIdGenerator","HTTP_LOG_START_TIME_SYMBOL","formatIncomingMessage","identity","formatResponseTime","formatOutgoingMessageStatus"],"sources":["../../src/http/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Colors } from '@neodx/colors';\nimport { colors as defaultColors } from '@neodx/colors';\nimport { identity } from '@neodx/std';\nimport type { IncomingMessage, OutgoingMessage } from 'http';\nimport type { Logger } from '../core/types';\nimport { createLogger } from '../node';\nimport {\n  createRequestIdGenerator,\n  formatIncomingMessage,\n  formatOutgoingMessageStatus,\n  formatResponseTime,\n  HTTP_LOG_START_TIME_SYMBOL\n} from './utils';\n\nexport interface HttpLoggerParams<\n  Req extends IncomingMessage = IncomingMessage,\n  Res extends OutgoingMessage = OutgoingMessage\n> {\n  /**\n   * Custom logger instance.\n   * @default createLogger()\n   */\n  logger?: Logger<HttpLogLevels>;\n  /**\n   * Custom colors instance\n   * @see `@neodx/colors`\n   */\n  colors?: Colors;\n  /**\n   * If `true`, the logger will only log the pre-formatted message without any additional metadata.\n   * @default process.env.NODE_ENV === 'development'\n   */\n  simple?: boolean;\n  /**\n   * Optional function to extract/create request ID.\n   * @default built-in simple safe number counter\n   */\n  getRequestId?: (req: Req, res: Res) => string | number;\n\n  // ===\n  // Metadata and formatting\n  // ===\n\n  /**\n   * Extract shared metadata for every produced log\n   */\n  getMeta?: (req: Req, res: Res) => Record<string, unknown>;\n  /**\n   * Extract metadata for request logs\n   */\n  getRequestMeta?: (ctx: HttpResponseContext<Req, Res>) => Record<string, unknown>;\n  /**\n   * Custom incoming request message formatter\n   */\n  getRequestMessage?: (ctx: HttpResponseContext<Req, Res>) => string;\n  /**\n   * Extract metadata for success response logs\n   */\n  getResponseMeta?: (ctx: HttpResponseContext<Req, Res>) => Record<string, unknown>;\n  /**\n   * Custom success response message formatter\n   */\n  getResponseMessage?: (ctx: HttpResponseContext<Req, Res>) => string;\n  /**\n   * Extract metadata for error response logs\n   */\n  getErrorMeta?: (ctx: HttpResponseContext<Req, Res>) => Record<string, unknown>;\n  /**\n   * Custom error response message formatter\n   */\n  getErrorMessage?: (ctx: HttpResponseContext<Req, Res>) => string;\n\n  // ===\n  // Control logging behavior\n  // ===\n\n  /**\n   * Whether to log anything at all.\n   * @default true\n   */\n  shouldLog?: boolean | ((req: Req, res: Res) => boolean);\n  /**\n   * Prevents logging of errors.\n   * @default true\n   */\n  shouldLogError?: boolean | ((ctx: HttpResponseContext<Req, Res>) => boolean);\n  /**\n   * Prevents built-in logging of requests.\n   * DISABLED BY DEFAULT, because it can be very verbose.\n   * @default false\n   */\n  shouldLogRequest?: boolean | ((ctx: HttpResponseContext<Req, Res>) => boolean);\n  /**\n   * Prevents built-in logging of responses.\n   * @default true\n   */\n  shouldLogResponse?: boolean | ((ctx: HttpResponseContext<Req, Res>) => boolean);\n}\n\nexport interface HttpLoggerMetaKeys {\n  req: string;\n  res: string;\n  err: string;\n  requestId: string;\n  responseTime: string;\n}\n\nexport interface HttpResponseContext<\n  Req extends IncomingMessage = IncomingMessage,\n  Res extends OutgoingMessage = OutgoingMessage\n> {\n  req: Req;\n  res: Res;\n  error?: Error;\n  logger: Logger<HttpLogLevels>;\n  colors: Colors;\n  responseTime: number;\n}\n\nexport type HttpRequestId = string | number;\nexport type HttpLogLevels = 'debug' | 'error' | 'info' | 'done';\n\ndeclare module 'http' {\n  interface IncomingMessage {\n    id: HttpRequestId;\n    log: Logger<HttpLogLevels>;\n  }\n\n  interface ServerResponse {\n    err?: Error | undefined;\n  }\n\n  interface OutgoingMessage {\n    [HTTP_LOG_START_TIME_SYMBOL]: number;\n  }\n}\n\nexport function createHttpLogger<\n  Req extends IncomingMessage = IncomingMessage,\n  Res extends OutgoingMessage = OutgoingMessage\n>({\n  simple = process.env.NODE_ENV === 'development',\n  colors = defaultColors,\n  logger: rootLogger = createLogger(),\n  getRequestId = createRequestIdGenerator(),\n\n  getMeta,\n  getErrorMeta,\n  getErrorMessage = simple ? simpleErrorMessage : defaultErrorMessage,\n  getRequestMeta,\n  getRequestMessage = defaultRequestMessage,\n  getResponseMeta,\n  getResponseMessage = defaultResponseMessage,\n\n  shouldLog = true,\n  shouldLogError = true,\n  shouldLogRequest = false,\n  shouldLogResponse = true\n}: HttpLoggerParams<Req, Res> = {}) {\n  function handleEnd(ctx: HttpResponseContext<Req, Res>) {\n    const { error, logger, res, responseTime } = ctx;\n    const err =\n      error ||\n      (res as any).err ||\n      ((res as any).statusCode >= 500 &&\n        new Error('failed with status code ' + (res as any).statusCode));\n\n    if (err) {\n      if (!condition(shouldLogError, ctx)) return;\n      const errorMeta = getErrorMeta?.(ctx) ?? {\n        err,\n        res,\n        responseTime\n      };\n\n      if (simple) {\n        logger.error({ err }, getErrorMessage(ctx));\n        logger.debug(errorMeta, 'Error details:');\n        return;\n      }\n      logger.error(errorMeta, getErrorMessage(ctx));\n      return;\n    }\n    if (!condition(shouldLogResponse, ctx)) return;\n    const responseMeta = getResponseMeta?.(ctx) ?? {\n      res,\n      responseTime\n    };\n    if (simple) {\n      logger.done(getResponseMessage(ctx));\n      logger.debug(responseMeta, 'Response details:');\n      return;\n    }\n    logger.done(responseMeta, getResponseMessage(ctx));\n  }\n\n  return function httpLogger(req: Req, res: Res, next?: () => void) {\n    if (!condition(shouldLog, req, res)) return next?.();\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    req.id ??= getRequestId(req, res);\n\n    const startTime = Date.now();\n    const logger = rootLogger.fork({\n      meta: {\n        ...rootLogger.meta,\n        ...getMeta?.(req, res),\n        ...(!simple && {\n          requestId: req.id,\n          req\n        })\n      }\n    });\n    const baseContext = { req, res, logger, colors, responseTime: 0 };\n\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n    req.log ??= logger;\n    res[HTTP_LOG_START_TIME_SYMBOL] = startTime;\n\n    const handleResponse = (error?: Error) => {\n      res.removeListener('finish', handleResponse);\n      res.removeListener('close', handleResponse);\n      res.removeListener('error', handleResponse);\n      return handleEnd({\n        ...baseContext,\n        error,\n        responseTime: Date.now() - startTime\n      });\n    };\n\n    res.on('error', handleResponse);\n    res.on('close', handleResponse);\n    res.on('finish', handleResponse);\n\n    if (condition(shouldLogRequest, baseContext)) {\n      if (!simple) {\n        logger.info(getRequestMeta?.(baseContext) ?? {}, getRequestMessage(baseContext));\n      } else {\n        logger.info(getRequestMessage(baseContext));\n        if (getRequestMeta) {\n          logger.debug(getRequestMeta(baseContext), 'Request details:');\n        }\n      }\n    }\n    next?.();\n  };\n}\n\nconst createMessageFormatter =\n  (\n    fn: (requestMessage: string, ctx: HttpResponseContext) => string,\n    { ignoreWritableEnded = false, delimiter = (_: HttpResponseContext): string => ' ' } = {}\n  ) =>\n  (ctx: HttpResponseContext) => {\n    const requestMessage = formatIncomingMessage(ctx.req, ctx.colors, delimiter(ctx));\n\n    return ignoreWritableEnded || ctx.res.writableEnded\n      ? fn(requestMessage, ctx)\n      : `(aborted) ${requestMessage}`;\n  };\n\nconst defaultRequestMessage = createMessageFormatter(identity, {\n  ignoreWritableEnded: true\n});\nconst defaultResponseMessage = createMessageFormatter(\n  (msg, ctx) => `${ctx.colors.greenBright(formatResponseTime(ctx.responseTime))} ${msg}`\n);\nconst defaultErrorMessage = createMessageFormatter(\n  (msg, ctx) => `${ctx.colors.redBright(formatResponseTime(ctx.responseTime))} ${msg}`\n);\nconst simpleErrorMessage = createMessageFormatter(\n  (msg, ctx) => `${ctx.colors.redBright(formatResponseTime(ctx.responseTime))} ${msg}`,\n  {\n    delimiter: ({ colors, res }) => colors.italic(` (${formatOutgoingMessageStatus(res)}) `)\n  }\n);\n\nconst condition = <Args extends unknown[]>(\n  condition: boolean | ((...args: Args) => boolean),\n  ...args: Args\n) => (typeof condition === 'function' ? condition(...args) : condition);\n"],"mappings":"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"}