apitally 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +3 -3
  2. package/dist/common/client.cjs +37 -21
  3. package/dist/common/client.cjs.map +1 -1
  4. package/dist/common/client.js +37 -21
  5. package/dist/common/client.js.map +1 -1
  6. package/dist/common/requestLogger.cjs +47 -21
  7. package/dist/common/requestLogger.cjs.map +1 -1
  8. package/dist/common/requestLogger.d.cts +2 -0
  9. package/dist/common/requestLogger.d.ts +2 -0
  10. package/dist/common/requestLogger.js +37 -21
  11. package/dist/common/requestLogger.js.map +1 -1
  12. package/dist/express/index.cjs +37 -21
  13. package/dist/express/index.cjs.map +1 -1
  14. package/dist/express/index.js +37 -21
  15. package/dist/express/index.js.map +1 -1
  16. package/dist/express/middleware.cjs +37 -21
  17. package/dist/express/middleware.cjs.map +1 -1
  18. package/dist/express/middleware.js +37 -21
  19. package/dist/express/middleware.js.map +1 -1
  20. package/dist/fastify/index.cjs +37 -21
  21. package/dist/fastify/index.cjs.map +1 -1
  22. package/dist/fastify/index.js +37 -21
  23. package/dist/fastify/index.js.map +1 -1
  24. package/dist/fastify/plugin.cjs +37 -21
  25. package/dist/fastify/plugin.cjs.map +1 -1
  26. package/dist/fastify/plugin.js +37 -21
  27. package/dist/fastify/plugin.js.map +1 -1
  28. package/dist/hono/index.cjs +37 -21
  29. package/dist/hono/index.cjs.map +1 -1
  30. package/dist/hono/index.js +37 -21
  31. package/dist/hono/index.js.map +1 -1
  32. package/dist/hono/middleware.cjs +37 -21
  33. package/dist/hono/middleware.cjs.map +1 -1
  34. package/dist/hono/middleware.js +37 -21
  35. package/dist/hono/middleware.js.map +1 -1
  36. package/dist/koa/index.cjs +37 -21
  37. package/dist/koa/index.cjs.map +1 -1
  38. package/dist/koa/index.js +37 -21
  39. package/dist/koa/index.js.map +1 -1
  40. package/dist/koa/middleware.cjs +37 -21
  41. package/dist/koa/middleware.cjs.map +1 -1
  42. package/dist/koa/middleware.js +37 -21
  43. package/dist/koa/middleware.js.map +1 -1
  44. package/dist/nestjs/index.cjs +37 -21
  45. package/dist/nestjs/index.cjs.map +1 -1
  46. package/dist/nestjs/index.js +37 -21
  47. package/dist/nestjs/index.js.map +1 -1
  48. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/client.ts","../../src/common/consumerRegistry.ts","../../src/common/logging.ts","../../src/common/paramValidation.ts","../../src/common/requestCounter.ts","../../src/common/requestLogger.ts","../../src/common/tempGzipFile.ts","../../src/common/serverErrorCounter.ts","../../src/common/validationErrorCounter.ts"],"sourcesContent":["import { randomUUID } from \"crypto\";\nimport fetchRetry from \"fetch-retry\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: Array<[number, SyncPayload]>;\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n if (!isValidClientId(clientId)) {\n throw new Error(\n `Invalid client ID '${clientId}' (expecting hexadeciaml UUID format)`,\n );\n }\n if (!isValidEnv(env)) {\n throw new Error(\n `Invalid env '${env}' (expecting 1-32 alphanumeric lowercase characters and hyphens only)`,\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = randomUUID();\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(requestLoggingConfig);\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.logger = logger || getLogger();\n\n this.startSync();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n private startSync() {\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n this.sendStartupData();\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n time_offset: 0,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n };\n this.syncDataQueue.push([Date.now(), newPayload]);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const queueItem = this.syncDataQueue.shift();\n if (queueItem) {\n const [time, payload] = queueItem;\n try {\n const timeOffset = Date.now() - time;\n if (timeOffset <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n payload.time_offset = timeOffset / 1000.0; // in seconds\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(queueItem);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: await logFile.getContent(),\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 200;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","import { ApitallyConsumer } from \"./types.js\";\n\nexport const consumerFromStringOrObject = (\n consumer: ApitallyConsumer | string,\n) => {\n if (typeof consumer === \"string\") {\n consumer = String(consumer).trim().substring(0, 128);\n return consumer ? { identifier: consumer } : null;\n } else {\n consumer.identifier = String(consumer.identifier).trim().substring(0, 128);\n consumer.name = consumer.name?.trim().substring(0, 64);\n consumer.group = consumer.group?.trim().substring(0, 64);\n return consumer.identifier ? consumer : null;\n }\n};\n\nexport default class ConsumerRegistry {\n private consumers: Map<string, ApitallyConsumer>;\n private updated: Set<string>;\n\n constructor() {\n this.consumers = new Map();\n this.updated = new Set();\n }\n\n public addOrUpdateConsumer(consumer?: ApitallyConsumer | null) {\n if (!consumer || (!consumer.name && !consumer.group)) {\n return;\n }\n const existing = this.consumers.get(consumer.identifier);\n if (!existing) {\n this.consumers.set(consumer.identifier, consumer);\n this.updated.add(consumer.identifier);\n } else {\n if (consumer.name && consumer.name !== existing.name) {\n existing.name = consumer.name;\n this.updated.add(consumer.identifier);\n }\n if (consumer.group && consumer.group !== existing.group) {\n existing.group = consumer.group;\n this.updated.add(consumer.identifier);\n }\n }\n }\n\n public getAndResetUpdatedConsumers() {\n const data: Array<ApitallyConsumer> = [];\n this.updated.forEach((identifier) => {\n const consumer = this.consumers.get(identifier);\n if (consumer) {\n data.push(consumer);\n }\n });\n this.updated.clear();\n return data;\n }\n}\n","import { createLogger, format, transports } from \"winston\";\n\nexport interface Logger {\n debug: (message: string, meta?: object) => void;\n info: (message: string, meta?: object) => void;\n warn: (message: string, meta?: object) => void;\n error: (message: string, meta?: object) => void;\n}\n\nexport const getLogger = () => {\n return createLogger({\n level: process.env.APITALLY_DEBUG ? \"debug\" : \"warn\",\n format: format.combine(\n format.colorize(),\n format.timestamp(),\n format.printf(\n (info) => `${info.timestamp} ${info.level}: ${info.message}`,\n ),\n ),\n transports: [new transports.Console()],\n });\n};\n","export function isValidClientId(clientId: string): boolean {\n const regexExp =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n return regexExp.test(clientId);\n}\n\nexport function isValidEnv(env: string): boolean {\n const regexExp = /^[\\w-]{1,32}$/;\n return regexExp.test(env);\n}\n","import { RequestInfo, RequestsItem } from \"./types.js\";\n\nexport default class RequestCounter {\n private requestCounts: Map<string, number>;\n private requestSizeSums: Map<string, number>;\n private responseSizeSums: Map<string, number>;\n private responseTimes: Map<string, Map<number, number>>;\n private requestSizes: Map<string, Map<number, number>>;\n private responseSizes: Map<string, Map<number, number>>;\n\n constructor() {\n this.requestCounts = new Map<string, number>();\n this.requestSizeSums = new Map<string, number>();\n this.responseSizeSums = new Map<string, number>();\n this.responseTimes = new Map<string, Map<number, number>>();\n this.requestSizes = new Map<string, Map<number, number>>();\n this.responseSizes = new Map<string, Map<number, number>>();\n }\n\n private getKey(requestInfo: RequestInfo) {\n return [\n requestInfo.consumer || \"\",\n requestInfo.method.toUpperCase(),\n requestInfo.path,\n requestInfo.statusCode,\n ].join(\"|\");\n }\n\n addRequest(requestInfo: RequestInfo) {\n const key = this.getKey(requestInfo);\n\n // Increment request count\n this.requestCounts.set(key, (this.requestCounts.get(key) || 0) + 1);\n\n // Add response time\n if (!this.responseTimes.has(key)) {\n this.responseTimes.set(key, new Map<number, number>());\n }\n const responseTimeMap = this.responseTimes.get(key)!;\n const responseTimeMsBin = Math.floor(requestInfo.responseTime / 10) * 10; // Rounded to nearest 10ms\n responseTimeMap.set(\n responseTimeMsBin,\n (responseTimeMap.get(responseTimeMsBin) || 0) + 1,\n );\n\n // Add request size\n if (requestInfo.requestSize !== undefined) {\n requestInfo.requestSize = Number(requestInfo.requestSize);\n this.requestSizeSums.set(\n key,\n (this.requestSizeSums.get(key) || 0) + requestInfo.requestSize,\n );\n if (!this.requestSizes.has(key)) {\n this.requestSizes.set(key, new Map<number, number>());\n }\n const requestSizeMap = this.requestSizes.get(key)!;\n const requestSizeKbBin = Math.floor(requestInfo.requestSize / 1000); // Rounded down to nearest KB\n requestSizeMap.set(\n requestSizeKbBin,\n (requestSizeMap.get(requestSizeKbBin) || 0) + 1,\n );\n }\n\n // Add response size\n if (requestInfo.responseSize !== undefined) {\n requestInfo.responseSize = Number(requestInfo.responseSize);\n this.responseSizeSums.set(\n key,\n (this.responseSizeSums.get(key) || 0) + requestInfo.responseSize,\n );\n if (!this.responseSizes.has(key)) {\n this.responseSizes.set(key, new Map<number, number>());\n }\n const responseSizeMap = this.responseSizes.get(key)!;\n const responseSizeKbBin = Math.floor(requestInfo.responseSize / 1000); // Rounded down to nearest KB\n responseSizeMap.set(\n responseSizeKbBin,\n (responseSizeMap.get(responseSizeKbBin) || 0) + 1,\n );\n }\n }\n\n getAndResetRequests() {\n const data: Array<RequestsItem> = [];\n this.requestCounts.forEach((count, key) => {\n const [consumer, method, path, statusCodeStr] = key.split(\"|\");\n const responseTimes =\n this.responseTimes.get(key) || new Map<number, number>();\n const requestSizes =\n this.requestSizes.get(key) || new Map<number, number>();\n const responseSizes =\n this.responseSizes.get(key) || new Map<number, number>();\n data.push({\n consumer: consumer || null,\n method,\n path,\n status_code: parseInt(statusCodeStr),\n request_count: count,\n request_size_sum: this.requestSizeSums.get(key) || 0,\n response_size_sum: this.responseSizeSums.get(key) || 0,\n response_times: Object.fromEntries(responseTimes),\n request_sizes: Object.fromEntries(requestSizes),\n response_sizes: Object.fromEntries(responseSizes),\n });\n });\n\n // Reset the counts and times\n this.requestCounts.clear();\n this.requestSizeSums.clear();\n this.responseSizeSums.clear();\n this.responseTimes.clear();\n this.requestSizes.clear();\n this.responseSizes.clear();\n\n return data;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/,\n /\\/_?health[_-]?checks?$/,\n /\\/_?heart[_-]?beats?$/,\n /\\/ping$/,\n /\\/ready$/,\n /\\/live$/,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return (\n contentType !== undefined &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n logRequest(request: Request, response: Response) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n\n if (\n this.shouldExcludePath(path) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(response),\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n return new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n","import type * as Sentry from \"@sentry/node\";\nimport { createHash } from \"crypto\";\n\nimport { ConsumerMethodPath, ServerError, ServerErrorsItem } from \"./types.js\";\n\nconst MAX_MSG_LENGTH = 2048;\nconst MAX_STACKTRACE_LENGTH = 65536;\n\nexport default class ServerErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ServerError>;\n private sentryEventIds: Map<string, string>;\n private sentry: typeof Sentry | undefined;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n this.sentryEventIds = new Map();\n this.tryImportSentry();\n }\n\n public addServerError(serverError: ConsumerMethodPath & ServerError) {\n const key = this.getKey(serverError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, serverError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n this.captureSentryEventId(key);\n }\n\n public getAndResetServerErrors() {\n const data: Array<ServerErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const serverError = this.errorDetails.get(key);\n if (serverError) {\n data.push({\n consumer: serverError.consumer || null,\n method: serverError.method,\n path: serverError.path,\n type: serverError.type,\n msg: this.getTruncatedMessage(serverError.msg),\n traceback: this.getTruncatedStack(serverError.traceback),\n sentry_event_id: this.sentryEventIds.get(key) || null,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(serverError: ConsumerMethodPath & ServerError) {\n const hashInput = [\n serverError.consumer || \"\",\n serverError.method.toUpperCase(),\n serverError.path,\n serverError.type,\n serverError.msg.trim(),\n serverError.traceback.trim(),\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n\n private getTruncatedMessage(msg: string) {\n msg = msg.trim();\n if (msg.length <= MAX_MSG_LENGTH) {\n return msg;\n }\n const suffix = \"... (truncated)\";\n const cutoff = MAX_MSG_LENGTH - suffix.length;\n return msg.substring(0, cutoff) + suffix;\n }\n\n private getTruncatedStack(stack: string) {\n const suffix = \"... (truncated) ...\";\n const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;\n const lines = stack.trim().split(\"\\n\");\n const truncatedLines: string[] = [];\n let length = 0;\n for (const line of lines) {\n if (length + line.length + 1 > cutoff) {\n truncatedLines.push(suffix);\n break;\n }\n truncatedLines.push(line);\n length += line.length + 1;\n }\n return truncatedLines.join(\"\\n\");\n }\n\n private captureSentryEventId(serverErrorKey: string) {\n if (this.sentry && this.sentry.lastEventId) {\n const eventId = this.sentry.lastEventId();\n if (eventId) {\n this.sentryEventIds.set(serverErrorKey, eventId);\n }\n }\n }\n\n private async tryImportSentry() {\n try {\n this.sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n }\n}\n","import { createHash } from \"crypto\";\n\nimport {\n ConsumerMethodPath,\n ValidationError,\n ValidationErrorsItem,\n} from \"./types.js\";\n\nexport default class ValidationErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ValidationError>;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n }\n\n public addValidationError(\n validationError: ConsumerMethodPath & ValidationError,\n ) {\n const key = this.getKey(validationError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, validationError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n }\n\n public getAndResetValidationErrors() {\n const data: Array<ValidationErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const validationError = this.errorDetails.get(key);\n if (validationError) {\n data.push({\n consumer: validationError.consumer || null,\n method: validationError.method,\n path: validationError.path,\n loc: validationError.loc.split(\".\"),\n msg: validationError.msg,\n type: validationError.type,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(validationError: ConsumerMethodPath & ValidationError) {\n const hashInput = [\n validationError.consumer || \"\",\n validationError.method.toUpperCase(),\n validationError.path,\n validationError.loc,\n validationError.msg.trim(),\n validationError.type,\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n}\n"],"mappings":"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","names":["randomUUID","fetchRetry","ConsumerRegistry","consumers","updated","constructor","Map","Set","addOrUpdateConsumer","consumer","name","group","existing","get","identifier","set","add","getAndResetUpdatedConsumers","data","forEach","push","clear","createLogger","format","transports","getLogger","createLogger","level","process","env","APITALLY_DEBUG","format","combine","colorize","timestamp","printf","info","message","transports","Console","isValidClientId","clientId","regexExp","test","isValidEnv","env","RequestCounter","requestCounts","requestSizeSums","responseSizeSums","responseTimes","requestSizes","responseSizes","constructor","Map","getKey","requestInfo","consumer","method","toUpperCase","path","statusCode","join","addRequest","key","set","get","has","responseTimeMap","responseTimeMsBin","Math","floor","responseTime","requestSize","undefined","Number","requestSizeMap","requestSizeKbBin","responseSize","responseSizeMap","responseSizeKbBin","getAndResetRequests","data","forEach","count","statusCodeStr","split","push","status_code","parseInt","request_count","request_size_sum","response_size_sum","response_times","Object","fromEntries","request_sizes","response_sizes","clear","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","Buffer","randomUUID","createWriteStream","readFile","unlinkSync","tmpdir","join","createGzip","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","_a","contentType","find","k","toLowerCase","undefined","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","url","URL","path","pathname","excludeCallback","body","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","forEach","toJSON","push","JSON","stringify","shift","writeToFile","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","matchPatterns","value","patterns","some","pattern","test","skipEmptyValues","data","Object","fromEntries","entries","filter","_","v","Number","isNaN","Array","isArray","Buffer","isBuffer","length","checkWritableFs","testPath","join","tmpdir","randomUUID","writeFileSync","unlinkSync","error","createHash","MAX_MSG_LENGTH","MAX_STACKTRACE_LENGTH","ServerErrorCounter","errorCounts","errorDetails","sentryEventIds","sentry","constructor","Map","tryImportSentry","addServerError","serverError","key","getKey","has","set","get","captureSentryEventId","getAndResetServerErrors","data","forEach","count","push","consumer","method","path","type","msg","getTruncatedMessage","traceback","getTruncatedStack","sentry_event_id","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","length","suffix","cutoff","substring","stack","lines","split","truncatedLines","line","serverErrorKey","lastEventId","eventId","e","createHash","ValidationErrorCounter","errorCounts","errorDetails","constructor","Map","addValidationError","validationError","key","getKey","has","set","get","getAndResetValidationErrors","data","forEach","count","push","consumer","method","path","loc","split","msg","type","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","constructor","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLoggingConfig","instance","isValidClientId","isValidEnv","randomUUID","RequestCounter","RequestLogger","ValidationErrorCounter","ServerErrorCounter","ConsumerRegistry","getLogger","startSync","handleShutdown","bind","getInstance","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetchRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","error","setStartupData","data","debug","instance_uuid","message_uuid","handled","handleHubError","message","newPayload","time_offset","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","Date","now","i","length","queueItem","shift","time","timeOffset","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
1
+ {"version":3,"sources":["../../src/common/client.ts","../../src/common/consumerRegistry.ts","../../src/common/logging.ts","../../src/common/paramValidation.ts","../../src/common/requestCounter.ts","../../src/common/requestLogger.ts","../../src/common/tempGzipFile.ts","../../src/common/serverErrorCounter.ts","../../src/common/validationErrorCounter.ts"],"sourcesContent":["import { randomUUID } from \"crypto\";\nimport fetchRetry from \"fetch-retry\";\n\nimport ConsumerRegistry from \"./consumerRegistry.js\";\nimport { Logger, getLogger } from \"./logging.js\";\nimport { isValidClientId, isValidEnv } from \"./paramValidation.js\";\nimport RequestCounter from \"./requestCounter.js\";\nimport RequestLogger from \"./requestLogger.js\";\nimport ServerErrorCounter from \"./serverErrorCounter.js\";\nimport {\n ApitallyConfig,\n StartupData,\n StartupPayload,\n SyncPayload,\n} from \"./types.js\";\nimport ValidationErrorCounter from \"./validationErrorCounter.js\";\n\nconst SYNC_INTERVAL = 60000; // 60 seconds\nconst INITIAL_SYNC_INTERVAL = 10000; // 10 seconds\nconst INITIAL_SYNC_INTERVAL_DURATION = 3600000; // 1 hour\nconst MAX_QUEUE_TIME = 3.6e6; // 1 hour\n\nclass HTTPError extends Error {\n public response: Response;\n\n constructor(response: Response) {\n const reason = response.status\n ? `status code ${response.status}`\n : \"an unknown error\";\n super(`Request failed with ${reason}`);\n this.response = response;\n }\n}\n\nexport class ApitallyClient {\n private clientId: string;\n private env: string;\n\n private static instance?: ApitallyClient;\n private instanceUuid: string;\n private syncDataQueue: Array<[number, SyncPayload]>;\n private syncIntervalId?: NodeJS.Timeout;\n public startupData?: StartupData;\n private startupDataSent: boolean = false;\n\n public requestCounter: RequestCounter;\n public requestLogger: RequestLogger;\n public validationErrorCounter: ValidationErrorCounter;\n public serverErrorCounter: ServerErrorCounter;\n public consumerRegistry: ConsumerRegistry;\n public logger: Logger;\n\n constructor({\n clientId,\n env = \"dev\",\n requestLoggingConfig,\n logger,\n }: ApitallyConfig) {\n if (ApitallyClient.instance) {\n throw new Error(\"Apitally client is already initialized\");\n }\n if (!isValidClientId(clientId)) {\n throw new Error(\n `Invalid client ID '${clientId}' (expecting hexadeciaml UUID format)`,\n );\n }\n if (!isValidEnv(env)) {\n throw new Error(\n `Invalid env '${env}' (expecting 1-32 alphanumeric lowercase characters and hyphens only)`,\n );\n }\n\n ApitallyClient.instance = this;\n this.clientId = clientId;\n this.env = env;\n this.instanceUuid = randomUUID();\n this.syncDataQueue = [];\n this.requestCounter = new RequestCounter();\n this.requestLogger = new RequestLogger(requestLoggingConfig);\n this.validationErrorCounter = new ValidationErrorCounter();\n this.serverErrorCounter = new ServerErrorCounter();\n this.consumerRegistry = new ConsumerRegistry();\n this.logger = logger || getLogger();\n\n this.startSync();\n this.handleShutdown = this.handleShutdown.bind(this);\n }\n\n public static getInstance() {\n if (!ApitallyClient.instance) {\n throw new Error(\"Apitally client is not initialized\");\n }\n return ApitallyClient.instance;\n }\n\n public static async shutdown() {\n if (ApitallyClient.instance) {\n await ApitallyClient.instance.handleShutdown();\n }\n }\n\n public async handleShutdown() {\n this.stopSync();\n await this.sendSyncData();\n await this.sendLogData();\n await this.requestLogger.close();\n ApitallyClient.instance = undefined;\n }\n\n private getHubUrlPrefix() {\n const baseURL =\n process.env.APITALLY_HUB_BASE_URL || \"https://hub.apitally.io\";\n const version = \"v2\";\n return `${baseURL}/${version}/${this.clientId}/${this.env}/`;\n }\n\n private async sendData(url: string, payload: any) {\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n const response = await fetchWithRetry(this.getHubUrlPrefix() + url, {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!response.ok) {\n throw new HTTPError(response);\n }\n }\n\n private startSync() {\n this.sync();\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, INITIAL_SYNC_INTERVAL);\n setTimeout(() => {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = setInterval(() => {\n this.sync();\n }, SYNC_INTERVAL);\n }, INITIAL_SYNC_INTERVAL_DURATION);\n }\n\n private async sync() {\n try {\n const promises = [this.sendSyncData(), this.sendLogData()];\n if (!this.startupDataSent) {\n promises.push(this.sendStartupData());\n }\n await Promise.all(promises);\n } catch (error) {\n this.logger.error(\"Error while syncing with Apitally Hub\", {\n error,\n });\n }\n }\n\n private stopSync() {\n if (this.syncIntervalId) {\n clearInterval(this.syncIntervalId);\n this.syncIntervalId = undefined;\n }\n }\n\n public setStartupData(data: StartupData) {\n this.startupData = data;\n this.startupDataSent = false;\n this.sendStartupData();\n }\n\n private async sendStartupData() {\n if (this.startupData) {\n this.logger.debug(\"Sending startup data to Apitally Hub\");\n const payload: StartupPayload = {\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n ...this.startupData,\n };\n try {\n await this.sendData(\"startup\", payload);\n this.startupDataSent = true;\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.error((error as Error).message);\n this.logger.debug(\n \"Error while sending startup data to Apitally Hub (will retry)\",\n { error },\n );\n }\n }\n }\n }\n\n private async sendSyncData() {\n this.logger.debug(\"Synchronizing data with Apitally Hub\");\n const newPayload: SyncPayload = {\n time_offset: 0,\n instance_uuid: this.instanceUuid,\n message_uuid: randomUUID(),\n requests: this.requestCounter.getAndResetRequests(),\n validation_errors:\n this.validationErrorCounter.getAndResetValidationErrors(),\n server_errors: this.serverErrorCounter.getAndResetServerErrors(),\n consumers: this.consumerRegistry.getAndResetUpdatedConsumers(),\n };\n this.syncDataQueue.push([Date.now(), newPayload]);\n\n let i = 0;\n while (this.syncDataQueue.length > 0) {\n const queueItem = this.syncDataQueue.shift();\n if (queueItem) {\n const [time, payload] = queueItem;\n try {\n const timeOffset = Date.now() - time;\n if (timeOffset <= MAX_QUEUE_TIME) {\n if (i > 0) {\n await this.randomDelay();\n }\n payload.time_offset = timeOffset / 1000.0; // in seconds\n await this.sendData(\"sync\", payload);\n i += 1;\n }\n } catch (error) {\n const handled = this.handleHubError(error);\n if (!handled) {\n this.logger.debug(\n \"Error while synchronizing data with Apitally Hub (will retry)\",\n { error },\n );\n this.syncDataQueue.push(queueItem);\n break;\n }\n }\n }\n }\n }\n\n private async sendLogData() {\n this.logger.debug(\"Sending request log data to Apitally Hub\");\n await this.requestLogger.rotateFile();\n\n const fetchWithRetry = fetchRetry(fetch, {\n retries: 3,\n retryDelay: 1000,\n retryOn: [408, 429, 500, 502, 503, 504],\n });\n\n let i = 0;\n let logFile;\n while ((logFile = this.requestLogger.getFile())) {\n if (i > 0) {\n await this.randomDelay();\n }\n\n try {\n const response = await fetchWithRetry(\n `${this.getHubUrlPrefix()}log?uuid=${logFile.uuid}`,\n {\n method: \"POST\",\n body: await logFile.getContent(),\n },\n );\n\n if (response.status === 402 && response.headers.has(\"Retry-After\")) {\n const retryAfter = parseInt(\n response.headers.get(\"Retry-After\") ?? \"0\",\n );\n if (retryAfter > 0) {\n this.requestLogger.suspendUntil = Date.now() + retryAfter * 1000;\n this.requestLogger.clear();\n return;\n }\n }\n\n if (!response.ok) {\n throw new HTTPError(response);\n }\n\n logFile.delete();\n } catch (error) {\n this.requestLogger.retryFileLater(logFile);\n break;\n }\n\n i++;\n if (i >= 10) break;\n }\n }\n\n private handleHubError(error: unknown) {\n if (error instanceof HTTPError) {\n if (error.response.status === 404) {\n this.logger.error(`Invalid Apitally client ID: '${this.clientId}'`);\n this.stopSync();\n return true;\n }\n if (error.response.status === 422) {\n this.logger.error(\"Received validation error from Apitally Hub\");\n return true;\n }\n }\n return false;\n }\n\n private async randomDelay() {\n const delay = 100 + Math.random() * 200;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n}\n","import { ApitallyConsumer } from \"./types.js\";\n\nexport const consumerFromStringOrObject = (\n consumer: ApitallyConsumer | string,\n) => {\n if (typeof consumer === \"string\") {\n consumer = String(consumer).trim().substring(0, 128);\n return consumer ? { identifier: consumer } : null;\n } else {\n consumer.identifier = String(consumer.identifier).trim().substring(0, 128);\n consumer.name = consumer.name?.trim().substring(0, 64);\n consumer.group = consumer.group?.trim().substring(0, 64);\n return consumer.identifier ? consumer : null;\n }\n};\n\nexport default class ConsumerRegistry {\n private consumers: Map<string, ApitallyConsumer>;\n private updated: Set<string>;\n\n constructor() {\n this.consumers = new Map();\n this.updated = new Set();\n }\n\n public addOrUpdateConsumer(consumer?: ApitallyConsumer | null) {\n if (!consumer || (!consumer.name && !consumer.group)) {\n return;\n }\n const existing = this.consumers.get(consumer.identifier);\n if (!existing) {\n this.consumers.set(consumer.identifier, consumer);\n this.updated.add(consumer.identifier);\n } else {\n if (consumer.name && consumer.name !== existing.name) {\n existing.name = consumer.name;\n this.updated.add(consumer.identifier);\n }\n if (consumer.group && consumer.group !== existing.group) {\n existing.group = consumer.group;\n this.updated.add(consumer.identifier);\n }\n }\n }\n\n public getAndResetUpdatedConsumers() {\n const data: Array<ApitallyConsumer> = [];\n this.updated.forEach((identifier) => {\n const consumer = this.consumers.get(identifier);\n if (consumer) {\n data.push(consumer);\n }\n });\n this.updated.clear();\n return data;\n }\n}\n","import { createLogger, format, transports } from \"winston\";\n\nexport interface Logger {\n debug: (message: string, meta?: object) => void;\n info: (message: string, meta?: object) => void;\n warn: (message: string, meta?: object) => void;\n error: (message: string, meta?: object) => void;\n}\n\nexport const getLogger = () => {\n return createLogger({\n level: process.env.APITALLY_DEBUG ? \"debug\" : \"warn\",\n format: format.combine(\n format.colorize(),\n format.timestamp(),\n format.printf(\n (info) => `${info.timestamp} ${info.level}: ${info.message}`,\n ),\n ),\n transports: [new transports.Console()],\n });\n};\n","export function isValidClientId(clientId: string): boolean {\n const regexExp =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n return regexExp.test(clientId);\n}\n\nexport function isValidEnv(env: string): boolean {\n const regexExp = /^[\\w-]{1,32}$/;\n return regexExp.test(env);\n}\n","import { RequestInfo, RequestsItem } from \"./types.js\";\n\nexport default class RequestCounter {\n private requestCounts: Map<string, number>;\n private requestSizeSums: Map<string, number>;\n private responseSizeSums: Map<string, number>;\n private responseTimes: Map<string, Map<number, number>>;\n private requestSizes: Map<string, Map<number, number>>;\n private responseSizes: Map<string, Map<number, number>>;\n\n constructor() {\n this.requestCounts = new Map<string, number>();\n this.requestSizeSums = new Map<string, number>();\n this.responseSizeSums = new Map<string, number>();\n this.responseTimes = new Map<string, Map<number, number>>();\n this.requestSizes = new Map<string, Map<number, number>>();\n this.responseSizes = new Map<string, Map<number, number>>();\n }\n\n private getKey(requestInfo: RequestInfo) {\n return [\n requestInfo.consumer || \"\",\n requestInfo.method.toUpperCase(),\n requestInfo.path,\n requestInfo.statusCode,\n ].join(\"|\");\n }\n\n addRequest(requestInfo: RequestInfo) {\n const key = this.getKey(requestInfo);\n\n // Increment request count\n this.requestCounts.set(key, (this.requestCounts.get(key) || 0) + 1);\n\n // Add response time\n if (!this.responseTimes.has(key)) {\n this.responseTimes.set(key, new Map<number, number>());\n }\n const responseTimeMap = this.responseTimes.get(key)!;\n const responseTimeMsBin = Math.floor(requestInfo.responseTime / 10) * 10; // Rounded to nearest 10ms\n responseTimeMap.set(\n responseTimeMsBin,\n (responseTimeMap.get(responseTimeMsBin) || 0) + 1,\n );\n\n // Add request size\n if (requestInfo.requestSize !== undefined) {\n requestInfo.requestSize = Number(requestInfo.requestSize);\n this.requestSizeSums.set(\n key,\n (this.requestSizeSums.get(key) || 0) + requestInfo.requestSize,\n );\n if (!this.requestSizes.has(key)) {\n this.requestSizes.set(key, new Map<number, number>());\n }\n const requestSizeMap = this.requestSizes.get(key)!;\n const requestSizeKbBin = Math.floor(requestInfo.requestSize / 1000); // Rounded down to nearest KB\n requestSizeMap.set(\n requestSizeKbBin,\n (requestSizeMap.get(requestSizeKbBin) || 0) + 1,\n );\n }\n\n // Add response size\n if (requestInfo.responseSize !== undefined) {\n requestInfo.responseSize = Number(requestInfo.responseSize);\n this.responseSizeSums.set(\n key,\n (this.responseSizeSums.get(key) || 0) + requestInfo.responseSize,\n );\n if (!this.responseSizes.has(key)) {\n this.responseSizes.set(key, new Map<number, number>());\n }\n const responseSizeMap = this.responseSizes.get(key)!;\n const responseSizeKbBin = Math.floor(requestInfo.responseSize / 1000); // Rounded down to nearest KB\n responseSizeMap.set(\n responseSizeKbBin,\n (responseSizeMap.get(responseSizeKbBin) || 0) + 1,\n );\n }\n }\n\n getAndResetRequests() {\n const data: Array<RequestsItem> = [];\n this.requestCounts.forEach((count, key) => {\n const [consumer, method, path, statusCodeStr] = key.split(\"|\");\n const responseTimes =\n this.responseTimes.get(key) || new Map<number, number>();\n const requestSizes =\n this.requestSizes.get(key) || new Map<number, number>();\n const responseSizes =\n this.responseSizes.get(key) || new Map<number, number>();\n data.push({\n consumer: consumer || null,\n method,\n path,\n status_code: parseInt(statusCodeStr),\n request_count: count,\n request_size_sum: this.requestSizeSums.get(key) || 0,\n response_size_sum: this.responseSizeSums.get(key) || 0,\n response_times: Object.fromEntries(responseTimes),\n request_sizes: Object.fromEntries(requestSizes),\n response_sizes: Object.fromEntries(responseSizes),\n });\n });\n\n // Reset the counts and times\n this.requestCounts.clear();\n this.requestSizeSums.clear();\n this.responseSizeSums.clear();\n this.responseTimes.clear();\n this.requestSizes.clear();\n this.responseSizes.clear();\n\n return data;\n }\n}\n","import AsyncLock from \"async-lock\";\nimport { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return (\n contentType !== undefined &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n logRequest(request: Request, response: Response) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(response),\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n return new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n","import type * as Sentry from \"@sentry/node\";\nimport { createHash } from \"crypto\";\n\nimport { ConsumerMethodPath, ServerError, ServerErrorsItem } from \"./types.js\";\n\nconst MAX_MSG_LENGTH = 2048;\nconst MAX_STACKTRACE_LENGTH = 65536;\n\nexport default class ServerErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ServerError>;\n private sentryEventIds: Map<string, string>;\n private sentry: typeof Sentry | undefined;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n this.sentryEventIds = new Map();\n this.tryImportSentry();\n }\n\n public addServerError(serverError: ConsumerMethodPath & ServerError) {\n const key = this.getKey(serverError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, serverError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n this.captureSentryEventId(key);\n }\n\n public getAndResetServerErrors() {\n const data: Array<ServerErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const serverError = this.errorDetails.get(key);\n if (serverError) {\n data.push({\n consumer: serverError.consumer || null,\n method: serverError.method,\n path: serverError.path,\n type: serverError.type,\n msg: this.getTruncatedMessage(serverError.msg),\n traceback: this.getTruncatedStack(serverError.traceback),\n sentry_event_id: this.sentryEventIds.get(key) || null,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(serverError: ConsumerMethodPath & ServerError) {\n const hashInput = [\n serverError.consumer || \"\",\n serverError.method.toUpperCase(),\n serverError.path,\n serverError.type,\n serverError.msg.trim(),\n serverError.traceback.trim(),\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n\n private getTruncatedMessage(msg: string) {\n msg = msg.trim();\n if (msg.length <= MAX_MSG_LENGTH) {\n return msg;\n }\n const suffix = \"... (truncated)\";\n const cutoff = MAX_MSG_LENGTH - suffix.length;\n return msg.substring(0, cutoff) + suffix;\n }\n\n private getTruncatedStack(stack: string) {\n const suffix = \"... (truncated) ...\";\n const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;\n const lines = stack.trim().split(\"\\n\");\n const truncatedLines: string[] = [];\n let length = 0;\n for (const line of lines) {\n if (length + line.length + 1 > cutoff) {\n truncatedLines.push(suffix);\n break;\n }\n truncatedLines.push(line);\n length += line.length + 1;\n }\n return truncatedLines.join(\"\\n\");\n }\n\n private captureSentryEventId(serverErrorKey: string) {\n if (this.sentry && this.sentry.lastEventId) {\n const eventId = this.sentry.lastEventId();\n if (eventId) {\n this.sentryEventIds.set(serverErrorKey, eventId);\n }\n }\n }\n\n private async tryImportSentry() {\n try {\n this.sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n }\n}\n","import { createHash } from \"crypto\";\n\nimport {\n ConsumerMethodPath,\n ValidationError,\n ValidationErrorsItem,\n} from \"./types.js\";\n\nexport default class ValidationErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ValidationError>;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n }\n\n public addValidationError(\n validationError: ConsumerMethodPath & ValidationError,\n ) {\n const key = this.getKey(validationError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, validationError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n }\n\n public getAndResetValidationErrors() {\n const data: Array<ValidationErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const validationError = this.errorDetails.get(key);\n if (validationError) {\n data.push({\n consumer: validationError.consumer || null,\n method: validationError.method,\n path: validationError.path,\n loc: validationError.loc.split(\".\"),\n msg: validationError.msg,\n type: validationError.type,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(validationError: ConsumerMethodPath & ValidationError) {\n const hashInput = [\n validationError.consumer || \"\",\n validationError.method.toUpperCase(),\n validationError.path,\n validationError.loc,\n validationError.msg.trim(),\n validationError.type,\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n}\n"],"mappings":"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","names":["randomUUID","fetchRetry","ConsumerRegistry","consumers","updated","constructor","Map","Set","addOrUpdateConsumer","consumer","name","group","existing","get","identifier","set","add","getAndResetUpdatedConsumers","data","forEach","push","clear","createLogger","format","transports","getLogger","createLogger","level","process","env","APITALLY_DEBUG","format","combine","colorize","timestamp","printf","info","message","transports","Console","isValidClientId","clientId","regexExp","test","isValidEnv","env","RequestCounter","requestCounts","requestSizeSums","responseSizeSums","responseTimes","requestSizes","responseSizes","constructor","Map","getKey","requestInfo","consumer","method","toUpperCase","path","statusCode","join","addRequest","key","set","get","has","responseTimeMap","responseTimeMsBin","Math","floor","responseTime","requestSize","undefined","Number","requestSizeMap","requestSizeKbBin","responseSize","responseSizeMap","responseSizeKbBin","getAndResetRequests","data","forEach","count","statusCodeStr","split","push","status_code","parseInt","request_count","request_size_sum","response_size_sum","response_times","Object","fromEntries","request_sizes","response_sizes","clear","AsyncLock","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","Buffer","randomUUID","createWriteStream","readFile","unlinkSync","tmpdir","join","createGzip","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","_a","contentType","find","k","toLowerCase","undefined","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","url","URL","path","pathname","excludeCallback","body","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","forEach","toJSON","push","JSON","stringify","shift","writeToFile","acquire","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","matchPatterns","value","patterns","some","pattern","test","skipEmptyValues","data","Object","fromEntries","entries","filter","_","v","Number","isNaN","Array","isArray","Buffer","isBuffer","length","checkWritableFs","testPath","join","tmpdir","randomUUID","writeFileSync","unlinkSync","error","createHash","MAX_MSG_LENGTH","MAX_STACKTRACE_LENGTH","ServerErrorCounter","errorCounts","errorDetails","sentryEventIds","sentry","constructor","Map","tryImportSentry","addServerError","serverError","key","getKey","has","set","get","captureSentryEventId","getAndResetServerErrors","data","forEach","count","push","consumer","method","path","type","msg","getTruncatedMessage","traceback","getTruncatedStack","sentry_event_id","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","length","suffix","cutoff","substring","stack","lines","split","truncatedLines","line","serverErrorKey","lastEventId","eventId","e","createHash","ValidationErrorCounter","errorCounts","errorDetails","constructor","Map","addValidationError","validationError","key","getKey","has","set","get","getAndResetValidationErrors","data","forEach","count","push","consumer","method","path","loc","split","msg","type","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","SYNC_INTERVAL","INITIAL_SYNC_INTERVAL","INITIAL_SYNC_INTERVAL_DURATION","MAX_QUEUE_TIME","HTTPError","Error","response","constructor","reason","status","ApitallyClient","clientId","env","instanceUuid","syncDataQueue","syncIntervalId","startupData","startupDataSent","requestCounter","requestLogger","validationErrorCounter","serverErrorCounter","consumerRegistry","logger","requestLoggingConfig","instance","isValidClientId","isValidEnv","randomUUID","RequestCounter","RequestLogger","ValidationErrorCounter","ServerErrorCounter","ConsumerRegistry","getLogger","startSync","handleShutdown","bind","getInstance","shutdown","stopSync","sendSyncData","sendLogData","close","undefined","getHubUrlPrefix","baseURL","process","APITALLY_HUB_BASE_URL","version","sendData","url","payload","fetchWithRetry","fetchRetry","fetch","retries","retryDelay","retryOn","method","body","JSON","stringify","headers","ok","sync","setInterval","setTimeout","clearInterval","promises","push","sendStartupData","Promise","all","error","setStartupData","data","debug","instance_uuid","message_uuid","handled","handleHubError","message","newPayload","time_offset","requests","getAndResetRequests","validation_errors","getAndResetValidationErrors","server_errors","getAndResetServerErrors","consumers","getAndResetUpdatedConsumers","Date","now","i","length","queueItem","shift","time","timeOffset","randomDelay","rotateFile","logFile","getFile","uuid","getContent","has","retryAfter","parseInt","get","suspendUntil","clear","delete","retryFileLater","delay","Math","random","resolve"]}
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
9
  var __export = (target, all) => {
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/common/requestLogger.ts
@@ -26,6 +36,7 @@ __export(requestLogger_exports, {
26
36
  default: () => RequestLogger
27
37
  });
28
38
  module.exports = __toCommonJS(requestLogger_exports);
39
+ var import_async_lock = __toESM(require("async-lock"), 1);
29
40
  var import_buffer2 = require("buffer");
30
41
  var import_crypto2 = require("crypto");
31
42
  var import_fs2 = require("fs");
@@ -113,12 +124,18 @@ var ALLOWED_CONTENT_TYPES = [
113
124
  "text/plain"
114
125
  ];
115
126
  var EXCLUDE_PATH_PATTERNS = [
116
- /\/_?healthz?$/,
117
- /\/_?health[_-]?checks?$/,
118
- /\/_?heart[_-]?beats?$/,
119
- /\/ping$/,
120
- /\/ready$/,
121
- /\/live$/
127
+ /\/_?healthz?$/i,
128
+ /\/_?health[_-]?checks?$/i,
129
+ /\/_?heart[_-]?beats?$/i,
130
+ /\/ping$/i,
131
+ /\/ready$/i,
132
+ /\/live$/i
133
+ ];
134
+ var EXCLUDE_USER_AGENT_PATTERNS = [
135
+ /health[-_ ]?check/i,
136
+ /microsoft-azure-application-lb/i,
137
+ /googlehc/i,
138
+ /kube-probe/i
122
139
  ];
123
140
  var MASK_QUERY_PARAM_PATTERNS = [
124
141
  /auth/i,
@@ -154,6 +171,7 @@ var _RequestLogger = class _RequestLogger {
154
171
  currentFile = null;
155
172
  files = [];
156
173
  maintainIntervalId;
174
+ lock = new import_async_lock.default();
157
175
  constructor(config) {
158
176
  this.config = {
159
177
  ...DEFAULT_CONFIG,
@@ -173,6 +191,9 @@ var _RequestLogger = class _RequestLogger {
173
191
  ];
174
192
  return matchPatterns(urlPath, patterns);
175
193
  }
194
+ shouldExcludeUserAgent(userAgent) {
195
+ return userAgent ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS) : false;
196
+ }
176
197
  shouldMaskQueryParam(name) {
177
198
  const patterns = [
178
199
  ...this.config.maskQueryParams,
@@ -208,11 +229,12 @@ var _RequestLogger = class _RequestLogger {
208
229
  ]);
209
230
  }
210
231
  logRequest(request, response) {
211
- var _a, _b;
232
+ var _a, _b, _c;
212
233
  if (!this.enabled || this.suspendUntil !== null) return;
213
234
  const url = new URL(request.url);
214
235
  const path = request.path ?? url.pathname;
215
- if (this.shouldExcludePath(path) || (((_b = (_a = this.config).excludeCallback) == null ? void 0 : _b.call(_a, request, response)) ?? false)) {
236
+ const userAgent = (_a = request.headers.find(([k]) => k.toLowerCase() === "user-agent")) == null ? void 0 : _a[1];
237
+ if (this.shouldExcludePath(path) || this.shouldExcludeUserAgent(userAgent) || (((_c = (_b = this.config).excludeCallback) == null ? void 0 : _c.call(_b, request, response)) ?? false)) {
216
238
  return;
217
239
  }
218
240
  url.search = this.config.logQueryParams ? this.maskQueryParams(url.search) : "";
@@ -275,15 +297,17 @@ var _RequestLogger = class _RequestLogger {
275
297
  if (!this.enabled || this.pendingWrites.length === 0) {
276
298
  return;
277
299
  }
278
- if (!this.currentFile) {
279
- this.currentFile = new TempGzipFile();
280
- }
281
- while (this.pendingWrites.length > 0) {
282
- const item = this.pendingWrites.shift();
283
- if (item) {
284
- await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
300
+ return this.lock.acquire("file", async () => {
301
+ if (!this.currentFile) {
302
+ this.currentFile = new TempGzipFile();
285
303
  }
286
- }
304
+ while (this.pendingWrites.length > 0) {
305
+ const item = this.pendingWrites.shift();
306
+ if (item) {
307
+ await this.currentFile.writeLine(import_buffer2.Buffer.from(item));
308
+ }
309
+ }
310
+ });
287
311
  }
288
312
  getFile() {
289
313
  return this.files.shift();
@@ -292,11 +316,13 @@ var _RequestLogger = class _RequestLogger {
292
316
  this.files.unshift(file);
293
317
  }
294
318
  async rotateFile() {
295
- if (this.currentFile) {
296
- await this.currentFile.close();
297
- this.files.push(this.currentFile);
298
- this.currentFile = null;
299
- }
319
+ return this.lock.acquire("file", async () => {
320
+ if (this.currentFile) {
321
+ await this.currentFile.close();
322
+ this.files.push(this.currentFile);
323
+ this.currentFile = null;
324
+ }
325
+ });
300
326
  }
301
327
  async maintain() {
302
328
  await this.writeToFile();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/requestLogger.ts","../../src/common/tempGzipFile.ts"],"sourcesContent":["import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/,\n /\\/_?health[_-]?checks?$/,\n /\\/_?heart[_-]?beats?$/,\n /\\/ping$/,\n /\\/ready$/,\n /\\/live$/,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return (\n contentType !== undefined &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n logRequest(request: Request, response: Response) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n\n if (\n this.shouldExcludePath(path) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(response),\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n return new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n"],"mappings":"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","names":["import_buffer","import_crypto","import_fs","import_os","import_path","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","contentType","find","k","toLowerCase","undefined","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","url","URL","path","pathname","excludeCallback","body","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","forEach","toJSON","push","JSON","stringify","shift","writeToFile","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","convertHeaders","Headers","Array","entries","Object","flatMap","value","isArray","convertBody","isValidJsonString","error","parse","pattern","test","data","fromEntries","filter","_","Number","isNaN","isBuffer","testPath","join","tmpdir","writeFileSync","unlinkSync"]}
1
+ {"version":3,"sources":["../../src/common/requestLogger.ts","../../src/common/tempGzipFile.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return (\n contentType !== undefined &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n logRequest(request: Request, response: Response) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(response),\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n return new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n"],"mappings":"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","names":["import_buffer","import_crypto","import_fs","import_os","import_path","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","contentType","find","k","toLowerCase","undefined","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","url","URL","path","pathname","excludeCallback","body","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","forEach","toJSON","push","JSON","stringify","shift","writeToFile","acquire","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","convertHeaders","Headers","Array","entries","Object","flatMap","value","isArray","convertBody","isValidJsonString","error","parse","pattern","test","data","fromEntries","filter","_","Number","isNaN","isBuffer","testPath","join","tmpdir","writeFileSync","unlinkSync"]}
@@ -41,8 +41,10 @@ declare class RequestLogger {
41
41
  private currentFile;
42
42
  private files;
43
43
  private maintainIntervalId?;
44
+ private lock;
44
45
  constructor(config?: Partial<RequestLoggingConfig>);
45
46
  private shouldExcludePath;
47
+ private shouldExcludeUserAgent;
46
48
  private shouldMaskQueryParam;
47
49
  private shouldMaskHeader;
48
50
  private hasSupportedContentType;
@@ -41,8 +41,10 @@ declare class RequestLogger {
41
41
  private currentFile;
42
42
  private files;
43
43
  private maintainIntervalId?;
44
+ private lock;
44
45
  constructor(config?: Partial<RequestLoggingConfig>);
45
46
  private shouldExcludePath;
47
+ private shouldExcludeUserAgent;
46
48
  private shouldMaskQueryParam;
47
49
  private shouldMaskHeader;
48
50
  private hasSupportedContentType;