{"version":3,"file":"index.mjs","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"],"names":["createHttpLogger","simple","process","env","NODE_ENV","colors","defaultColors","logger","rootLogger","createLogger","getRequestId","createRequestIdGenerator","getMeta","getErrorMeta","getErrorMessage","simpleErrorMessage","defaultErrorMessage","getRequestMeta","getRequestMessage","defaultRequestMessage","getResponseMeta","getResponseMessage","defaultResponseMessage","shouldLog","shouldLogError","shouldLogRequest","shouldLogResponse","req","res","next","condition","id","startTime","Date","now","fork","meta","requestId","baseContext","responseTime","log","HTTP_LOG_START_TIME_SYMBOL","handleResponse","error","removeListener","handleEnd","ctx","err","statusCode","Error","errorMeta","debug","responseMeta","done","on","info","createMessageFormatter","fn","ignoreWritableEnded","delimiter","_","requestMessage","formatIncomingMessage","writableEnded","identity","msg","greenBright","formatResponseTime","redBright","italic","formatOutgoingMessageStatus","args"],"mappings":"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"}