apitally
Version:
Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.
1 lines • 10.9 kB
Source Map (JSON)
{"version":3,"sources":["../../src/adonisjs/middleware.ts"],"sourcesContent":["import { HttpContext } from \"@adonisjs/core/http\";\nimport { NextFn } from \"@adonisjs/core/types/http\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { OutgoingHttpHeaders } from \"node:http\";\nimport { performance } from \"node:perf_hooks\";\n\nimport type { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport type { ApitallyConsumer } from \"../common/types.js\";\n\ndeclare module \"@adonisjs/core/http\" {\n interface HttpContext {\n apitallyConsumer?: ApitallyConsumer | string;\n apitallyError?: Error;\n }\n}\n\nexport default class ApitallyMiddleware {\n async handle(ctx: HttpContext, next: NextFn) {\n const client: ApitallyClient =\n await ctx.containerResolver.make(\"apitallyClient\");\n const logsContext: AsyncLocalStorage<LogRecord[]> =\n await ctx.containerResolver.make(\"apitallyLogsContext\");\n\n if (\n !client.isEnabled() ||\n ctx.request.method().toUpperCase() === \"OPTIONS\"\n ) {\n await next();\n return;\n }\n\n return logsContext.run([], async () => {\n const path = ctx.route?.pattern;\n const timestamp = Date.now() / 1000;\n const startTime = performance.now();\n\n const spanHandle = client.spanCollector.startSpan();\n await spanHandle.runInContext(next);\n\n const responseTime = performance.now() - startTime;\n spanHandle.setName(`${ctx.request.method()} ${path}`);\n const spans = spanHandle.end();\n const traceId = spanHandle.traceId;\n\n const requestSize = parseContentLength(\n ctx.request.header(\"content-length\"),\n );\n const requestContentType = ctx.request.header(\"content-type\")?.toString();\n let responseStatus = ctx.response.getStatus();\n let responseHeaders = ctx.response.getHeaders();\n let responseSize: number | undefined;\n let responseContentType: string | undefined;\n\n const consumer = ctx.apitallyConsumer\n ? consumerFromStringOrObject(ctx.apitallyConsumer)\n : null;\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n const onWriteHead = (\n statusCode: number,\n headers: OutgoingHttpHeaders,\n ) => {\n responseStatus = statusCode;\n responseHeaders = headers;\n responseSize = parseContentLength(headers[\"content-length\"]);\n responseContentType = headers[\"content-type\"]?.toString();\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: ctx.request.method(),\n path,\n statusCode: responseStatus,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n responseStatus === 422 &&\n ctx.apitallyError &&\n \"code\" in ctx.apitallyError &&\n \"messages\" in ctx.apitallyError &&\n ctx.apitallyError.code === \"E_VALIDATION_ERROR\" &&\n Array.isArray(ctx.apitallyError.messages)\n ) {\n ctx.apitallyError.messages.forEach((message) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: ctx.request.method(),\n path,\n loc: message.field,\n msg: message.message,\n type: message.rule,\n });\n });\n }\n\n if (responseStatus === 500 && ctx.apitallyError) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: ctx.request.method(),\n path,\n type: ctx.apitallyError.name,\n msg: ctx.apitallyError.message,\n traceback: ctx.apitallyError.stack || \"\",\n });\n }\n }\n };\n\n // Capture the final status code and response headers just before they are sent\n const originalWriteHead = ctx.response.response.writeHead;\n ctx.response.response.writeHead = (...args: any) => {\n originalWriteHead.apply(ctx.response.response, args);\n onWriteHead(args[0], typeof args[1] === \"string\" ? args[2] : args[1]);\n return ctx.response.response;\n };\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n\n const onEnd = (chunk: any) => {\n const requestBody =\n client.requestLogger.config.logRequestBody &&\n client.requestLogger.isSupportedContentType(requestContentType)\n ? ctx.request.raw()\n : undefined;\n const responseBody =\n client.requestLogger.config.logResponseBody &&\n client.requestLogger.isSupportedContentType(responseContentType)\n ? chunk\n : undefined;\n\n client.requestLogger.logRequest(\n {\n timestamp,\n method: ctx.request.method(),\n path,\n url: ctx.request.completeUrl(true),\n headers: convertHeaders(ctx.request.headers()),\n size: requestSize,\n consumer: consumer?.identifier,\n body: requestBody ? Buffer.from(requestBody) : undefined,\n },\n {\n statusCode: responseStatus,\n responseTime: responseTime / 1000,\n headers: convertHeaders(responseHeaders),\n size: responseSize,\n body: responseBody ? Buffer.from(responseBody) : undefined,\n },\n ctx.apitallyError,\n logs,\n spans,\n traceId,\n );\n };\n\n // Capture the final response body just before it is sent\n const originalEnd = ctx.response.response.end;\n ctx.response.response.end = (...args: any) => {\n originalEnd.apply(ctx.response.response, args);\n onEnd(typeof args[0] !== \"function\" ? args[0] : undefined);\n return ctx.response.response;\n };\n }\n });\n }\n}\n"],"mappings":"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","names":["ApitallyMiddleware","handle","ctx","next","client","containerResolver","make","logsContext","isEnabled","request","method","toUpperCase","run","path","route","pattern","timestamp","Date","now","startTime","performance","spanHandle","spanCollector","startSpan","runInContext","responseTime","setName","spans","end","traceId","requestSize","parseContentLength","header","requestContentType","toString","responseStatus","response","getStatus","responseHeaders","getHeaders","responseSize","responseContentType","consumer","apitallyConsumer","consumerFromStringOrObject","consumerRegistry","addOrUpdateConsumer","onWriteHead","statusCode","headers","_a","requestCounter","addRequest","identifier","apitallyError","code","Array","isArray","messages","forEach","message","validationErrorCounter","addValidationError","loc","field","msg","type","rule","serverErrorCounter","addServerError","name","traceback","stack","originalWriteHead","writeHead","args","apply","requestLogger","enabled","logs","getStore","onEnd","chunk","requestBody","config","logRequestBody","isSupportedContentType","raw","undefined","responseBody","logResponseBody","logRequest","url","completeUrl","convertHeaders","size","body","Buffer","from","originalEnd"]}