apitally 0.11.4 → 0.12.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.
- package/README.md +27 -14
- package/dist/common/client.cjs +130 -110
- package/dist/common/client.cjs.map +1 -1
- package/dist/common/client.d.cts +2 -0
- package/dist/common/client.d.ts +2 -0
- package/dist/common/client.js +125 -105
- package/dist/common/client.js.map +1 -1
- package/dist/common/consumerRegistry.cjs.map +1 -1
- package/dist/common/consumerRegistry.js.map +1 -1
- package/dist/common/logging.cjs.map +1 -1
- package/dist/common/logging.js.map +1 -1
- package/dist/common/packageVersions.cjs.map +1 -1
- package/dist/common/packageVersions.js.map +1 -1
- package/dist/common/paramValidation.cjs.map +1 -1
- package/dist/common/paramValidation.js.map +1 -1
- package/dist/common/requestCounter.cjs.map +1 -1
- package/dist/common/requestCounter.js.map +1 -1
- package/dist/common/requestLogger.cjs +63 -7
- package/dist/common/requestLogger.cjs.map +1 -1
- package/dist/common/requestLogger.d.cts +3 -1
- package/dist/common/requestLogger.d.ts +3 -1
- package/dist/common/requestLogger.js +63 -7
- package/dist/common/requestLogger.js.map +1 -1
- package/dist/common/sentry.cjs +55 -0
- package/dist/common/sentry.cjs.map +1 -0
- package/dist/common/sentry.d.cts +6 -0
- package/dist/common/sentry.d.ts +6 -0
- package/dist/common/sentry.js +22 -0
- package/dist/common/sentry.js.map +1 -0
- package/dist/common/serverErrorCounter.cjs +58 -45
- package/dist/common/serverErrorCounter.cjs.map +1 -1
- package/dist/common/serverErrorCounter.d.cts +3 -6
- package/dist/common/serverErrorCounter.d.ts +3 -6
- package/dist/common/serverErrorCounter.js +53 -45
- package/dist/common/serverErrorCounter.js.map +1 -1
- package/dist/common/tempGzipFile.cjs +2 -2
- package/dist/common/tempGzipFile.cjs.map +1 -1
- package/dist/common/tempGzipFile.js +2 -2
- package/dist/common/tempGzipFile.js.map +1 -1
- package/dist/common/types.cjs.map +1 -1
- package/dist/common/validationErrorCounter.cjs.map +1 -1
- package/dist/common/validationErrorCounter.js.map +1 -1
- package/dist/express/index.cjs +139 -112
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.js +134 -107
- package/dist/express/index.js.map +1 -1
- package/dist/express/middleware.cjs +139 -112
- package/dist/express/middleware.cjs.map +1 -1
- package/dist/express/middleware.js +134 -107
- package/dist/express/middleware.js.map +1 -1
- package/dist/express/utils.cjs.map +1 -1
- package/dist/express/utils.js.map +1 -1
- package/dist/fastify/index.cjs +136 -113
- package/dist/fastify/index.cjs.map +1 -1
- package/dist/fastify/index.js +131 -108
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/plugin.cjs +136 -113
- package/dist/fastify/plugin.cjs.map +1 -1
- package/dist/fastify/plugin.js +131 -108
- package/dist/fastify/plugin.js.map +1 -1
- package/dist/hono/index.cjs +137 -112
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.js +132 -107
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/middleware.cjs +137 -112
- package/dist/hono/middleware.cjs.map +1 -1
- package/dist/hono/middleware.js +132 -107
- package/dist/hono/middleware.js.map +1 -1
- package/dist/koa/index.cjs +137 -111
- package/dist/koa/index.cjs.map +1 -1
- package/dist/koa/index.js +132 -106
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/middleware.cjs +137 -111
- package/dist/koa/middleware.cjs.map +1 -1
- package/dist/koa/middleware.js +132 -106
- package/dist/koa/middleware.js.map +1 -1
- package/dist/nestjs/index.cjs +139 -112
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +134 -107
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/common/requestLogger.ts","../../src/common/tempGzipFile.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return (\n contentType !== undefined &&\n ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n );\n }\n\n private maskQueryParams(search: string) {\n const params = new URLSearchParams(search);\n for (const [key] of params) {\n if (this.shouldMaskQueryParam(key)) {\n params.set(key, MASKED);\n }\n }\n return params.toString();\n }\n\n private maskHeaders(headers: [string, string][]): [string, string][] {\n return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n }\n\n logRequest(request: Request, response: Response) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(response),\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n private closedPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.closedPromise = new Promise<void>((resolve) => {\n this.writeStream.once(\"close\", resolve);\n this.writeStream.once(\"error\", resolve);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n await new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n await this.closedPromise;\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n"],"mappings":"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","names":["AsyncLock","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","Buffer","randomUUID","createWriteStream","readFile","unlinkSync","tmpdir","join","createGzip","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","closedPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","contentType","find","k","toLowerCase","undefined","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","url","URL","path","pathname","excludeCallback","body","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","forEach","toJSON","push","JSON","stringify","shift","writeToFile","acquire","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","convertHeaders","Headers","Array","entries","Object","flatMap","value","isArray","convertBody","isValidJsonString","error","parse","pattern","test","data","fromEntries","filter","_","Number","isNaN","isBuffer","testPath","join","tmpdir","writeFileSync","unlinkSync"]}
|
|
1
|
+
{"version":3,"sources":["requestLogger.ts","sentry.ts","serverErrorCounter.ts","tempGzipFile.ts"],"sourcesContent":["import AsyncLock from \"async-lock\";\nimport { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { 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 BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n /\\/_?healthz?$/i,\n /\\/_?health[_-]?checks?$/i,\n /\\/_?heart[_-]?beats?$/i,\n /\\/ping$/i,\n /\\/ready$/i,\n /\\/live$/i,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n /health[-_ ]?check/i,\n /microsoft-azure-application-lb/i,\n /googlehc/i,\n /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /password/i,\n /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n /auth/i,\n /api-?key/i,\n /secret/i,\n /token/i,\n /cookie/i,\n];\n\nexport type Request = {\n timestamp: number;\n method: string;\n path?: string;\n url: string;\n headers: [string, string][];\n size?: number;\n consumer?: string;\n body?: Buffer;\n};\n\nexport type Response = {\n statusCode: number;\n responseTime: number;\n headers: [string, string][];\n size?: number;\n body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n enabled: boolean;\n logQueryParams: boolean;\n logRequestHeaders: boolean;\n logRequestBody: boolean;\n logResponseHeaders: boolean;\n logResponseBody: boolean;\n logException: boolean;\n maskQueryParams: RegExp[];\n maskHeaders: RegExp[];\n maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n maskResponseBodyCallback?: (\n request: Request,\n response: Response,\n ) => Buffer | null | undefined;\n excludePaths: RegExp[];\n excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n enabled: false,\n logQueryParams: true,\n logRequestHeaders: false,\n logRequestBody: false,\n logResponseHeaders: true,\n logResponseBody: false,\n logException: true,\n maskQueryParams: [],\n maskHeaders: [],\n excludePaths: [],\n};\n\nexport default class RequestLogger {\n public config: RequestLoggingConfig;\n public enabled: boolean;\n public suspendUntil: number | null = null;\n private pendingWrites: string[] = [];\n private currentFile: TempGzipFile | null = null;\n private files: TempGzipFile[] = [];\n private maintainIntervalId?: NodeJS.Timeout;\n private lock = new AsyncLock();\n\n constructor(config?: Partial<RequestLoggingConfig>) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.enabled = this.config.enabled && checkWritableFs();\n\n if (this.enabled) {\n this.maintainIntervalId = setInterval(() => {\n this.maintain();\n }, 1000);\n }\n }\n\n private shouldExcludePath(urlPath: string) {\n const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n return matchPatterns(urlPath, patterns);\n }\n\n private shouldExcludeUserAgent(userAgent?: string) {\n return userAgent\n ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n : false;\n }\n\n private shouldMaskQueryParam(name: string) {\n const patterns = [\n ...this.config.maskQueryParams,\n ...MASK_QUERY_PARAM_PATTERNS,\n ];\n return matchPatterns(name, patterns);\n }\n\n private shouldMaskHeader(name: string) {\n const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n return matchPatterns(name, patterns);\n }\n\n private hasSupportedContentType(headers: [string, string][]) {\n const contentType = headers.find(\n ([k]) => k.toLowerCase() === \"content-type\",\n )?.[1];\n return 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 logRequest(request: Request, response: Response, error?: Error) {\n if (!this.enabled || this.suspendUntil !== null) return;\n\n const url = new URL(request.url);\n const path = request.path ?? url.pathname;\n const userAgent = request.headers.find(\n ([k]) => k.toLowerCase() === \"user-agent\",\n )?.[1];\n\n if (\n this.shouldExcludePath(path) ||\n this.shouldExcludeUserAgent(userAgent) ||\n (this.config.excludeCallback?.(request, response) ?? false)\n ) {\n return;\n }\n\n // Process query params\n url.search = this.config.logQueryParams\n ? this.maskQueryParams(url.search)\n : \"\";\n request.url = url.toString();\n\n // Process request body\n if (\n !this.config.logRequestBody ||\n !this.hasSupportedContentType(request.headers)\n ) {\n request.body = undefined;\n } else if (request.body) {\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n } else if (this.config.maskRequestBodyCallback) {\n try {\n request.body =\n this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n if (request.body.length > MAX_BODY_SIZE) {\n request.body = BODY_TOO_LARGE;\n }\n } catch {\n request.body = undefined;\n }\n }\n }\n\n // Process response body\n if (\n !this.config.logResponseBody ||\n !this.hasSupportedContentType(response.headers)\n ) {\n response.body = undefined;\n } else if (response.body) {\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n } else if (this.config.maskResponseBodyCallback) {\n try {\n response.body =\n this.config.maskResponseBodyCallback(request, response) ??\n BODY_MASKED;\n if (response.body.length > MAX_BODY_SIZE) {\n response.body = BODY_TOO_LARGE;\n }\n } catch {\n response.body = undefined;\n }\n }\n }\n\n // Process headers\n request.headers = this.config.logRequestHeaders\n ? this.maskHeaders(request.headers)\n : [];\n response.headers = this.config.logResponseHeaders\n ? this.maskHeaders(response.headers)\n : [];\n\n const item = {\n uuid: randomUUID(),\n request: skipEmptyValues(request),\n response: skipEmptyValues(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 : null,\n };\n [item.request.body, item.response.body].forEach((body) => {\n if (body) {\n // @ts-expect-error Different return type\n body.toJSON = function () {\n return this.toString(\"base64\");\n };\n }\n });\n this.pendingWrites.push(JSON.stringify(item));\n\n if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n this.pendingWrites.shift();\n }\n }\n\n async writeToFile() {\n if (!this.enabled || this.pendingWrites.length === 0) {\n return;\n }\n return this.lock.acquire(\"file\", async () => {\n if (!this.currentFile) {\n this.currentFile = new TempGzipFile();\n }\n while (this.pendingWrites.length > 0) {\n const item = this.pendingWrites.shift();\n if (item) {\n await this.currentFile.writeLine(Buffer.from(item));\n }\n }\n });\n }\n\n getFile() {\n return this.files.shift();\n }\n\n retryFileLater(file: TempGzipFile) {\n this.files.unshift(file);\n }\n\n async rotateFile() {\n return this.lock.acquire(\"file\", async () => {\n if (this.currentFile) {\n await this.currentFile.close();\n this.files.push(this.currentFile);\n this.currentFile = null;\n }\n });\n }\n\n async maintain() {\n await this.writeToFile();\n if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n await this.rotateFile();\n }\n while (this.files.length > MAX_FILES) {\n const file = this.files.shift();\n file?.delete();\n }\n if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n this.suspendUntil = null;\n }\n }\n\n async clear() {\n this.pendingWrites = [];\n await this.rotateFile();\n this.files.forEach((file) => {\n file.delete();\n });\n this.files = [];\n }\n\n async close() {\n this.enabled = false;\n await this.clear();\n if (this.maintainIntervalId) {\n clearInterval(this.maintainIntervalId);\n }\n }\n}\n\nexport function convertHeaders(\n headers:\n | Headers\n | IncomingHttpHeaders\n | OutgoingHttpHeaders\n | Record<string, string | string[] | number | undefined>,\n) {\n if (headers instanceof Headers) {\n return Array.from(headers.entries());\n }\n return Object.entries(headers).flatMap(([key, value]) => {\n if (value === undefined) {\n return [];\n }\n if (Array.isArray(value)) {\n return value.map((v) => [key, v]);\n }\n return [[key, value.toString()]];\n }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n if (!body || !contentType) {\n return;\n }\n try {\n if (contentType.startsWith(\"application/json\")) {\n if (isValidJsonString(body)) {\n return Buffer.from(body);\n } else {\n return Buffer.from(JSON.stringify(body));\n }\n }\n if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n return Buffer.from(body);\n }\n } catch (error) {\n return;\n }\n}\n\nfunction isValidJsonString(body: any) {\n if (typeof body !== \"string\") {\n return false;\n }\n try {\n JSON.parse(body);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n return patterns.some((pattern) => {\n return pattern.test(value);\n });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n return Object.fromEntries(\n Object.entries(data).filter(([_, v]) => {\n if (v == null || Number.isNaN(v)) return false;\n if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n return v.length > 0;\n }\n return true;\n }),\n ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n try {\n const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n writeFileSync(testPath, \"test\");\n unlinkSync(testPath);\n return true;\n } catch (error) {\n return false;\n }\n}\n","import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n try {\n sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n if (sentry && sentry.lastEventId) {\n return sentry.lastEventId();\n }\n return undefined;\n}\n","import { createHash } from \"crypto\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport { ConsumerMethodPath, ServerError, ServerErrorsItem } from \"./types.js\";\n\nconst MAX_MSG_LENGTH = 2048;\nconst MAX_STACKTRACE_LENGTH = 65536;\n\nexport default class ServerErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ServerError>;\n private sentryEventIds: Map<string, string>;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n this.sentryEventIds = new Map();\n }\n\n public addServerError(serverError: ConsumerMethodPath & ServerError) {\n const key = this.getKey(serverError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, serverError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n\n const sentryEventId = getSentryEventId();\n if (sentryEventId) {\n this.sentryEventIds.set(key, sentryEventId);\n }\n }\n\n public getAndResetServerErrors() {\n const data: Array<ServerErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const serverError = this.errorDetails.get(key);\n if (serverError) {\n data.push({\n consumer: serverError.consumer || null,\n method: serverError.method,\n path: serverError.path,\n type: serverError.type,\n msg: truncateExceptionMessage(serverError.msg),\n traceback: truncateExceptionStackTrace(serverError.traceback),\n sentry_event_id: this.sentryEventIds.get(key) || null,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(serverError: ConsumerMethodPath & ServerError) {\n const hashInput = [\n serverError.consumer || \"\",\n serverError.method.toUpperCase(),\n serverError.path,\n serverError.type,\n serverError.msg.trim(),\n serverError.traceback.trim(),\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n}\n\nexport function truncateExceptionMessage(msg: string) {\n if (msg.length <= MAX_MSG_LENGTH) {\n return msg;\n }\n const suffix = \"... (truncated)\";\n const cutoff = MAX_MSG_LENGTH - suffix.length;\n return msg.substring(0, cutoff) + suffix;\n}\n\nexport function truncateExceptionStackTrace(stack: string) {\n const suffix = \"... (truncated) ...\";\n const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;\n const lines = stack.trim().split(\"\\n\");\n const truncatedLines: string[] = [];\n let length = 0;\n for (const line of lines) {\n if (length + line.length + 1 > cutoff) {\n truncatedLines.push(suffix);\n break;\n }\n truncatedLines.push(line);\n length += line.length + 1;\n }\n return truncatedLines.join(\"\\n\");\n}\n","import { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { createWriteStream, readFile, unlinkSync, WriteStream } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { createGzip, Gzip } from \"zlib\";\n\nexport default class TempGzipFile {\n public uuid: string;\n private filePath: string;\n private gzip: Gzip;\n private writeStream: WriteStream;\n private readyPromise: Promise<void>;\n private closedPromise: Promise<void>;\n\n constructor() {\n this.uuid = randomUUID();\n this.filePath = join(tmpdir(), `apitally-${this.uuid}.gz`);\n this.writeStream = createWriteStream(this.filePath);\n this.readyPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"ready\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.closedPromise = new Promise<void>((resolve, reject) => {\n this.writeStream.once(\"close\", resolve);\n this.writeStream.once(\"error\", reject);\n });\n this.gzip = createGzip();\n this.gzip.pipe(this.writeStream);\n }\n\n get size() {\n return this.writeStream.bytesWritten;\n }\n\n async writeLine(data: Buffer) {\n await this.readyPromise;\n return new Promise<void>((resolve, reject) => {\n this.gzip.write(Buffer.concat([data, Buffer.from(\"\\n\")]), (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n }\n\n async getContent() {\n return new Promise<Buffer>((resolve, reject) => {\n readFile(this.filePath, (error, data) => {\n if (error) {\n reject(error);\n } else {\n resolve(data);\n }\n });\n });\n }\n\n async close() {\n await new Promise<void>((resolve) => {\n this.gzip.end(() => {\n resolve();\n });\n });\n await this.closedPromise;\n }\n\n async delete() {\n await this.close();\n unlinkSync(this.filePath);\n }\n}\n"],"mappings":"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","names":["AsyncLock","Buffer","randomUUID","unlinkSync","writeFileSync","tmpdir","join","sentry","e","getSentryEventId","lastEventId","undefined","MAX_MSG_LENGTH","MAX_STACKTRACE_LENGTH","truncateExceptionMessage","msg","length","MAX_MSG_LENGTH","suffix","cutoff","substring","truncateExceptionStackTrace","stack","MAX_STACKTRACE_LENGTH","lines","trim","split","truncatedLines","line","push","join","Buffer","randomUUID","createWriteStream","readFile","unlinkSync","tmpdir","join","createGzip","TempGzipFile","uuid","filePath","gzip","writeStream","readyPromise","closedPromise","constructor","randomUUID","join","tmpdir","createWriteStream","Promise","resolve","reject","once","createGzip","pipe","size","bytesWritten","writeLine","data","write","Buffer","concat","from","error","getContent","readFile","close","end","delete","unlinkSync","MAX_BODY_SIZE","MAX_FILE_SIZE","MAX_FILES","MAX_PENDING_WRITES","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","MASKED","ALLOWED_CONTENT_TYPES","EXCLUDE_PATH_PATTERNS","EXCLUDE_USER_AGENT_PATTERNS","MASK_QUERY_PARAM_PATTERNS","MASK_HEADER_PATTERNS","DEFAULT_CONFIG","enabled","logQueryParams","logRequestHeaders","logRequestBody","logResponseHeaders","logResponseBody","logException","maskQueryParams","maskHeaders","excludePaths","RequestLogger","config","suspendUntil","pendingWrites","currentFile","files","maintainIntervalId","lock","AsyncLock","constructor","checkWritableFs","setInterval","maintain","shouldExcludePath","urlPath","patterns","matchPatterns","shouldExcludeUserAgent","userAgent","shouldMaskQueryParam","name","shouldMaskHeader","hasSupportedContentType","headers","contentType","find","k","toLowerCase","isSupportedContentType","some","t","startsWith","search","params","URLSearchParams","key","set","toString","map","v","logRequest","request","response","error","url","URL","path","pathname","excludeCallback","body","undefined","length","maskRequestBodyCallback","maskResponseBodyCallback","item","uuid","randomUUID","skipEmptyValues","exception","type","message","truncateExceptionMessage","stacktrace","truncateExceptionStackTrace","stack","sentryEventId","getSentryEventId","forEach","toJSON","push","JSON","stringify","shift","writeToFile","acquire","TempGzipFile","writeLine","getFile","retryFileLater","file","unshift","rotateFile","close","size","delete","Date","now","clear","clearInterval","convertHeaders","Headers","Array","entries","Object","flatMap","value","isArray","convertBody","isValidJsonString","parse","pattern","test","data","fromEntries","filter","_","Number","isNaN","isBuffer","testPath","join","tmpdir","writeFileSync","unlinkSync"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/common/sentry.ts
|
|
32
|
+
var sentry_exports = {};
|
|
33
|
+
__export(sentry_exports, {
|
|
34
|
+
getSentryEventId: () => getSentryEventId
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(sentry_exports);
|
|
37
|
+
var sentry;
|
|
38
|
+
(async () => {
|
|
39
|
+
try {
|
|
40
|
+
sentry = await import("@sentry/node");
|
|
41
|
+
} catch (e) {
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
function getSentryEventId() {
|
|
45
|
+
if (sentry && sentry.lastEventId) {
|
|
46
|
+
return sentry.lastEventId();
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
__name(getSentryEventId, "getSentryEventId");
|
|
51
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
52
|
+
0 && (module.exports = {
|
|
53
|
+
getSentryEventId
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=sentry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["sentry.ts"],"sourcesContent":["import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n try {\n sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n if (sentry && sentry.lastEventId) {\n return sentry.lastEventId();\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA;;;;;AAAA,IAAIA;CAGH,YAAA;AACC,MAAI;AACFA,aAAS,MAAM,OAAO,cAAA;EACxB,SAASC,GAAG;EAEZ;AACF,GAAA;AAKO,SAASC,mBAAAA;AACd,MAAIF,UAAUA,OAAOG,aAAa;AAChC,WAAOH,OAAOG,YAAW;EAC3B;AACA,SAAOC;AACT;AALgBF;","names":["sentry","e","getSentryEventId","lastEventId","undefined"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/common/sentry.ts
|
|
5
|
+
var sentry;
|
|
6
|
+
(async () => {
|
|
7
|
+
try {
|
|
8
|
+
sentry = await import("@sentry/node");
|
|
9
|
+
} catch (e) {
|
|
10
|
+
}
|
|
11
|
+
})();
|
|
12
|
+
function getSentryEventId() {
|
|
13
|
+
if (sentry && sentry.lastEventId) {
|
|
14
|
+
return sentry.lastEventId();
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
__name(getSentryEventId, "getSentryEventId");
|
|
19
|
+
export {
|
|
20
|
+
getSentryEventId
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=sentry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["sentry.ts"],"sourcesContent":["import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n try {\n sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n if (sentry && sentry.lastEventId) {\n return sentry.lastEventId();\n }\n return undefined;\n}\n"],"mappings":";;;;AAEA,IAAIA;CAGH,YAAA;AACC,MAAI;AACFA,aAAS,MAAM,OAAO,cAAA;EACxB,SAASC,GAAG;EAEZ;AACF,GAAA;AAKO,SAASC,mBAAAA;AACd,MAAIF,UAAUA,OAAOG,aAAa;AAChC,WAAOH,OAAOG,YAAW;EAC3B;AACA,SAAOC;AACT;AALgBF;","names":["sentry","e","getSentryEventId","lastEventId","undefined"]}
|
|
@@ -31,22 +31,40 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/common/serverErrorCounter.ts
|
|
32
32
|
var serverErrorCounter_exports = {};
|
|
33
33
|
__export(serverErrorCounter_exports, {
|
|
34
|
-
default: () => ServerErrorCounter
|
|
34
|
+
default: () => ServerErrorCounter,
|
|
35
|
+
truncateExceptionMessage: () => truncateExceptionMessage,
|
|
36
|
+
truncateExceptionStackTrace: () => truncateExceptionStackTrace
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(serverErrorCounter_exports);
|
|
37
39
|
var import_crypto = require("crypto");
|
|
40
|
+
|
|
41
|
+
// src/common/sentry.ts
|
|
42
|
+
var sentry;
|
|
43
|
+
(async () => {
|
|
44
|
+
try {
|
|
45
|
+
sentry = await import("@sentry/node");
|
|
46
|
+
} catch (e) {
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
function getSentryEventId() {
|
|
50
|
+
if (sentry && sentry.lastEventId) {
|
|
51
|
+
return sentry.lastEventId();
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
__name(getSentryEventId, "getSentryEventId");
|
|
56
|
+
|
|
57
|
+
// src/common/serverErrorCounter.ts
|
|
38
58
|
var MAX_MSG_LENGTH = 2048;
|
|
39
59
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
40
60
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
41
61
|
errorCounts;
|
|
42
62
|
errorDetails;
|
|
43
63
|
sentryEventIds;
|
|
44
|
-
sentry;
|
|
45
64
|
constructor() {
|
|
46
65
|
this.errorCounts = /* @__PURE__ */ new Map();
|
|
47
66
|
this.errorDetails = /* @__PURE__ */ new Map();
|
|
48
67
|
this.sentryEventIds = /* @__PURE__ */ new Map();
|
|
49
|
-
this.tryImportSentry();
|
|
50
68
|
}
|
|
51
69
|
addServerError(serverError) {
|
|
52
70
|
const key = this.getKey(serverError);
|
|
@@ -54,7 +72,10 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
54
72
|
this.errorDetails.set(key, serverError);
|
|
55
73
|
}
|
|
56
74
|
this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
|
|
57
|
-
|
|
75
|
+
const sentryEventId = getSentryEventId();
|
|
76
|
+
if (sentryEventId) {
|
|
77
|
+
this.sentryEventIds.set(key, sentryEventId);
|
|
78
|
+
}
|
|
58
79
|
}
|
|
59
80
|
getAndResetServerErrors() {
|
|
60
81
|
const data = [];
|
|
@@ -66,8 +87,8 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
66
87
|
method: serverError.method,
|
|
67
88
|
path: serverError.path,
|
|
68
89
|
type: serverError.type,
|
|
69
|
-
msg:
|
|
70
|
-
traceback:
|
|
90
|
+
msg: truncateExceptionMessage(serverError.msg),
|
|
91
|
+
traceback: truncateExceptionStackTrace(serverError.traceback),
|
|
71
92
|
sentry_event_id: this.sentryEventIds.get(key) || null,
|
|
72
93
|
error_count: count
|
|
73
94
|
});
|
|
@@ -88,46 +109,38 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
88
109
|
].join("|");
|
|
89
110
|
return (0, import_crypto.createHash)("md5").update(hashInput).digest("hex");
|
|
90
111
|
}
|
|
91
|
-
getTruncatedMessage(msg) {
|
|
92
|
-
msg = msg.trim();
|
|
93
|
-
if (msg.length <= MAX_MSG_LENGTH) {
|
|
94
|
-
return msg;
|
|
95
|
-
}
|
|
96
|
-
const suffix = "... (truncated)";
|
|
97
|
-
const cutoff = MAX_MSG_LENGTH - suffix.length;
|
|
98
|
-
return msg.substring(0, cutoff) + suffix;
|
|
99
|
-
}
|
|
100
|
-
getTruncatedStack(stack) {
|
|
101
|
-
const suffix = "... (truncated) ...";
|
|
102
|
-
const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
|
|
103
|
-
const lines = stack.trim().split("\n");
|
|
104
|
-
const truncatedLines = [];
|
|
105
|
-
let length = 0;
|
|
106
|
-
for (const line of lines) {
|
|
107
|
-
if (length + line.length + 1 > cutoff) {
|
|
108
|
-
truncatedLines.push(suffix);
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
truncatedLines.push(line);
|
|
112
|
-
length += line.length + 1;
|
|
113
|
-
}
|
|
114
|
-
return truncatedLines.join("\n");
|
|
115
|
-
}
|
|
116
|
-
captureSentryEventId(serverErrorKey) {
|
|
117
|
-
if (this.sentry && this.sentry.lastEventId) {
|
|
118
|
-
const eventId = this.sentry.lastEventId();
|
|
119
|
-
if (eventId) {
|
|
120
|
-
this.sentryEventIds.set(serverErrorKey, eventId);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
async tryImportSentry() {
|
|
125
|
-
try {
|
|
126
|
-
this.sentry = await import("@sentry/node");
|
|
127
|
-
} catch (e) {
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
112
|
};
|
|
131
113
|
__name(_ServerErrorCounter, "ServerErrorCounter");
|
|
132
114
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
115
|
+
function truncateExceptionMessage(msg) {
|
|
116
|
+
if (msg.length <= MAX_MSG_LENGTH) {
|
|
117
|
+
return msg;
|
|
118
|
+
}
|
|
119
|
+
const suffix = "... (truncated)";
|
|
120
|
+
const cutoff = MAX_MSG_LENGTH - suffix.length;
|
|
121
|
+
return msg.substring(0, cutoff) + suffix;
|
|
122
|
+
}
|
|
123
|
+
__name(truncateExceptionMessage, "truncateExceptionMessage");
|
|
124
|
+
function truncateExceptionStackTrace(stack) {
|
|
125
|
+
const suffix = "... (truncated) ...";
|
|
126
|
+
const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
|
|
127
|
+
const lines = stack.trim().split("\n");
|
|
128
|
+
const truncatedLines = [];
|
|
129
|
+
let length = 0;
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (length + line.length + 1 > cutoff) {
|
|
132
|
+
truncatedLines.push(suffix);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
truncatedLines.push(line);
|
|
136
|
+
length += line.length + 1;
|
|
137
|
+
}
|
|
138
|
+
return truncatedLines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
__name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
|
|
141
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
142
|
+
0 && (module.exports = {
|
|
143
|
+
truncateExceptionMessage,
|
|
144
|
+
truncateExceptionStackTrace
|
|
145
|
+
});
|
|
133
146
|
//# sourceMappingURL=serverErrorCounter.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["
|
|
1
|
+
{"version":3,"sources":["serverErrorCounter.ts","sentry.ts"],"sourcesContent":["import { createHash } from \"crypto\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport { ConsumerMethodPath, ServerError, ServerErrorsItem } from \"./types.js\";\n\nconst MAX_MSG_LENGTH = 2048;\nconst MAX_STACKTRACE_LENGTH = 65536;\n\nexport default class ServerErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ServerError>;\n private sentryEventIds: Map<string, string>;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n this.sentryEventIds = new Map();\n }\n\n public addServerError(serverError: ConsumerMethodPath & ServerError) {\n const key = this.getKey(serverError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, serverError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n\n const sentryEventId = getSentryEventId();\n if (sentryEventId) {\n this.sentryEventIds.set(key, sentryEventId);\n }\n }\n\n public getAndResetServerErrors() {\n const data: Array<ServerErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const serverError = this.errorDetails.get(key);\n if (serverError) {\n data.push({\n consumer: serverError.consumer || null,\n method: serverError.method,\n path: serverError.path,\n type: serverError.type,\n msg: truncateExceptionMessage(serverError.msg),\n traceback: truncateExceptionStackTrace(serverError.traceback),\n sentry_event_id: this.sentryEventIds.get(key) || null,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(serverError: ConsumerMethodPath & ServerError) {\n const hashInput = [\n serverError.consumer || \"\",\n serverError.method.toUpperCase(),\n serverError.path,\n serverError.type,\n serverError.msg.trim(),\n serverError.traceback.trim(),\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n}\n\nexport function truncateExceptionMessage(msg: string) {\n if (msg.length <= MAX_MSG_LENGTH) {\n return msg;\n }\n const suffix = \"... (truncated)\";\n const cutoff = MAX_MSG_LENGTH - suffix.length;\n return msg.substring(0, cutoff) + suffix;\n}\n\nexport function truncateExceptionStackTrace(stack: string) {\n const suffix = \"... (truncated) ...\";\n const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;\n const lines = stack.trim().split(\"\\n\");\n const truncatedLines: string[] = [];\n let length = 0;\n for (const line of lines) {\n if (length + line.length + 1 > cutoff) {\n truncatedLines.push(suffix);\n break;\n }\n truncatedLines.push(line);\n length += line.length + 1;\n }\n return truncatedLines.join(\"\\n\");\n}\n","import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n try {\n sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n if (sentry && sentry.lastEventId) {\n return sentry.lastEventId();\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;oBAA2B;;;ACE3B,IAAIA;CAGH,YAAA;AACC,MAAI;AACFA,aAAS,MAAM,OAAO,cAAA;EACxB,SAASC,GAAG;EAEZ;AACF,GAAA;AAKO,SAASC,mBAAAA;AACd,MAAIF,UAAUA,OAAOG,aAAa;AAChC,WAAOH,OAAOG,YAAW;EAC3B;AACA,SAAOC;AACT;AALgBF;;;ADXhB,IAAMG,iBAAiB;AACvB,IAAMC,wBAAwB;AAE9B,IAAqBC,sBAArB,MAAqBA,oBAAAA;EACXC;EACAC;EACAC;EAERC,cAAc;AACZ,SAAKH,cAAc,oBAAII,IAAAA;AACvB,SAAKH,eAAe,oBAAIG,IAAAA;AACxB,SAAKF,iBAAiB,oBAAIE,IAAAA;EAC5B;EAEOC,eAAeC,aAA+C;AACnE,UAAMC,MAAM,KAAKC,OAAOF,WAAAA;AACxB,QAAI,CAAC,KAAKL,aAAaQ,IAAIF,GAAAA,GAAM;AAC/B,WAAKN,aAAaS,IAAIH,KAAKD,WAAAA;IAC7B;AACA,SAAKN,YAAYU,IAAIH,MAAM,KAAKP,YAAYW,IAAIJ,GAAAA,KAAQ,KAAK,CAAA;AAE7D,UAAMK,gBAAgBC,iBAAAA;AACtB,QAAID,eAAe;AACjB,WAAKV,eAAeQ,IAAIH,KAAKK,aAAAA;IAC/B;EACF;EAEOE,0BAA0B;AAC/B,UAAMC,OAAgC,CAAA;AACtC,SAAKf,YAAYgB,QAAQ,CAACC,OAAOV,QAAAA;AAC/B,YAAMD,cAAc,KAAKL,aAAaU,IAAIJ,GAAAA;AAC1C,UAAID,aAAa;AACfS,aAAKG,KAAK;UACRC,UAAUb,YAAYa,YAAY;UAClCC,QAAQd,YAAYc;UACpBC,MAAMf,YAAYe;UAClBC,MAAMhB,YAAYgB;UAClBC,KAAKC,yBAAyBlB,YAAYiB,GAAG;UAC7CE,WAAWC,4BAA4BpB,YAAYmB,SAAS;UAC5DE,iBAAiB,KAAKzB,eAAeS,IAAIJ,GAAAA,KAAQ;UACjDqB,aAAaX;QACf,CAAA;MACF;IACF,CAAA;AACA,SAAKjB,YAAY6B,MAAK;AACtB,SAAK5B,aAAa4B,MAAK;AACvB,WAAOd;EACT;EAEQP,OAAOF,aAA+C;AAC5D,UAAMwB,YAAY;MAChBxB,YAAYa,YAAY;MACxBb,YAAYc,OAAOW,YAAW;MAC9BzB,YAAYe;MACZf,YAAYgB;MACZhB,YAAYiB,IAAIS,KAAI;MACpB1B,YAAYmB,UAAUO,KAAI;MAC1BC,KAAK,GAAA;AACP,eAAOC,0BAAW,KAAA,EAAOC,OAAOL,SAAAA,EAAWM,OAAO,KAAA;EACpD;AACF;AAzDqBrC;AAArB,IAAqBA,qBAArB;AA2DO,SAASyB,yBAAyBD,KAAW;AAClD,MAAIA,IAAIc,UAAUxC,gBAAgB;AAChC,WAAO0B;EACT;AACA,QAAMe,SAAS;AACf,QAAMC,SAAS1C,iBAAiByC,OAAOD;AACvC,SAAOd,IAAIiB,UAAU,GAAGD,MAAAA,IAAUD;AACpC;AAPgBd;AAST,SAASE,4BAA4Be,OAAa;AACvD,QAAMH,SAAS;AACf,QAAMC,SAASzC,wBAAwBwC,OAAOD;AAC9C,QAAMK,QAAQD,MAAMT,KAAI,EAAGW,MAAM,IAAA;AACjC,QAAMC,iBAA2B,CAAA;AACjC,MAAIP,SAAS;AACb,aAAWQ,QAAQH,OAAO;AACxB,QAAIL,SAASQ,KAAKR,SAAS,IAAIE,QAAQ;AACrCK,qBAAe1B,KAAKoB,MAAAA;AACpB;IACF;AACAM,mBAAe1B,KAAK2B,IAAAA;AACpBR,cAAUQ,KAAKR,SAAS;EAC1B;AACA,SAAOO,eAAeX,KAAK,IAAA;AAC7B;AAfgBP;","names":["sentry","e","getSentryEventId","lastEventId","undefined","MAX_MSG_LENGTH","MAX_STACKTRACE_LENGTH","ServerErrorCounter","errorCounts","errorDetails","sentryEventIds","constructor","Map","addServerError","serverError","key","getKey","has","set","get","sentryEventId","getSentryEventId","getAndResetServerErrors","data","forEach","count","push","consumer","method","path","type","msg","truncateExceptionMessage","traceback","truncateExceptionStackTrace","sentry_event_id","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","length","suffix","cutoff","substring","stack","lines","split","truncatedLines","line"]}
|
|
@@ -10,15 +10,12 @@ declare class ServerErrorCounter {
|
|
|
10
10
|
private errorCounts;
|
|
11
11
|
private errorDetails;
|
|
12
12
|
private sentryEventIds;
|
|
13
|
-
private sentry;
|
|
14
13
|
constructor();
|
|
15
14
|
addServerError(serverError: ConsumerMethodPath & ServerError): void;
|
|
16
15
|
getAndResetServerErrors(): ServerErrorsItem[];
|
|
17
16
|
private getKey;
|
|
18
|
-
private getTruncatedMessage;
|
|
19
|
-
private getTruncatedStack;
|
|
20
|
-
private captureSentryEventId;
|
|
21
|
-
private tryImportSentry;
|
|
22
17
|
}
|
|
18
|
+
declare function truncateExceptionMessage(msg: string): string;
|
|
19
|
+
declare function truncateExceptionStackTrace(stack: string): string;
|
|
23
20
|
|
|
24
|
-
export { ServerErrorCounter as default };
|
|
21
|
+
export { ServerErrorCounter as default, truncateExceptionMessage, truncateExceptionStackTrace };
|
|
@@ -10,15 +10,12 @@ declare class ServerErrorCounter {
|
|
|
10
10
|
private errorCounts;
|
|
11
11
|
private errorDetails;
|
|
12
12
|
private sentryEventIds;
|
|
13
|
-
private sentry;
|
|
14
13
|
constructor();
|
|
15
14
|
addServerError(serverError: ConsumerMethodPath & ServerError): void;
|
|
16
15
|
getAndResetServerErrors(): ServerErrorsItem[];
|
|
17
16
|
private getKey;
|
|
18
|
-
private getTruncatedMessage;
|
|
19
|
-
private getTruncatedStack;
|
|
20
|
-
private captureSentryEventId;
|
|
21
|
-
private tryImportSentry;
|
|
22
17
|
}
|
|
18
|
+
declare function truncateExceptionMessage(msg: string): string;
|
|
19
|
+
declare function truncateExceptionStackTrace(stack: string): string;
|
|
23
20
|
|
|
24
|
-
export { ServerErrorCounter as default };
|
|
21
|
+
export { ServerErrorCounter as default, truncateExceptionMessage, truncateExceptionStackTrace };
|
|
@@ -3,18 +3,34 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/common/serverErrorCounter.ts
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
|
+
|
|
7
|
+
// src/common/sentry.ts
|
|
8
|
+
var sentry;
|
|
9
|
+
(async () => {
|
|
10
|
+
try {
|
|
11
|
+
sentry = await import("@sentry/node");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
15
|
+
function getSentryEventId() {
|
|
16
|
+
if (sentry && sentry.lastEventId) {
|
|
17
|
+
return sentry.lastEventId();
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
__name(getSentryEventId, "getSentryEventId");
|
|
22
|
+
|
|
23
|
+
// src/common/serverErrorCounter.ts
|
|
6
24
|
var MAX_MSG_LENGTH = 2048;
|
|
7
25
|
var MAX_STACKTRACE_LENGTH = 65536;
|
|
8
26
|
var _ServerErrorCounter = class _ServerErrorCounter {
|
|
9
27
|
errorCounts;
|
|
10
28
|
errorDetails;
|
|
11
29
|
sentryEventIds;
|
|
12
|
-
sentry;
|
|
13
30
|
constructor() {
|
|
14
31
|
this.errorCounts = /* @__PURE__ */ new Map();
|
|
15
32
|
this.errorDetails = /* @__PURE__ */ new Map();
|
|
16
33
|
this.sentryEventIds = /* @__PURE__ */ new Map();
|
|
17
|
-
this.tryImportSentry();
|
|
18
34
|
}
|
|
19
35
|
addServerError(serverError) {
|
|
20
36
|
const key = this.getKey(serverError);
|
|
@@ -22,7 +38,10 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
22
38
|
this.errorDetails.set(key, serverError);
|
|
23
39
|
}
|
|
24
40
|
this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);
|
|
25
|
-
|
|
41
|
+
const sentryEventId = getSentryEventId();
|
|
42
|
+
if (sentryEventId) {
|
|
43
|
+
this.sentryEventIds.set(key, sentryEventId);
|
|
44
|
+
}
|
|
26
45
|
}
|
|
27
46
|
getAndResetServerErrors() {
|
|
28
47
|
const data = [];
|
|
@@ -34,8 +53,8 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
34
53
|
method: serverError.method,
|
|
35
54
|
path: serverError.path,
|
|
36
55
|
type: serverError.type,
|
|
37
|
-
msg:
|
|
38
|
-
traceback:
|
|
56
|
+
msg: truncateExceptionMessage(serverError.msg),
|
|
57
|
+
traceback: truncateExceptionStackTrace(serverError.traceback),
|
|
39
58
|
sentry_event_id: this.sentryEventIds.get(key) || null,
|
|
40
59
|
error_count: count
|
|
41
60
|
});
|
|
@@ -56,49 +75,38 @@ var _ServerErrorCounter = class _ServerErrorCounter {
|
|
|
56
75
|
].join("|");
|
|
57
76
|
return createHash("md5").update(hashInput).digest("hex");
|
|
58
77
|
}
|
|
59
|
-
getTruncatedMessage(msg) {
|
|
60
|
-
msg = msg.trim();
|
|
61
|
-
if (msg.length <= MAX_MSG_LENGTH) {
|
|
62
|
-
return msg;
|
|
63
|
-
}
|
|
64
|
-
const suffix = "... (truncated)";
|
|
65
|
-
const cutoff = MAX_MSG_LENGTH - suffix.length;
|
|
66
|
-
return msg.substring(0, cutoff) + suffix;
|
|
67
|
-
}
|
|
68
|
-
getTruncatedStack(stack) {
|
|
69
|
-
const suffix = "... (truncated) ...";
|
|
70
|
-
const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
|
|
71
|
-
const lines = stack.trim().split("\n");
|
|
72
|
-
const truncatedLines = [];
|
|
73
|
-
let length = 0;
|
|
74
|
-
for (const line of lines) {
|
|
75
|
-
if (length + line.length + 1 > cutoff) {
|
|
76
|
-
truncatedLines.push(suffix);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
truncatedLines.push(line);
|
|
80
|
-
length += line.length + 1;
|
|
81
|
-
}
|
|
82
|
-
return truncatedLines.join("\n");
|
|
83
|
-
}
|
|
84
|
-
captureSentryEventId(serverErrorKey) {
|
|
85
|
-
if (this.sentry && this.sentry.lastEventId) {
|
|
86
|
-
const eventId = this.sentry.lastEventId();
|
|
87
|
-
if (eventId) {
|
|
88
|
-
this.sentryEventIds.set(serverErrorKey, eventId);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async tryImportSentry() {
|
|
93
|
-
try {
|
|
94
|
-
this.sentry = await import("@sentry/node");
|
|
95
|
-
} catch (e) {
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
78
|
};
|
|
99
79
|
__name(_ServerErrorCounter, "ServerErrorCounter");
|
|
100
80
|
var ServerErrorCounter = _ServerErrorCounter;
|
|
81
|
+
function truncateExceptionMessage(msg) {
|
|
82
|
+
if (msg.length <= MAX_MSG_LENGTH) {
|
|
83
|
+
return msg;
|
|
84
|
+
}
|
|
85
|
+
const suffix = "... (truncated)";
|
|
86
|
+
const cutoff = MAX_MSG_LENGTH - suffix.length;
|
|
87
|
+
return msg.substring(0, cutoff) + suffix;
|
|
88
|
+
}
|
|
89
|
+
__name(truncateExceptionMessage, "truncateExceptionMessage");
|
|
90
|
+
function truncateExceptionStackTrace(stack) {
|
|
91
|
+
const suffix = "... (truncated) ...";
|
|
92
|
+
const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;
|
|
93
|
+
const lines = stack.trim().split("\n");
|
|
94
|
+
const truncatedLines = [];
|
|
95
|
+
let length = 0;
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
if (length + line.length + 1 > cutoff) {
|
|
98
|
+
truncatedLines.push(suffix);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
truncatedLines.push(line);
|
|
102
|
+
length += line.length + 1;
|
|
103
|
+
}
|
|
104
|
+
return truncatedLines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
__name(truncateExceptionStackTrace, "truncateExceptionStackTrace");
|
|
101
107
|
export {
|
|
102
|
-
ServerErrorCounter as default
|
|
108
|
+
ServerErrorCounter as default,
|
|
109
|
+
truncateExceptionMessage,
|
|
110
|
+
truncateExceptionStackTrace
|
|
103
111
|
};
|
|
104
112
|
//# sourceMappingURL=serverErrorCounter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["
|
|
1
|
+
{"version":3,"sources":["serverErrorCounter.ts","sentry.ts"],"sourcesContent":["import { createHash } from \"crypto\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport { ConsumerMethodPath, ServerError, ServerErrorsItem } from \"./types.js\";\n\nconst MAX_MSG_LENGTH = 2048;\nconst MAX_STACKTRACE_LENGTH = 65536;\n\nexport default class ServerErrorCounter {\n private errorCounts: Map<string, number>;\n private errorDetails: Map<string, ConsumerMethodPath & ServerError>;\n private sentryEventIds: Map<string, string>;\n\n constructor() {\n this.errorCounts = new Map();\n this.errorDetails = new Map();\n this.sentryEventIds = new Map();\n }\n\n public addServerError(serverError: ConsumerMethodPath & ServerError) {\n const key = this.getKey(serverError);\n if (!this.errorDetails.has(key)) {\n this.errorDetails.set(key, serverError);\n }\n this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);\n\n const sentryEventId = getSentryEventId();\n if (sentryEventId) {\n this.sentryEventIds.set(key, sentryEventId);\n }\n }\n\n public getAndResetServerErrors() {\n const data: Array<ServerErrorsItem> = [];\n this.errorCounts.forEach((count, key) => {\n const serverError = this.errorDetails.get(key);\n if (serverError) {\n data.push({\n consumer: serverError.consumer || null,\n method: serverError.method,\n path: serverError.path,\n type: serverError.type,\n msg: truncateExceptionMessage(serverError.msg),\n traceback: truncateExceptionStackTrace(serverError.traceback),\n sentry_event_id: this.sentryEventIds.get(key) || null,\n error_count: count,\n });\n }\n });\n this.errorCounts.clear();\n this.errorDetails.clear();\n return data;\n }\n\n private getKey(serverError: ConsumerMethodPath & ServerError) {\n const hashInput = [\n serverError.consumer || \"\",\n serverError.method.toUpperCase(),\n serverError.path,\n serverError.type,\n serverError.msg.trim(),\n serverError.traceback.trim(),\n ].join(\"|\");\n return createHash(\"md5\").update(hashInput).digest(\"hex\");\n }\n}\n\nexport function truncateExceptionMessage(msg: string) {\n if (msg.length <= MAX_MSG_LENGTH) {\n return msg;\n }\n const suffix = \"... (truncated)\";\n const cutoff = MAX_MSG_LENGTH - suffix.length;\n return msg.substring(0, cutoff) + suffix;\n}\n\nexport function truncateExceptionStackTrace(stack: string) {\n const suffix = \"... (truncated) ...\";\n const cutoff = MAX_STACKTRACE_LENGTH - suffix.length;\n const lines = stack.trim().split(\"\\n\");\n const truncatedLines: string[] = [];\n let length = 0;\n for (const line of lines) {\n if (length + line.length + 1 > cutoff) {\n truncatedLines.push(suffix);\n break;\n }\n truncatedLines.push(line);\n length += line.length + 1;\n }\n return truncatedLines.join(\"\\n\");\n}\n","import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n try {\n sentry = await import(\"@sentry/node\");\n } catch (e) {\n // Sentry SDK is not installed, ignore\n }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n if (sentry && sentry.lastEventId) {\n return sentry.lastEventId();\n }\n return undefined;\n}\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;;;ACE3B,IAAIC;CAGH,YAAA;AACC,MAAI;AACFA,aAAS,MAAM,OAAO,cAAA;EACxB,SAASC,GAAG;EAEZ;AACF,GAAA;AAKO,SAASC,mBAAAA;AACd,MAAIF,UAAUA,OAAOG,aAAa;AAChC,WAAOH,OAAOG,YAAW;EAC3B;AACA,SAAOC;AACT;AALgBF;;;ADXhB,IAAMG,iBAAiB;AACvB,IAAMC,wBAAwB;AAE9B,IAAqBC,sBAArB,MAAqBA,oBAAAA;EACXC;EACAC;EACAC;EAERC,cAAc;AACZ,SAAKH,cAAc,oBAAII,IAAAA;AACvB,SAAKH,eAAe,oBAAIG,IAAAA;AACxB,SAAKF,iBAAiB,oBAAIE,IAAAA;EAC5B;EAEOC,eAAeC,aAA+C;AACnE,UAAMC,MAAM,KAAKC,OAAOF,WAAAA;AACxB,QAAI,CAAC,KAAKL,aAAaQ,IAAIF,GAAAA,GAAM;AAC/B,WAAKN,aAAaS,IAAIH,KAAKD,WAAAA;IAC7B;AACA,SAAKN,YAAYU,IAAIH,MAAM,KAAKP,YAAYW,IAAIJ,GAAAA,KAAQ,KAAK,CAAA;AAE7D,UAAMK,gBAAgBC,iBAAAA;AACtB,QAAID,eAAe;AACjB,WAAKV,eAAeQ,IAAIH,KAAKK,aAAAA;IAC/B;EACF;EAEOE,0BAA0B;AAC/B,UAAMC,OAAgC,CAAA;AACtC,SAAKf,YAAYgB,QAAQ,CAACC,OAAOV,QAAAA;AAC/B,YAAMD,cAAc,KAAKL,aAAaU,IAAIJ,GAAAA;AAC1C,UAAID,aAAa;AACfS,aAAKG,KAAK;UACRC,UAAUb,YAAYa,YAAY;UAClCC,QAAQd,YAAYc;UACpBC,MAAMf,YAAYe;UAClBC,MAAMhB,YAAYgB;UAClBC,KAAKC,yBAAyBlB,YAAYiB,GAAG;UAC7CE,WAAWC,4BAA4BpB,YAAYmB,SAAS;UAC5DE,iBAAiB,KAAKzB,eAAeS,IAAIJ,GAAAA,KAAQ;UACjDqB,aAAaX;QACf,CAAA;MACF;IACF,CAAA;AACA,SAAKjB,YAAY6B,MAAK;AACtB,SAAK5B,aAAa4B,MAAK;AACvB,WAAOd;EACT;EAEQP,OAAOF,aAA+C;AAC5D,UAAMwB,YAAY;MAChBxB,YAAYa,YAAY;MACxBb,YAAYc,OAAOW,YAAW;MAC9BzB,YAAYe;MACZf,YAAYgB;MACZhB,YAAYiB,IAAIS,KAAI;MACpB1B,YAAYmB,UAAUO,KAAI;MAC1BC,KAAK,GAAA;AACP,WAAOC,WAAW,KAAA,EAAOC,OAAOL,SAAAA,EAAWM,OAAO,KAAA;EACpD;AACF;AAzDqBrC;AAArB,IAAqBA,qBAArB;AA2DO,SAASyB,yBAAyBD,KAAW;AAClD,MAAIA,IAAIc,UAAUxC,gBAAgB;AAChC,WAAO0B;EACT;AACA,QAAMe,SAAS;AACf,QAAMC,SAAS1C,iBAAiByC,OAAOD;AACvC,SAAOd,IAAIiB,UAAU,GAAGD,MAAAA,IAAUD;AACpC;AAPgBd;AAST,SAASE,4BAA4Be,OAAa;AACvD,QAAMH,SAAS;AACf,QAAMC,SAASzC,wBAAwBwC,OAAOD;AAC9C,QAAMK,QAAQD,MAAMT,KAAI,EAAGW,MAAM,IAAA;AACjC,QAAMC,iBAA2B,CAAA;AACjC,MAAIP,SAAS;AACb,aAAWQ,QAAQH,OAAO;AACxB,QAAIL,SAASQ,KAAKR,SAAS,IAAIE,QAAQ;AACrCK,qBAAe1B,KAAKoB,MAAAA;AACpB;IACF;AACAM,mBAAe1B,KAAK2B,IAAAA;AACpBR,cAAUQ,KAAKR,SAAS;EAC1B;AACA,SAAOO,eAAeX,KAAK,IAAA;AAC7B;AAfgBP;","names":["createHash","sentry","e","getSentryEventId","lastEventId","undefined","MAX_MSG_LENGTH","MAX_STACKTRACE_LENGTH","ServerErrorCounter","errorCounts","errorDetails","sentryEventIds","constructor","Map","addServerError","serverError","key","getKey","has","set","get","sentryEventId","getSentryEventId","getAndResetServerErrors","data","forEach","count","push","consumer","method","path","type","msg","truncateExceptionMessage","traceback","truncateExceptionStackTrace","sentry_event_id","error_count","clear","hashInput","toUpperCase","trim","join","createHash","update","digest","length","suffix","cutoff","substring","stack","lines","split","truncatedLines","line"]}
|