UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

151 lines 6.39 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { definePlugin, onError, onRequest, onResponse } from "h3"; import { AsyncLocalStorage } from "node:async_hooks"; import { performance } from "node:perf_hooks"; import { ApitallyClient } from "../common/client.js"; import { consumerFromStringOrObject } from "../common/consumerRegistry.js"; import { mergeHeaders, parseContentLength } from "../common/headers.js"; import { convertHeaders } from "../common/requestLogger.js"; import { captureResponse } from "../common/response.js"; import { patchConsole, patchWinston } from "../loggers/index.js"; import { getAppInfo } from "./utils.js"; const REQUEST_TIMESTAMP_SYMBOL = Symbol("apitally.requestTimestamp"); const REQUEST_BODY_SYMBOL = Symbol("apitally.requestBody"); const jsonHeaders = new Headers({ "content-type": "application/json;charset=UTF-8" }); const apitallyPlugin = definePlugin((app, config) => { const client = new ApitallyClient(config); const logsContext = new AsyncLocalStorage(); const setStartupData = /* @__PURE__ */ __name((attempt = 1) => { const appInfo = getAppInfo(app, config.appVersion); if (appInfo.paths.length > 0 || attempt >= 10) { client.setStartupData(appInfo); client.startSync(); } else { setTimeout(() => setStartupData(attempt + 1), 500); } }, "setStartupData"); setTimeout(() => setStartupData(), 500); if (client.requestLogger.config.captureLogs) { patchConsole(logsContext); patchWinston(logsContext); } const handleResponse = /* @__PURE__ */ __name(async (event, response, error) => { var _a, _b; if (event.req.method.toUpperCase() === "OPTIONS") { return response; } const startTime = event.context[REQUEST_TIMESTAMP_SYMBOL]; const path = (_a = event.context.matchedRoute) == null ? void 0 : _a.route; const consumer = getConsumer(event); client.consumerRegistry.addOrUpdateConsumer(consumer); if (!response) { response = new Response(null, { status: (error == null ? void 0 : error.status) || 500, statusText: (error == null ? void 0 : error.statusText) || "Internal Server Error", headers: (error == null ? void 0 : error.headers) ? mergeHeaders(jsonHeaders, error.headers) : jsonHeaders }); } const [newResponse, responsePromise] = captureResponse(response, { captureBody: client.requestLogger.enabled && client.requestLogger.config.logResponseBody, maxBodySize: client.requestLogger.maxBodySize }); responsePromise.then(async (capturedResponse) => { const responseTime = startTime ? performance.now() - startTime : 0; const responseSize = capturedResponse.completed ? capturedResponse.size : void 0; const requestSize = parseContentLength(event.req.headers.get("content-length")); if (path) { client.requestCounter.addRequest({ consumer: consumer == null ? void 0 : consumer.identifier, method: event.req.method, path, statusCode: response.status, responseTime, requestSize, responseSize }); } if (client.requestLogger.enabled) { const logs = logsContext.getStore(); client.requestLogger.logRequest({ timestamp: (Date.now() - responseTime) / 1e3, method: event.req.method, path, url: event.req.url, headers: convertHeaders(Object.fromEntries(event.req.headers.entries())), size: requestSize, consumer: consumer == null ? void 0 : consumer.identifier, body: event.context[REQUEST_BODY_SYMBOL] }, { statusCode: response.status, responseTime: responseTime / 1e3, headers: convertHeaders(Object.fromEntries(response.headers.entries())), size: responseSize, body: capturedResponse.body }, (error == null ? void 0 : error.cause) instanceof Error ? error.cause : void 0, logs); } }); if (path && (error == null ? void 0 : error.status) === 400 && error.data && error.data.name === "ZodError") { const zodError = error.data; (_b = zodError.issues) == null ? void 0 : _b.forEach((issue) => { client.validationErrorCounter.addValidationError({ consumer: consumer == null ? void 0 : consumer.identifier, method: event.req.method, path, loc: issue.path.join("."), msg: issue.message, type: issue.code }); }); } if (path && (error == null ? void 0 : error.status) === 500 && error.cause instanceof Error) { client.serverErrorCounter.addServerError({ consumer: consumer == null ? void 0 : consumer.identifier, method: event.req.method, path, type: error.cause.name, msg: error.cause.message, traceback: error.cause.stack || "" }); } return newResponse; }, "handleResponse"); app.use(onRequest(async (event) => { logsContext.enterWith([]); event.context[REQUEST_TIMESTAMP_SYMBOL] = performance.now(); const requestContentType = event.req.headers.get("content-type"); const requestSize = parseContentLength(event.req.headers.get("content-length")) ?? 0; if (client.requestLogger.enabled && client.requestLogger.config.logRequestBody && client.requestLogger.isSupportedContentType(requestContentType) && requestSize <= client.requestLogger.maxBodySize) { const clonedRequest = event.req.clone(); const requestBody = Buffer.from(await clonedRequest.arrayBuffer()); event.context[REQUEST_BODY_SYMBOL] = requestBody; } })).use(onResponse((response, event) => { if (client.isEnabled()) { return handleResponse(event, response, void 0); } })).use(onError((error, event) => { if (client.isEnabled()) { handleResponse(event, void 0, error); } })); }); function setConsumer(event, consumer) { event.context.apitallyConsumer = consumer || void 0; } __name(setConsumer, "setConsumer"); function getConsumer(event) { const consumer = event.context.apitallyConsumer; if (consumer) { return consumerFromStringOrObject(consumer); } return null; } __name(getConsumer, "getConsumer"); export { apitallyPlugin, setConsumer }; //# sourceMappingURL=plugin.js.map