apitally 0.21.5 → 0.22.1
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.
- package/dist/common/requestLogger.cjs +23 -12
- package/dist/common/requestLogger.cjs.map +1 -1
- package/dist/common/requestLogger.d.cts +1 -1
- package/dist/common/requestLogger.d.ts +1 -1
- package/dist/common/requestLogger.js +23 -12
- package/dist/common/requestLogger.js.map +1 -1
- package/dist/common/response.cjs +63 -105
- package/dist/common/response.cjs.map +1 -1
- package/dist/common/response.d.cts +14 -6
- package/dist/common/response.d.ts +14 -6
- package/dist/common/response.js +61 -100
- package/dist/common/response.js.map +1 -1
- package/dist/common/sentry.cjs +4 -1
- package/dist/common/sentry.cjs.map +1 -1
- package/dist/common/sentry.js +4 -1
- package/dist/common/sentry.js.map +1 -1
- package/dist/elysia/index.d.cts +6 -0
- package/dist/elysia/index.d.ts +6 -0
- package/dist/elysia/plugin.cjs +104 -76
- package/dist/elysia/plugin.cjs.map +1 -1
- package/dist/elysia/plugin.d.cts +10 -0
- package/dist/elysia/plugin.d.ts +10 -0
- package/dist/elysia/plugin.js +105 -77
- package/dist/elysia/plugin.js.map +1 -1
- package/dist/h3/plugin.cjs +59 -59
- package/dist/h3/plugin.cjs.map +1 -1
- package/dist/h3/plugin.js +60 -60
- package/dist/h3/plugin.js.map +1 -1
- package/dist/h3/utils.cjs +1 -1
- package/dist/h3/utils.cjs.map +1 -1
- package/dist/h3/utils.js +1 -1
- package/dist/h3/utils.js.map +1 -1
- package/dist/hono/middleware.cjs +63 -64
- package/dist/hono/middleware.cjs.map +1 -1
- package/dist/hono/middleware.js +64 -65
- package/dist/hono/middleware.js.map +1 -1
- package/dist/loggers/nestjs.cjs +4 -1
- package/dist/loggers/nestjs.cjs.map +1 -1
- package/dist/loggers/nestjs.js +4 -1
- package/dist/loggers/nestjs.js.map +1 -1
- package/dist/loggers/pino.cjs +4 -1
- package/dist/loggers/pino.cjs.map +1 -1
- package/dist/loggers/pino.js +4 -1
- package/dist/loggers/pino.js.map +1 -1
- package/dist/loggers/winston.cjs +5 -1
- package/dist/loggers/winston.cjs.map +1 -1
- package/dist/loggers/winston.js +5 -1
- package/dist/loggers/winston.js.map +1 -1
- package/package.json +7 -3
|
@@ -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
|
-
|
|
171
|
+
getContentType(headers) {
|
|
170
172
|
var _a;
|
|
171
|
-
|
|
172
|
-
return this.isSupportedContentType(contentType);
|
|
173
|
+
return (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
137
|
+
getContentType(headers) {
|
|
136
138
|
var _a;
|
|
137
|
-
|
|
138
|
-
return this.isSupportedContentType(contentType);
|
|
139
|
+
return (_a = headers.find(([k]) => k.toLowerCase() === "content-type")) == null ? void 0 : _a[1];
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
}
|
|
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":"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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","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"]}
|
package/dist/common/response.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
Promise.resolve({
|
|
31
|
+
size: 0,
|
|
32
|
+
completed: true
|
|
33
|
+
})
|
|
102
34
|
];
|
|
103
35
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
83
|
+
newResponse,
|
|
84
|
+
racePromise
|
|
118
85
|
];
|
|
119
86
|
}
|
|
120
|
-
__name(
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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(
|
|
98
|
+
__name(getResponseJson, "getResponseJson");
|
|
138
99
|
// Annotate the CommonJS export names for ESM import in node:
|
|
139
100
|
0 && (module.exports = {
|
|
140
|
-
|
|
141
|
-
getResponseJson
|
|
142
|
-
measureResponseSize,
|
|
143
|
-
teeResponse,
|
|
144
|
-
teeResponseBlob
|
|
101
|
+
captureResponse,
|
|
102
|
+
getResponseJson
|
|
145
103
|
});
|
|
146
104
|
//# sourceMappingURL=response.cjs.map
|