apitally 0.21.5 → 0.22.0

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.
@@ -53,8 +53,10 @@ const BODY_MASKED = import_node_buffer.Buffer.from("<masked>");
53
53
  const MASKED = "******";
54
54
  const ALLOWED_CONTENT_TYPES = [
55
55
  "application/json",
56
+ "application/ld+json",
56
57
  "application/problem+json",
57
58
  "application/vnd.api+json",
59
+ "application/x-ndjson",
58
60
  "text/plain",
59
61
  "text/html"
60
62
  ];
@@ -166,15 +168,13 @@ const _RequestLogger = class _RequestLogger {
166
168
  ];
167
169
  return matchPatterns(name, patterns);
168
170
  }
169
- hasSupportedContentType(headers) {
171
+ getContentType(headers) {
170
172
  var _a;
171
- const contentType = (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
172
- return this.isSupportedContentType(contentType);
173
+ return (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
173
174
  }
174
- hasJsonContentType(headers) {
175
- var _a;
176
- const contentType = (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
177
- return contentType ? /\bjson\b/i.test(contentType) : null;
175
+ hasSupportedContentType(headers) {
176
+ const contentType = this.getContentType(headers);
177
+ return this.isSupportedContentType(contentType);
178
178
  }
179
179
  isSupportedContentType(contentType) {
180
180
  return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
@@ -242,15 +242,26 @@ const _RequestLogger = class _RequestLogger {
242
242
  if (!bodyData || bodyData === BODY_TOO_LARGE || bodyData === BODY_MASKED) {
243
243
  continue;
244
244
  }
245
- const headers = item[key].headers;
246
- const hasJsonContent = this.hasJsonContentType(headers);
247
- if (hasJsonContent === null || hasJsonContent) {
248
- try {
245
+ try {
246
+ const contentType = this.getContentType(item[key].headers);
247
+ if (!contentType || /\bjson\b/i.test(contentType)) {
249
248
  const parsedBody = JSON.parse(bodyData.toString());
250
249
  const maskedBody = this.maskBody(parsedBody);
251
250
  item[key].body = import_node_buffer.Buffer.from(JSON.stringify(maskedBody));
252
- } catch {
251
+ } else if (/\bndjson\b/i.test(contentType)) {
252
+ const lines = bodyData.toString().split("\n").filter((line) => line.trim());
253
+ const maskedLines = lines.map((line) => {
254
+ try {
255
+ const parsed = JSON.parse(line);
256
+ const masked = this.maskBody(parsed);
257
+ return JSON.stringify(masked);
258
+ } catch {
259
+ return line;
260
+ }
261
+ });
262
+ item[key].body = import_node_buffer.Buffer.from(maskedLines.join("\n"));
253
263
  }
264
+ } catch {
254
265
  }
255
266
  }
256
267
  item.request.headers = this.config.logRequestHeaders ? this.maskHeaders(item.request.headers) : [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\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 MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"text/plain\",\n \"text/html\",\n];\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];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/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 LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: 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 logException: true,\n captureLogs: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\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 get maxBodySize() {\n return MAX_BODY_SIZE;\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 shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\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 this.isSupportedContentType(contentType);\n }\n\n private hasJsonContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return contentType ? /\\bjson\\b/i.test(contentType) : null;\n }\n\n public isSupportedContentType(contentType?: string | null) {\n return (\n typeof contentType === \"string\" &&\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 private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n const headers = item[key].headers;\n const hasJsonContent = this.hasJsonContentType(headers);\n if (hasJsonContent === null || hasJsonContent) {\n try {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } catch {\n // If parsing fails, leave body as is\n }\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n ) {\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 if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n this.pendingWrites.push(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 let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\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 | undefined,\n) {\n if (!headers) {\n return [];\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 truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\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"],"mappings":"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","names":["MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","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","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","hasSupportedContentType","headers","contentType","find","k","toLowerCase","isSupportedContentType","hasJsonContentType","test","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","Array","isArray","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","length","bodyData","hasJsonContent","parsedBody","JSON","parse","stringify","url","URL","logRequest","error","logs","path","pathname","excludeCallback","size","uuid","randomUUID","exception","type","message","truncateExceptionMessage","stacktrace","truncateExceptionStackTrace","stack","sentryEventId","getSentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","TempGzipFile","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","filter","_","Number","isNaN","isBuffer","msg","suffix","slice","testPath","join","tmpdir","writeFileSync","unlinkSync"]}
1
+ {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\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 MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\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];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/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 LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: 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 logException: true,\n captureLogs: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\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 get maxBodySize() {\n return MAX_BODY_SIZE;\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 shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | null) {\n return (\n typeof contentType === \"string\" &&\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 private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n ) {\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 if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n this.pendingWrites.push(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 let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\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 | undefined,\n) {\n if (!headers) {\n return [];\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 truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\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"],"mappings":"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","names":["MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","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","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","Array","isArray","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","length","bodyData","test","parsedBody","JSON","parse","stringify","lines","split","filter","line","trim","maskedLines","parsed","masked","join","url","URL","logRequest","error","logs","path","pathname","excludeCallback","size","uuid","randomUUID","exception","type","message","truncateExceptionMessage","stacktrace","truncateExceptionStackTrace","stack","sentryEventId","getSentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","TempGzipFile","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice","testPath","tmpdir","writeFileSync","unlinkSync"]}
@@ -58,8 +58,8 @@ declare class RequestLogger {
58
58
  private shouldMaskQueryParam;
59
59
  private shouldMaskHeader;
60
60
  private shouldMaskBodyField;
61
+ private getContentType;
61
62
  private hasSupportedContentType;
62
- private hasJsonContentType;
63
63
  isSupportedContentType(contentType?: string | null): boolean;
64
64
  private maskQueryParams;
65
65
  private maskHeaders;
@@ -58,8 +58,8 @@ declare class RequestLogger {
58
58
  private shouldMaskQueryParam;
59
59
  private shouldMaskHeader;
60
60
  private shouldMaskBodyField;
61
+ private getContentType;
61
62
  private hasSupportedContentType;
62
- private hasJsonContentType;
63
63
  isSupportedContentType(contentType?: string | null): boolean;
64
64
  private maskQueryParams;
65
65
  private maskHeaders;
@@ -19,8 +19,10 @@ const BODY_MASKED = Buffer2.from("<masked>");
19
19
  const MASKED = "******";
20
20
  const ALLOWED_CONTENT_TYPES = [
21
21
  "application/json",
22
+ "application/ld+json",
22
23
  "application/problem+json",
23
24
  "application/vnd.api+json",
25
+ "application/x-ndjson",
24
26
  "text/plain",
25
27
  "text/html"
26
28
  ];
@@ -132,15 +134,13 @@ const _RequestLogger = class _RequestLogger {
132
134
  ];
133
135
  return matchPatterns(name, patterns);
134
136
  }
135
- hasSupportedContentType(headers) {
137
+ getContentType(headers) {
136
138
  var _a;
137
- const contentType = (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
138
- return this.isSupportedContentType(contentType);
139
+ return (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
139
140
  }
140
- hasJsonContentType(headers) {
141
- var _a;
142
- const contentType = (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
143
- return contentType ? /\bjson\b/i.test(contentType) : null;
141
+ hasSupportedContentType(headers) {
142
+ const contentType = this.getContentType(headers);
143
+ return this.isSupportedContentType(contentType);
144
144
  }
145
145
  isSupportedContentType(contentType) {
146
146
  return typeof contentType === "string" && ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t));
@@ -208,15 +208,26 @@ const _RequestLogger = class _RequestLogger {
208
208
  if (!bodyData || bodyData === BODY_TOO_LARGE || bodyData === BODY_MASKED) {
209
209
  continue;
210
210
  }
211
- const headers = item[key].headers;
212
- const hasJsonContent = this.hasJsonContentType(headers);
213
- if (hasJsonContent === null || hasJsonContent) {
214
- try {
211
+ try {
212
+ const contentType = this.getContentType(item[key].headers);
213
+ if (!contentType || /\bjson\b/i.test(contentType)) {
215
214
  const parsedBody = JSON.parse(bodyData.toString());
216
215
  const maskedBody = this.maskBody(parsedBody);
217
216
  item[key].body = Buffer2.from(JSON.stringify(maskedBody));
218
- } catch {
217
+ } else if (/\bndjson\b/i.test(contentType)) {
218
+ const lines = bodyData.toString().split("\n").filter((line) => line.trim());
219
+ const maskedLines = lines.map((line) => {
220
+ try {
221
+ const parsed = JSON.parse(line);
222
+ const masked = this.maskBody(parsed);
223
+ return JSON.stringify(masked);
224
+ } catch {
225
+ return line;
226
+ }
227
+ });
228
+ item[key].body = Buffer2.from(maskedLines.join("\n"));
219
229
  }
230
+ } catch {
220
231
  }
221
232
  }
222
233
  item.request.headers = this.config.logRequestHeaders ? this.maskHeaders(item.request.headers) : [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\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 MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"text/plain\",\n \"text/html\",\n];\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];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/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 LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: 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 logException: true,\n captureLogs: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\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 get maxBodySize() {\n return MAX_BODY_SIZE;\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 shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\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 this.isSupportedContentType(contentType);\n }\n\n private hasJsonContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return contentType ? /\\bjson\\b/i.test(contentType) : null;\n }\n\n public isSupportedContentType(contentType?: string | null) {\n return (\n typeof contentType === \"string\" &&\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 private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n const headers = item[key].headers;\n const hasJsonContent = this.hasJsonContentType(headers);\n if (hasJsonContent === null || hasJsonContent) {\n try {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } catch {\n // If parsing fails, leave body as is\n }\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n ) {\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 if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n this.pendingWrites.push(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 let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\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 | undefined,\n) {\n if (!headers) {\n return [];\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 truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\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"],"mappings":"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","names":["AsyncLock","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","getSentryEventId","truncateExceptionMessage","truncateExceptionStackTrace","TempGzipFile","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","hasSupportedContentType","headers","contentType","find","k","toLowerCase","isSupportedContentType","hasJsonContentType","test","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","Array","isArray","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","length","bodyData","hasJsonContent","parsedBody","JSON","parse","stringify","url","URL","logRequest","error","logs","path","pathname","excludeCallback","size","uuid","exception","type","message","stacktrace","stack","sentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","filter","_","Number","isNaN","isBuffer","msg","suffix","slice","testPath"]}
1
+ {"version":3,"sources":["../../src/common/requestLogger.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"node:buffer\";\nimport { randomUUID } from \"node:crypto\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n truncateExceptionMessage,\n truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\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 MAX_LOG_MSG_LENGTH = 2048;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\n \"application/json\",\n \"application/ld+json\",\n \"application/problem+json\",\n \"application/vnd.api+json\",\n \"application/x-ndjson\",\n \"text/plain\",\n \"text/html\",\n];\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];\nconst MASK_BODY_FIELD_PATTERNS = [\n /password/i,\n /pwd/i,\n /token/i,\n /secret/i,\n /auth/i,\n /card[-_ ]?number/i,\n /ccv/i,\n /ssn/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 LogRecord = {\n timestamp: number;\n logger?: string;\n level: string;\n message: string;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n captureLogs: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskBodyFields: 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 logException: true,\n captureLogs: false,\n maskQueryParams: [],\n maskHeaders: [],\n maskBodyFields: [],\n excludePaths: [],\n};\n\ntype RequestLogItem = {\n uuid: string;\n request: Request;\n response: Response;\n exception?: {\n type: string;\n message: string;\n stacktrace: string;\n sentryEventId?: string;\n };\n logs?: LogRecord[];\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: RequestLogItem[] = [];\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 get maxBodySize() {\n return MAX_BODY_SIZE;\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 shouldMaskBodyField(name: string) {\n const patterns = [\n ...this.config.maskBodyFields,\n ...MASK_BODY_FIELD_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private getContentType(headers: [string, string][]) {\n return headers.find(([k]) => k.toLowerCase() === \"content-type\")?.[1];\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = this.getContentType(headers);\n return this.isSupportedContentType(contentType);\n }\n\n public isSupportedContentType(contentType?: string | null) {\n return (\n typeof contentType === \"string\" &&\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 private maskBody(data: any): any {\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n const result: any = {};\n for (const [key, value] of Object.entries(data)) {\n if (typeof value === \"string\" && this.shouldMaskBodyField(key)) {\n result[key] = MASKED;\n } else {\n result[key] = this.maskBody(value);\n }\n }\n return result;\n }\n if (Array.isArray(data)) {\n return data.map((item) => this.maskBody(item));\n }\n return data;\n }\n\n private applyMasking(item: RequestLogItem) {\n // Apply user-provided maskRequestBodyCallback function\n if (\n this.config.maskRequestBodyCallback &&\n item.request.body &&\n item.request.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskRequestBodyCallback(item.request);\n item.request.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.request.body = undefined;\n }\n }\n\n // Apply user-provided maskResponseBodyCallback function\n if (\n this.config.maskResponseBodyCallback &&\n item.response.body &&\n item.response.body !== BODY_TOO_LARGE\n ) {\n try {\n const maskedBody = this.config.maskResponseBodyCallback(\n item.request,\n item.response,\n );\n item.response.body = maskedBody ?? BODY_MASKED;\n } catch {\n item.response.body = undefined;\n }\n }\n\n // Check request and response body sizes\n if (item.request.body && item.request.body.length > MAX_BODY_SIZE) {\n item.request.body = BODY_TOO_LARGE;\n }\n if (item.response.body && item.response.body.length > MAX_BODY_SIZE) {\n item.response.body = BODY_TOO_LARGE;\n }\n\n // Mask request and response body fields\n for (const key of [\"request\", \"response\"] as const) {\n const bodyData = item[key].body;\n if (\n !bodyData ||\n bodyData === BODY_TOO_LARGE ||\n bodyData === BODY_MASKED\n ) {\n continue;\n }\n\n try {\n const contentType = this.getContentType(item[key].headers);\n if (!contentType || /\\bjson\\b/i.test(contentType)) {\n const parsedBody = JSON.parse(bodyData.toString());\n const maskedBody = this.maskBody(parsedBody);\n item[key].body = Buffer.from(JSON.stringify(maskedBody));\n } else if (/\\bndjson\\b/i.test(contentType)) {\n const lines = bodyData\n .toString()\n .split(\"\\n\")\n .filter((line) => line.trim());\n const maskedLines = lines.map((line) => {\n try {\n const parsed = JSON.parse(line);\n const masked = this.maskBody(parsed);\n return JSON.stringify(masked);\n } catch {\n return line; // Keep unparseable lines as is\n }\n });\n item[key].body = Buffer.from(maskedLines.join(\"\\n\"));\n }\n } catch {\n // If parsing fails, leave body as is\n }\n }\n\n // Mask request and response headers\n item.request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(item.request.headers)\n : [];\n item.response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(item.response.headers)\n : [];\n\n // Mask query params\n const url = new URL(item.request.url);\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n item.request.url = url.toString();\n\n return item;\n }\n\n logRequest(\n request: Request,\n response: Response,\n error?: Error,\n logs?: LogRecord[],\n ) {\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 if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n }\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n }\n\n if (request.size !== undefined && request.size < 0) {\n request.size = undefined;\n }\n if (response.size !== undefined && response.size < 0) {\n response.size = undefined;\n }\n\n const item: RequestLogItem = {\n uuid: randomUUID(),\n request: request,\n response: response,\n exception:\n error && this.config.logException\n ? {\n type: error.name,\n message: truncateExceptionMessage(error.message),\n stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n sentryEventId: getSentryEventId(),\n }\n : undefined,\n };\n\n if (logs && logs.length > 0) {\n item.logs = logs.map((log) => ({\n timestamp: log.timestamp,\n logger: log.logger,\n level: log.level,\n message: truncateLogMessage(log.message),\n }));\n }\n this.pendingWrites.push(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 let item = this.pendingWrites.shift();\n if (item) {\n item = this.applyMasking(item);\n\n const finalItem = {\n uuid: item.uuid,\n request: skipEmptyValues(item.request),\n response: skipEmptyValues(item.response),\n exception: item.exception,\n logs: item.logs,\n };\n\n // Set up body serialization for JSON\n [finalItem.request.body, finalItem.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Override Buffer's default JSON serialization\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n\n await this.currentFile.writeLine(\n Buffer.from(JSON.stringify(finalItem)),\n );\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 | undefined,\n) {\n if (!headers) {\n return [];\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 truncateLogMessage(msg: string) {\n if (msg.length > MAX_LOG_MSG_LENGTH) {\n const suffix = \"... (truncated)\";\n return msg.slice(0, MAX_LOG_MSG_LENGTH - suffix.length) + suffix;\n }\n return msg;\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"],"mappings":";;AAAA,OAAOA,eAAe;AACtB,SAASC,UAAAA,eAAc;AACvB,SAASC,kBAAkB;AAC3B,SAASC,YAAYC,qBAAqB;AAE1C,SAASC,cAAc;AACvB,SAASC,YAAY;AAErB,SAASC,wBAAwB;AACjC,SACEC,0BACAC,mCACK;AACP,OAAOC,kBAAkB;AAEzB,MAAMC,gBAAgB;AACtB,MAAMC,gBAAgB;AACtB,MAAMC,YAAY;AAClB,MAAMC,qBAAqB;AAC3B,MAAMC,qBAAqB;AAC3B,MAAMC,iBAAiBf,QAAOgB,KAAK,kBAAA;AACnC,MAAMC,cAAcjB,QAAOgB,KAAK,UAAA;AAChC,MAAME,SAAS;AACf,MAAMC,wBAAwB;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF,MAAMC,wBAAwB;EAC5B;EACA;EACA;EACA;EACA;EACA;;AAEF,MAAMC,8BAA8B;EAClC;EACA;EACA;EACA;;AAEF,MAAMC,4BAA4B;EAChC;EACA;EACA;EACA;EACA;EACA;;AAEF,MAAMC,uBAAuB;EAC3B;EACA;EACA;EACA;EACA;;AAEF,MAAMC,2BAA2B;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAkDF,MAAMC,iBAAuC;EAC3CC,SAAS;EACTC,gBAAgB;EAChBC,mBAAmB;EACnBC,gBAAgB;EAChBC,oBAAoB;EACpBC,iBAAiB;EACjBC,cAAc;EACdC,aAAa;EACbC,iBAAiB,CAAA;EACjBC,aAAa,CAAA;EACbC,gBAAgB,CAAA;EAChBC,cAAc,CAAA;AAChB;AAeA,MAAqBC,iBAArB,MAAqBA,eAAAA;EACZC;EACAb;EACAc,eAA8B;EAC7BC,gBAAkC,CAAA;EAClCC,cAAmC;EACnCC,QAAwB,CAAA;EACxBC;EACAC,OAAO,IAAI9C,UAAAA;EAEnB,YAAYwC,QAAwC;AAClD,SAAKA,SAAS;MAAE,GAAGd;MAAgB,GAAGc;IAAO;AAC7C,SAAKb,UAAU,KAAKa,OAAOb,WAAWoB,gBAAAA;AAEtC,QAAI,KAAKpB,SAAS;AAChB,WAAKkB,qBAAqBG,YAAY,MAAA;AACpC,aAAKC,SAAQ;MACf,GAAG,GAAA;IACL;EACF;EAEA,IAAIC,cAAc;AAChB,WAAOvC;EACT;EAEQwC,kBAAkBC,SAAiB;AACzC,UAAMC,WAAW;SAAI,KAAKb,OAAOF;SAAiBjB;;AAClD,WAAOiC,cAAcF,SAASC,QAAAA;EAChC;EAEQE,uBAAuBC,WAAoB;AACjD,WAAOA,YACHF,cAAcE,WAAWlC,2BAAAA,IACzB;EACN;EAEQmC,qBAAqBC,MAAc;AACzC,UAAML,WAAW;SACZ,KAAKb,OAAOL;SACZZ;;AAEL,WAAO+B,cAAcI,MAAML,QAAAA;EAC7B;EAEQM,iBAAiBD,MAAc;AACrC,UAAML,WAAW;SAAI,KAAKb,OAAOJ;SAAgBZ;;AACjD,WAAO8B,cAAcI,MAAML,QAAAA;EAC7B;EAEQO,oBAAoBF,MAAc;AACxC,UAAML,WAAW;SACZ,KAAKb,OAAOH;SACZZ;;AAEL,WAAO6B,cAAcI,MAAML,QAAAA;EAC7B;EAEQQ,eAAeC,SAA6B;AA5MtD;AA6MI,YAAOA,aAAQC,KAAK,CAAC,CAACC,CAAAA,MAAOA,EAAEC,YAAW,MAAO,cAAA,MAA1CH,mBAA4D;EACrE;EAEQI,wBAAwBJ,SAA6B;AAC3D,UAAMK,cAAc,KAAKN,eAAeC,OAAAA;AACxC,WAAO,KAAKM,uBAAuBD,WAAAA;EACrC;EAEOC,uBAAuBD,aAA6B;AACzD,WACE,OAAOA,gBAAgB,YACvB/C,sBAAsBiD,KAAK,CAACC,MAAMH,YAAYI,WAAWD,CAAAA,CAAAA;EAE7D;EAEQnC,gBAAgBqC,QAAgB;AACtC,UAAMC,SAAS,IAAIC,gBAAgBF,MAAAA;AACnC,eAAW,CAACG,GAAAA,KAAQF,QAAQ;AAC1B,UAAI,KAAKhB,qBAAqBkB,GAAAA,GAAM;AAClCF,eAAOG,IAAID,KAAKxD,MAAAA;MAClB;IACF;AACA,WAAOsD,OAAOI,SAAQ;EACxB;EAEQzC,YAAY0B,SAAiD;AACnE,WAAOA,QAAQgB,IAAI,CAAC,CAACd,GAAGe,CAAAA,MAAO;MAACf;MAAG,KAAKL,iBAAiBK,CAAAA,IAAK7C,SAAS4D;KAAE;EAC3E;EAEQC,SAASC,MAAgB;AAC/B,QAAI,OAAOA,SAAS,YAAYA,SAAS,QAAQ,CAACC,MAAMC,QAAQF,IAAAA,GAAO;AACrE,YAAMG,SAAc,CAAC;AACrB,iBAAW,CAACT,KAAKU,KAAAA,KAAUC,OAAOC,QAAQN,IAAAA,GAAO;AAC/C,YAAI,OAAOI,UAAU,YAAY,KAAKzB,oBAAoBe,GAAAA,GAAM;AAC9DS,iBAAOT,GAAAA,IAAOxD;QAChB,OAAO;AACLiE,iBAAOT,GAAAA,IAAO,KAAKK,SAASK,KAAAA;QAC9B;MACF;AACA,aAAOD;IACT;AACA,QAAIF,MAAMC,QAAQF,IAAAA,GAAO;AACvB,aAAOA,KAAKH,IAAI,CAACU,SAAS,KAAKR,SAASQ,IAAAA,CAAAA;IAC1C;AACA,WAAOP;EACT;EAEQQ,aAAaD,MAAsB;AAEzC,QACE,KAAKhD,OAAOkD,2BACZF,KAAKG,QAAQC,QACbJ,KAAKG,QAAQC,SAAS5E,gBACtB;AACA,UAAI;AACF,cAAM6E,aAAa,KAAKrD,OAAOkD,wBAAwBF,KAAKG,OAAO;AACnEH,aAAKG,QAAQC,OAAOC,cAAc3E;MACpC,QAAQ;AACNsE,aAAKG,QAAQC,OAAOE;MACtB;IACF;AAGA,QACE,KAAKtD,OAAOuD,4BACZP,KAAKQ,SAASJ,QACdJ,KAAKQ,SAASJ,SAAS5E,gBACvB;AACA,UAAI;AACF,cAAM6E,aAAa,KAAKrD,OAAOuD,yBAC7BP,KAAKG,SACLH,KAAKQ,QAAQ;AAEfR,aAAKQ,SAASJ,OAAOC,cAAc3E;MACrC,QAAQ;AACNsE,aAAKQ,SAASJ,OAAOE;MACvB;IACF;AAGA,QAAIN,KAAKG,QAAQC,QAAQJ,KAAKG,QAAQC,KAAKK,SAAStF,eAAe;AACjE6E,WAAKG,QAAQC,OAAO5E;IACtB;AACA,QAAIwE,KAAKQ,SAASJ,QAAQJ,KAAKQ,SAASJ,KAAKK,SAAStF,eAAe;AACnE6E,WAAKQ,SAASJ,OAAO5E;IACvB;AAGA,eAAW2D,OAAO;MAAC;MAAW;OAAsB;AAClD,YAAMuB,WAAWV,KAAKb,GAAAA,EAAKiB;AAC3B,UACE,CAACM,YACDA,aAAalF,kBACbkF,aAAahF,aACb;AACA;MACF;AAEA,UAAI;AACF,cAAMiD,cAAc,KAAKN,eAAe2B,KAAKb,GAAAA,EAAKb,OAAO;AACzD,YAAI,CAACK,eAAe,YAAYgC,KAAKhC,WAAAA,GAAc;AACjD,gBAAMiC,aAAaC,KAAKC,MAAMJ,SAASrB,SAAQ,CAAA;AAC/C,gBAAMgB,aAAa,KAAKb,SAASoB,UAAAA;AACjCZ,eAAKb,GAAAA,EAAKiB,OAAO3F,QAAOgB,KAAKoF,KAAKE,UAAUV,UAAAA,CAAAA;QAC9C,WAAW,cAAcM,KAAKhC,WAAAA,GAAc;AAC1C,gBAAMqC,QAAQN,SACXrB,SAAQ,EACR4B,MAAM,IAAA,EACNC,OAAO,CAACC,SAASA,KAAKC,KAAI,CAAA;AAC7B,gBAAMC,cAAcL,MAAM1B,IAAI,CAAC6B,SAAAA;AAC7B,gBAAI;AACF,oBAAMG,SAAST,KAAKC,MAAMK,IAAAA;AAC1B,oBAAMI,SAAS,KAAK/B,SAAS8B,MAAAA;AAC7B,qBAAOT,KAAKE,UAAUQ,MAAAA;YACxB,QAAQ;AACN,qBAAOJ;YACT;UACF,CAAA;AACAnB,eAAKb,GAAAA,EAAKiB,OAAO3F,QAAOgB,KAAK4F,YAAYvG,KAAK,IAAA,CAAA;QAChD;MACF,QAAQ;MAER;IACF;AAGAkF,SAAKG,QAAQ7B,UAAU,KAAKtB,OAAOX,oBAC/B,KAAKO,YAAYoD,KAAKG,QAAQ7B,OAAO,IACrC,CAAA;AACJ0B,SAAKQ,SAASlC,UAAU,KAAKtB,OAAOT,qBAChC,KAAKK,YAAYoD,KAAKQ,SAASlC,OAAO,IACtC,CAAA;AAGJ,UAAMkD,MAAM,IAAIC,IAAIzB,KAAKG,QAAQqB,GAAG;AACpCA,QAAIxC,SAAS,KAAKhC,OAAOZ,iBACrB,KAAKO,gBAAgB6E,IAAIxC,MAAM,IAC/B;AACJgB,SAAKG,QAAQqB,MAAMA,IAAInC,SAAQ;AAE/B,WAAOW;EACT;EAEA0B,WACEvB,SACAK,UACAmB,OACAC,MACA;AAjWJ;AAkWI,QAAI,CAAC,KAAKzF,WAAW,KAAKc,iBAAiB,KAAM;AAEjD,UAAMuE,MAAM,IAAIC,IAAItB,QAAQqB,GAAG;AAC/B,UAAMK,OAAO1B,QAAQ0B,QAAQL,IAAIM;AACjC,UAAM9D,aAAYmC,aAAQ7B,QAAQC,KAChC,CAAC,CAACC,CAAAA,MAAOA,EAAEC,YAAW,MAAO,YAAA,MADb0B,mBAEd;AAEJ,QACE,KAAKxC,kBAAkBkE,IAAAA,KACvB,KAAK9D,uBAAuBC,SAAAA,QAC3B,gBAAKhB,QAAO+E,oBAAZ,4BAA8B5B,SAASK,cAAa,QACrD;AACA;IACF;AAEA,QACE,CAAC,KAAKxD,OAAOV,kBACb,CAAC,KAAKoC,wBAAwByB,QAAQ7B,OAAO,GAC7C;AACA6B,cAAQC,OAAOE;IACjB;AACA,QACE,CAAC,KAAKtD,OAAOR,mBACb,CAAC,KAAKkC,wBAAwB8B,SAASlC,OAAO,GAC9C;AACAkC,eAASJ,OAAOE;IAClB;AAEA,QAAIH,QAAQ6B,SAAS1B,UAAaH,QAAQ6B,OAAO,GAAG;AAClD7B,cAAQ6B,OAAO1B;IACjB;AACA,QAAIE,SAASwB,SAAS1B,UAAaE,SAASwB,OAAO,GAAG;AACpDxB,eAASwB,OAAO1B;IAClB;AAEA,UAAMN,OAAuB;MAC3BiC,MAAMvH,WAAAA;MACNyF;MACAK;MACA0B,WACEP,SAAS,KAAK3E,OAAOP,eACjB;QACE0F,MAAMR,MAAMzD;QACZkE,SAASpH,yBAAyB2G,MAAMS,OAAO;QAC/CC,YAAYpH,4BAA4B0G,MAAMW,SAAS,EAAA;QACvDC,eAAexH,iBAAAA;MACjB,IACAuF;IACR;AAEA,QAAIsB,QAAQA,KAAKnB,SAAS,GAAG;AAC3BT,WAAK4B,OAAOA,KAAKtC,IAAI,CAACkD,SAAS;QAC7BC,WAAWD,IAAIC;QACfC,QAAQF,IAAIE;QACZC,OAAOH,IAAIG;QACXP,SAASQ,mBAAmBJ,IAAIJ,OAAO;MACzC,EAAA;IACF;AACA,SAAKlF,cAAc2F,KAAK7C,IAAAA;AAExB,QAAI,KAAK9C,cAAcuD,SAASnF,oBAAoB;AAClD,WAAK4B,cAAc4F,MAAK;IAC1B;EACF;EAEA,MAAMC,cAAc;AAClB,QAAI,CAAC,KAAK5G,WAAW,KAAKe,cAAcuD,WAAW,GAAG;AACpD;IACF;AACA,WAAO,KAAKnD,KAAK0F,QAAQ,QAAQ,YAAA;AAC/B,UAAI,CAAC,KAAK7F,aAAa;AACrB,aAAKA,cAAc,IAAIjC,aAAAA;MACzB;AACA,aAAO,KAAKgC,cAAcuD,SAAS,GAAG;AACpC,YAAIT,OAAO,KAAK9C,cAAc4F,MAAK;AACnC,YAAI9C,MAAM;AACRA,iBAAO,KAAKC,aAAaD,IAAAA;AAEzB,gBAAMiD,YAAY;YAChBhB,MAAMjC,KAAKiC;YACX9B,SAAS+C,gBAAgBlD,KAAKG,OAAO;YACrCK,UAAU0C,gBAAgBlD,KAAKQ,QAAQ;YACvC0B,WAAWlC,KAAKkC;YAChBN,MAAM5B,KAAK4B;UACb;AAGA;YAACqB,UAAU9C,QAAQC;YAAM6C,UAAUzC,SAASJ;YAAM+C,QAAQ,CAAC/C,SAAAA;AACzD,gBAAIA,MAAM;AAERA,mBAAKgD,SAAS,WAAA;AACZ,uBAAO,KAAK/D,SAAS,QAAA;cACvB;YACF;UACF,CAAA;AAEA,gBAAM,KAAKlC,YAAYkG,UACrB5I,QAAOgB,KAAKoF,KAAKE,UAAUkC,SAAAA,CAAAA,CAAAA;QAE/B;MACF;IACF,CAAA;EACF;EAEAK,UAAU;AACR,WAAO,KAAKlG,MAAM0F,MAAK;EACzB;EAEAS,eAAeC,MAAoB;AACjC,SAAKpG,MAAMqG,QAAQD,IAAAA;EACrB;EAEA,MAAME,aAAa;AACjB,WAAO,KAAKpG,KAAK0F,QAAQ,QAAQ,YAAA;AAC/B,UAAI,KAAK7F,aAAa;AACpB,cAAM,KAAKA,YAAYwG,MAAK;AAC5B,aAAKvG,MAAMyF,KAAK,KAAK1F,WAAW;AAChC,aAAKA,cAAc;MACrB;IACF,CAAA;EACF;EAEA,MAAMM,WAAW;AACf,UAAM,KAAKsF,YAAW;AACtB,QAAI,KAAK5F,eAAe,KAAKA,YAAY6E,OAAO5G,eAAe;AAC7D,YAAM,KAAKsI,WAAU;IACvB;AACA,WAAO,KAAKtG,MAAMqD,SAASpF,WAAW;AACpC,YAAMmI,OAAO,KAAKpG,MAAM0F,MAAK;AAC7BU,mCAAMI;IACR;AACA,QAAI,KAAK3G,iBAAiB,QAAQ,KAAKA,eAAe4G,KAAKC,IAAG,GAAI;AAChE,WAAK7G,eAAe;IACtB;EACF;EAEA,MAAM8G,QAAQ;AACZ,SAAK7G,gBAAgB,CAAA;AACrB,UAAM,KAAKwG,WAAU;AACrB,SAAKtG,MAAM+F,QAAQ,CAACK,SAAAA;AAClBA,WAAKI,OAAM;IACb,CAAA;AACA,SAAKxG,QAAQ,CAAA;EACf;EAEA,MAAMuG,QAAQ;AACZ,SAAKxH,UAAU;AACf,UAAM,KAAK4H,MAAK;AAChB,QAAI,KAAK1G,oBAAoB;AAC3B2G,oBAAc,KAAK3G,kBAAkB;IACvC;EACF;AACF;AAxWqBN;AAArB,IAAqBA,gBAArB;AA0WO,SAASkH,eACd3F,SAKa;AAEb,MAAI,CAACA,SAAS;AACZ,WAAO,CAAA;EACT;AACA,MAAIA,mBAAmB4F,SAAS;AAC9B,WAAOxE,MAAMjE,KAAK6C,QAAQyB,QAAO,CAAA;EACnC;AACA,SAAOD,OAAOC,QAAQzB,OAAAA,EAAS6F,QAAQ,CAAC,CAAChF,KAAKU,KAAAA,MAAM;AAClD,QAAIA,UAAUS,QAAW;AACvB,aAAO,CAAA;IACT;AACA,QAAIZ,MAAMC,QAAQE,KAAAA,GAAQ;AACxB,aAAOA,MAAMP,IAAI,CAACC,MAAM;QAACJ;QAAKI;OAAE;IAClC;AACA,WAAO;MAAC;QAACJ;QAAKU,MAAMR,SAAQ;;;EAC9B,CAAA;AACF;AAvBgB4E;AAyBT,SAASG,YAAYhE,MAAWzB,aAA2B;AAChE,MAAI,CAACyB,QAAQ,CAACzB,aAAa;AACzB;EACF;AACA,MAAI;AACF,QAAIA,YAAYI,WAAW,kBAAA,GAAqB;AAC9C,UAAIsF,kBAAkBjE,IAAAA,GAAO;AAC3B,eAAO3F,QAAOgB,KAAK2E,IAAAA;MACrB,OAAO;AACL,eAAO3F,QAAOgB,KAAKoF,KAAKE,UAAUX,IAAAA,CAAAA;MACpC;IACF;AACA,QAAIzB,YAAYI,WAAW,OAAA,KAAY,OAAOqB,SAAS,UAAU;AAC/D,aAAO3F,QAAOgB,KAAK2E,IAAAA;IACrB;EACF,SAASuB,OAAO;AACd;EACF;AACF;AAlBgByC;AAoBhB,SAASC,kBAAkBjE,MAAS;AAClC,MAAI,OAAOA,SAAS,UAAU;AAC5B,WAAO;EACT;AACA,MAAI;AACFS,SAAKC,MAAMV,IAAAA;AACX,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAVSiE;AAYT,SAASvG,cAAc+B,OAAehC,UAAkB;AACtD,SAAOA,SAASgB,KAAK,CAACyF,YAAAA;AACpB,WAAOA,QAAQ3D,KAAKd,KAAAA;EACtB,CAAA;AACF;AAJS/B;AAMT,SAASoF,gBAA+CzD,MAAO;AAC7D,SAAOK,OAAOyE,YACZzE,OAAOC,QAAQN,IAAAA,EAAMyB,OAAO,CAAC,CAACsD,GAAGjF,CAAAA,MAAE;AACjC,QAAIA,KAAK,QAAQkF,OAAOC,MAAMnF,CAAAA,EAAI,QAAO;AACzC,QAAIG,MAAMC,QAAQJ,CAAAA,KAAM9E,QAAOkK,SAASpF,CAAAA,KAAM,OAAOA,MAAM,UAAU;AACnE,aAAOA,EAAEkB,SAAS;IACpB;AACA,WAAO;EACT,CAAA,CAAA;AAEJ;AAVSyC;AAYT,SAASN,mBAAmBgC,KAAW;AACrC,MAAIA,IAAInE,SAASlF,oBAAoB;AACnC,UAAMsJ,SAAS;AACf,WAAOD,IAAIE,MAAM,GAAGvJ,qBAAqBsJ,OAAOpE,MAAM,IAAIoE;EAC5D;AACA,SAAOD;AACT;AANShC;AAQT,SAASrF,kBAAAA;AACP,MAAI;AACF,UAAMwH,WAAWjK,KAAKD,OAAAA,GAAU,YAAYH,WAAAA,CAAAA,EAAc;AAC1DE,kBAAcmK,UAAU,MAAA;AACxBpK,eAAWoK,QAAAA;AACX,WAAO;EACT,SAASpD,OAAO;AACd,WAAO;EACT;AACF;AATSpE;","names":["AsyncLock","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","getSentryEventId","truncateExceptionMessage","truncateExceptionStackTrace","TempGzipFile","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","MAX_LOG_MSG_LENGTH","BODY_TOO_LARGE","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","MASK_BODY_FIELD_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","captureLogs","maskQueryParams","maskHeaders","maskBodyFields","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","checkWritableFs","setInterval","maintain","maxBodySize","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","shouldMaskBodyField","getContentType","headers","find","k","toLowerCase","hasSupportedContentType","contentType","isSupportedContentType","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","maskBody","data","Array","isArray","result","value","Object","entries","item","applyMasking","maskRequestBodyCallback","request","body","maskedBody","undefined","maskResponseBodyCallback","response","length","bodyData","test","parsedBody","JSON","parse","stringify","lines","split","filter","line","trim","maskedLines","parsed","masked","url","URL","logRequest","error","logs","path","pathname","excludeCallback","size","uuid","exception","type","message","stacktrace","stack","sentryEventId","log","timestamp","logger","level","truncateLogMessage","push","shift","writeToFile","acquire","finalItem","skipEmptyValues","forEach","toJSON","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","delete","Date","now","clear","clearInterval","convertHeaders","Headers","flatMap","convertBody","isValidJsonString","pattern","fromEntries","_","Number","isNaN","isBuffer","msg","suffix","slice","testPath"]}
@@ -19,128 +19,86 @@ var __copyProps = (to, from, except, desc) => {
19
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
20
  var response_exports = {};
21
21
  __export(response_exports, {
22
- getResponseBody: () => getResponseBody,
23
- getResponseJson: () => getResponseJson,
24
- measureResponseSize: () => measureResponseSize,
25
- teeResponse: () => teeResponse,
26
- teeResponseBlob: () => teeResponseBlob
22
+ captureResponse: () => captureResponse,
23
+ getResponseJson: () => getResponseJson
27
24
  });
28
25
  module.exports = __toCommonJS(response_exports);
29
- async function measureResponseSize(response, tee = true) {
30
- const [newResponse1, newResponse2] = tee ? teeResponse(response) : [
31
- response,
32
- response
33
- ];
34
- let size = 0;
35
- if (newResponse2.body) {
36
- try {
37
- let done = false;
38
- const reader = newResponse2.body.getReader();
39
- while (!done) {
40
- const result = await reader.read();
41
- done = result.done;
42
- if (!done && result.value) {
43
- size += result.value.byteLength;
44
- }
45
- }
46
- } catch (error) {
47
- }
48
- }
49
- return [
50
- size,
51
- newResponse1
52
- ];
53
- }
54
- __name(measureResponseSize, "measureResponseSize");
55
- async function getResponseBody(response, tee = true) {
56
- const [newResponse1, newResponse2] = tee ? teeResponse(response) : [
57
- response,
58
- response
59
- ];
60
- try {
61
- const responseBuffer = Buffer.from(await newResponse2.arrayBuffer());
62
- return [
63
- responseBuffer,
64
- newResponse1
65
- ];
66
- } catch (error) {
67
- return [
68
- Buffer.from([]),
69
- newResponse1
70
- ];
71
- }
72
- }
73
- __name(getResponseBody, "getResponseBody");
74
- async function getResponseJson(response) {
75
- const contentType = response.headers.get("content-type");
76
- if (contentType == null ? void 0 : contentType.includes("application/json")) {
77
- const [newResponse1, newResponse2] = teeResponse(response);
78
- try {
79
- const responseJson = await newResponse2.json();
80
- return [
81
- responseJson,
82
- newResponse1
83
- ];
84
- } catch (error) {
85
- return [
86
- null,
87
- newResponse1
88
- ];
89
- }
90
- }
91
- return [
92
- null,
93
- response
94
- ];
95
- }
96
- __name(getResponseJson, "getResponseJson");
97
- function teeResponse(response) {
26
+ function captureResponse(response, { captureBody = false, maxBodySize = 0, readTimeout = 5e3 } = {}) {
98
27
  if (!response.body) {
99
28
  return [
100
29
  response,
101
- response
30
+ Promise.resolve({
31
+ size: 0,
32
+ completed: true
33
+ })
102
34
  ];
103
35
  }
104
- const [stream1, stream2] = response.body.tee();
105
- const newResponse1 = new Response(stream1, {
106
- status: response.status,
107
- statusText: response.statusText,
108
- headers: response.headers
36
+ let size = 0;
37
+ let bodySizeLimitExceeded = false;
38
+ let readStarted = false;
39
+ const chunks = [];
40
+ const { readable, writable } = new TransformStream({
41
+ transform: /* @__PURE__ */ __name((chunk, controller) => {
42
+ readStarted = true;
43
+ size += chunk.byteLength;
44
+ if (captureBody && !bodySizeLimitExceeded) {
45
+ if (size > maxBodySize) {
46
+ bodySizeLimitExceeded = true;
47
+ chunks.length = 0;
48
+ } else {
49
+ chunks.push(chunk);
50
+ }
51
+ }
52
+ controller.enqueue(chunk);
53
+ }, "transform")
109
54
  });
110
- const newResponse2 = new Response(stream2, {
55
+ const pipePromise = response.body.pipeTo(writable).then(() => ({
56
+ body: chunks.length > 0 ? Buffer.concat(chunks) : void 0,
57
+ size,
58
+ completed: true
59
+ })).catch((error) => ({
60
+ size,
61
+ completed: false,
62
+ error
63
+ }));
64
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => {
65
+ if (!readStarted) {
66
+ resolve({
67
+ size: 0,
68
+ completed: false
69
+ });
70
+ }
71
+ }, readTimeout));
72
+ const racePromise = Promise.race([
73
+ pipePromise,
74
+ timeoutPromise
75
+ ]);
76
+ const newResponse = new Response(readable, {
111
77
  status: response.status,
112
78
  statusText: response.statusText,
113
79
  headers: response.headers
114
80
  });
81
+ void newResponse.headers;
115
82
  return [
116
- newResponse1,
117
- newResponse2
83
+ newResponse,
84
+ racePromise
118
85
  ];
119
86
  }
120
- __name(teeResponse, "teeResponse");
121
- function teeResponseBlob(response, blob) {
122
- const newResponse1 = new Response(blob, {
123
- status: response.status,
124
- statusText: response.statusText,
125
- headers: response.headers
126
- });
127
- const newResponse2 = new Response(blob, {
128
- status: response.status,
129
- statusText: response.statusText,
130
- headers: response.headers
131
- });
132
- return [
133
- newResponse1,
134
- newResponse2
135
- ];
87
+ __name(captureResponse, "captureResponse");
88
+ function getResponseJson(body) {
89
+ if (body.length === 0) {
90
+ return null;
91
+ }
92
+ try {
93
+ return JSON.parse(body.toString());
94
+ } catch (error) {
95
+ return null;
96
+ }
136
97
  }
137
- __name(teeResponseBlob, "teeResponseBlob");
98
+ __name(getResponseJson, "getResponseJson");
138
99
  // Annotate the CommonJS export names for ESM import in node:
139
100
  0 && (module.exports = {
140
- getResponseBody,
141
- getResponseJson,
142
- measureResponseSize,
143
- teeResponse,
144
- teeResponseBlob
101
+ captureResponse,
102
+ getResponseJson
145
103
  });
146
104
  //# sourceMappingURL=response.cjs.map