UNPKG

@neodx/log

Version:

A lightweight universal logging framework

1 lines 14.3 kB
{"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","handleEnd","ctx","error","res","responseTime","err","statusCode","Error","condition","errorMeta","debug","responseMeta","done","httpLogger","req","next","id","startTime","Date","now","fork","meta","requestId","baseContext","log","HTTP_LOG_START_TIME_SYMBOL","handleResponse","removeListener","on","info","createMessageFormatter","fn","ignoreWritableEnded","delimiter","_","requestMessage","formatIncomingMessage","writableEnded","identity","msg","greenBright","formatResponseTime","redBright","italic","formatOutgoingMessageStatus","args"],"mappings":"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"}