apitally 1.0.0-beta.2 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +7 -2
  2. package/dist/activation.cjs +1 -0
  3. package/dist/activation.cjs.map +1 -1
  4. package/dist/activation.d.cts +1 -0
  5. package/dist/activation.d.ts +1 -0
  6. package/dist/activation.js +1 -0
  7. package/dist/activation.js.map +1 -1
  8. package/dist/consumers.cjs +96 -0
  9. package/dist/consumers.cjs.map +1 -0
  10. package/dist/consumers.d.cts +13 -0
  11. package/dist/consumers.d.ts +13 -0
  12. package/dist/consumers.js +93 -0
  13. package/dist/consumers.js.map +1 -0
  14. package/dist/context.cjs +2 -8
  15. package/dist/context.cjs.map +1 -1
  16. package/dist/context.d.cts +5 -5
  17. package/dist/context.d.ts +5 -5
  18. package/dist/context.js +3 -7
  19. package/dist/context.js.map +1 -1
  20. package/dist/index.cjs +2 -2
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/nestjs/index.cjs +6 -0
  27. package/dist/nestjs/index.cjs.map +1 -1
  28. package/dist/nestjs/index.js +6 -0
  29. package/dist/nestjs/index.js.map +1 -1
  30. package/dist/requestObservation.cjs +6 -4
  31. package/dist/requestObservation.cjs.map +1 -1
  32. package/dist/requestObservation.js +7 -5
  33. package/dist/requestObservation.js.map +1 -1
  34. package/dist/spanProcessor.cjs +0 -2
  35. package/dist/spanProcessor.cjs.map +1 -1
  36. package/dist/spanProcessor.js +0 -2
  37. package/dist/spanProcessor.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/consumer.cjs +0 -61
  40. package/dist/consumer.cjs.map +0 -1
  41. package/dist/consumer.d.cts +0 -12
  42. package/dist/consumer.d.ts +0 -12
  43. package/dist/consumer.js +0 -59
  44. package/dist/consumer.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"requestObservation.js","names":[],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n getConsumerHolder,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n const consumerHolder = getConsumerHolder(activeContext) ?? {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else {\n requestContext = withRequestHolders(\n extractedContext,\n spanHandle,\n requestRecord,\n consumerHolder,\n );\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"requestObservation.js","names":[],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport { emitConsumerUpdateIfChanged } from \"./consumers.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else {\n requestContext = withRequestHolders(extractedContext, spanHandle, requestRecord);\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_logger = require("./logger.cjs");
3
3
  const require_config = require("./config.cjs");
4
4
  const require_context = require("./context.cjs");
5
- const require_consumer = require("./consumer.cjs");
6
5
  let _opentelemetry_api = require("@opentelemetry/api");
7
6
  let _opentelemetry_core = require("@opentelemetry/core");
8
7
  //#region src/spanProcessor.ts
@@ -237,7 +236,6 @@ var SpanPipeline = class {
237
236
  if (handle) handle.span = span;
238
237
  const record = parentContext.getValue(require_context.REQUEST_RECORD_KEY);
239
238
  if (record) record.serverSpanId = spanId;
240
- require_consumer.writeConsumerAttributesFromContext(parentContext, span, record);
241
239
  writeUrlAttributesFromFullUrl(span);
242
240
  const dropReason = this.resolveRequestDropReasonBeforeSampling(span.attributes) ?? (this.isRequestSampledIn(span) ? void 0 : "sampled-out");
243
241
  if (dropReason) {
@@ -1 +1 @@
1
- {"version":3,"file":"spanProcessor.cjs","names":["compilePatterns","EXCLUDE_USER_AGENTS","SpanKind","getConfig","DEFAULT_EXCLUDE_PATHS","matchesAny","SPAN_HANDLE_KEY","REQUEST_RECORD_KEY","hrTime","hrTimeDuration"],"sources":["../src/spanProcessor.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind } from \"@opentelemetry/api\";\nimport { hrTime, hrTimeDuration } from \"@opentelemetry/core\";\nimport type { ReadableSpan, Span, SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport {\n type ApitallyConfig,\n compilePatterns,\n DEFAULT_EXCLUDE_PATHS,\n EXCLUDE_USER_AGENTS,\n getConfig,\n matchesAny,\n type SamplingCallback,\n} from \"./config.js\";\nimport { writeConsumerAttributesFromContext } from \"./consumer.js\";\nimport {\n REQUEST_RECORD_KEY,\n type RequestDropReason,\n type RequestRecord,\n SPAN_HANDLE_KEY,\n type SpanHandle,\n} from \"./context.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\n\nconst MAX_BUFFERED_SPANS = 1_000;\nconst MAX_TRACKED_SPAN_IDS = MAX_BUFFERED_SPANS + 1;\nconst MAX_STASHED_REQUESTS = 2_048;\nconst MAX_KEPT_SPAN_IDS = 10_000;\nconst EXCLUDE_USER_AGENT_PATTERNS = compilePatterns(EXCLUDE_USER_AGENTS);\n\n// Transport-captured headers and bodies are attached only to Apitally's export\n// copy.\nexport interface RequestStash {\n requestHeaders?: Record<string, string | string[]>;\n requestBody?: Buffer;\n requestContentEncoding?: string;\n responseHeaders?: Record<string, string | string[]>;\n responseBody?: Buffer;\n responseContentEncoding?: string;\n}\n\nexport interface ApitallySpanData {\n record?: RequestRecord;\n stash?: RequestStash;\n demoteToInternal?: boolean;\n}\n\n// Mutable ReadableSpan-shaped copy on Apitally's private export path; the span\n// handed to user-attached processors is never mutated.\nexport type SpanCopy = {\n -readonly [Key in keyof ReadableSpan]: ReadableSpan[Key];\n} & { apitallyData?: ApitallySpanData };\n\n// The public processor delegates to the process-global pipeline. Construction\n// only declares that the application owns the tracer provider.\nexport class ApitallySpanProcessor implements SpanProcessor {\n constructor() {\n setApitallySpanProcessorDeclared();\n }\n\n onStart(span: Span, parentContext: Context): void {\n let pipeline = getActiveSpanPipeline();\n if (!pipeline && span.kind === SpanKind.SERVER) {\n getServerSpanActivationCallback()?.();\n pipeline = getActiveSpanPipeline();\n }\n pipeline?.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n getActiveSpanPipeline()?.onEnd(span);\n }\n\n // Flush failures resolve instead of rejecting into the user's provider.\n forceFlush(): Promise<void> {\n const pipeline = getActiveSpanPipeline();\n if (!pipeline) {\n return Promise.resolve();\n }\n return pipeline.forceFlush().catch((error: unknown) => {\n logDebug(`Error flushing spans: ${String(error)}`);\n });\n }\n\n // A user provider's shutdown only flushes released requests; SDK shutdown owns\n // teardown.\n shutdown(): Promise<void> {\n return this.forceFlush();\n }\n}\n\ntype ServerSpanActivationCallback = () => void;\n\n// Symbol.for keys let ESM and CJS builds share processor coordination.\nconst ACTIVE_SPAN_PIPELINE_KEY = Symbol.for(\"apitally.activeSpanPipeline\");\nconst APITALLY_SPAN_PROCESSOR_DECLARED_KEY = Symbol.for(\"apitally.spanProcessorDeclared\");\nconst SERVER_SPAN_ACTIVATION_CALLBACK_KEY = Symbol.for(\"apitally.serverSpanActivationCallback\");\n\nexport function setActiveSpanPipeline(pipeline: SpanPipeline | undefined): void {\n (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY] = pipeline;\n}\n\nexport function getActiveSpanPipeline(): SpanPipeline | undefined {\n return (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY];\n}\n\nexport function setServerSpanActivationCallback(callback: ServerSpanActivationCallback): void {\n const holder = globalThis as Record<symbol, ServerSpanActivationCallback | undefined>;\n holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY] ??= callback;\n}\n\nexport function isApitallySpanProcessorDeclared(): boolean {\n return (\n (globalThis as Record<symbol, boolean | undefined>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] ===\n true\n );\n}\n\nexport function resetSpanProcessorState(): void {\n const holder = globalThis as Record<symbol, unknown>;\n delete holder[ACTIVE_SPAN_PIPELINE_KEY];\n delete holder[APITALLY_SPAN_PROCESSOR_DECLARED_KEY];\n delete holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY];\n}\n\nfunction setApitallySpanProcessorDeclared(): void {\n (globalThis as Record<symbol, boolean>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] = true;\n}\n\nfunction getServerSpanActivationCallback(): ServerSpanActivationCallback | undefined {\n return (globalThis as Record<symbol, ServerSpanActivationCallback | undefined>)[\n SERVER_SPAN_ACTIVATION_CALLBACK_KEY\n ];\n}\n\ninterface RequestEntry {\n readonly serverSpanId: string;\n readonly serverSpan: Span;\n readonly spanIds: Set<string>;\n record?: RequestRecord;\n buffered: ReadableSpan[];\n endedServerSpan?: ReadableSpan;\n transportCompleted: boolean;\n released: boolean;\n}\n\ninterface KeptSpanEntry {\n readonly serverSpanId: string;\n // Carries the demotion decision across release for spans that end late.\n readonly demoted: boolean;\n}\n\n// Requests remain buffered until the SERVER span and transport complete, then\n// one keep/drop decision releases them once.\nexport class SpanPipeline implements SpanProcessor {\n // Callbacks are optional because metrics and log pipelines start at activation.\n metricsRecorder?: (record: RequestRecord) => void;\n onRequestFinished?: (serverSpanId: string, kept: boolean) => void;\n private readonly downstream: SpanProcessor;\n private readonly config: ApitallyConfig;\n private readonly sampleRateBound: bigint;\n private readonly excludePathPatterns: RegExp[];\n // Every span ID remains mapped to one request until completion so late logs\n // resolve. A miss means dropped, completed, or non-request telemetry.\n private readonly requests = new Map<string, RequestEntry>();\n private readonly stash = new Map<string, RequestStash>();\n private readonly demotedSpanIds = new Set<string>();\n // Kept span IDs remain resolvable after release for late spans and logs; the\n // oldest are evicted at the cap.\n private readonly keptSpanIds = new Map<string, KeptSpanEntry>();\n\n constructor(\n downstream: SpanProcessor,\n private readonly flushExporter?: () => Promise<void>,\n ) {\n this.downstream = downstream;\n this.config = getConfig();\n this.sampleRateBound = boundForSampleRate(this.config.sampleRate);\n this.excludePathPatterns = compilePatterns(DEFAULT_EXCLUDE_PATHS, this.config.excludePaths);\n }\n\n onStart(span: Span, parentContext: Context): void {\n try {\n if (isContribPerMessageSpan(span)) {\n return;\n }\n const parent = span.parentSpanContext;\n if (!parent || parent.isRemote) {\n if (span.kind === SpanKind.SERVER) {\n this.startRequest(span, parentContext);\n }\n return;\n }\n const entry = this.requests.get(parent.spanId);\n const spanId = span.spanContext().spanId;\n if (!entry) {\n // A span started under a request already released as kept exports at\n // its end, like the released request's other late telemetry.\n const kept = this.keptSpanIds.get(parent.spanId);\n if (kept) {\n this.addKeptSpanId(spanId, {\n serverSpanId: kept.serverSpanId,\n demoted: false,\n });\n }\n return;\n }\n if (entry.spanIds.size >= MAX_TRACKED_SPAN_IDS) {\n logDebug(\"Apitally span tracking cap reached, dropping the span\");\n return;\n }\n if (span.kind === SpanKind.SERVER) {\n // A second middleware produced a SERVER span for this request. Apitally\n // demotes its copy, but user exporters retain the duplicate.\n this.demotedSpanIds.add(spanId);\n logWarning(\n `Detected a duplicate SERVER span produced by the instrumentation scope \"${span.instrumentationScope?.name ?? \"unknown\"}\" inside an active request. Apitally exports it as an INTERNAL span, but your own OpenTelemetry exporters still receive the duplicate. To resolve this, remove the middleware that produces it.`,\n );\n }\n this.requests.set(spanId, entry);\n entry.spanIds.add(spanId);\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n try {\n const spanId = span.spanContext().spanId;\n const isDemoted = this.demotedSpanIds.delete(spanId);\n const entry = this.requests.get(spanId);\n if (!entry) {\n const kept = this.keptSpanIds.get(spanId);\n if (kept) {\n let exportSpan = span;\n if (kept.demoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n }\n return;\n }\n if (spanId === entry.serverSpanId) {\n if (!entry.record) {\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n return;\n }\n entry.endedServerSpan = span;\n this.releaseIfComplete(entry);\n return;\n }\n // The span id stays mapped so log records emitted after this span ended\n // still resolve to the request until it completes.\n let exportSpan = span;\n if (isDemoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n if (entry.buffered.length < MAX_BUFFERED_SPANS) {\n entry.buffered.push(exportSpan);\n } else {\n logDebug(\"Apitally span buffer cap reached, dropping the span\");\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n attachRequestRecord(serverSpanId: string, record: RequestRecord): void {\n const entry = this.requests.get(serverSpanId);\n if (entry) {\n entry.record ??= record;\n }\n }\n\n // Transport completion triggers response sampling and metrics independently\n // of span-end timing. Map misses still record metrics and discard other data.\n handleTransportCompletion(record: RequestRecord): void {\n try {\n record.dropReason = resolveTransportDropReason(record.attributes) ?? record.dropReason;\n const entry =\n record.serverSpanId !== undefined ? this.requests.get(record.serverSpanId) : undefined;\n if (entry && !entry.transportCompleted && !entry.released) {\n entry.record ??= record;\n entry.transportCompleted = true;\n if (!this.isResponseSampledIn(entry)) {\n this.dropRequestOnResponse(entry, record);\n }\n }\n this.metricsRecorder?.(record);\n if (entry) {\n this.releaseIfComplete(entry);\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n // Holds captured headers and bodies until the exporter attaches them to the\n // exported SERVER span copy. Fields already stashed are kept unless replaced.\n updateStash(serverSpanId: string, update: RequestStash): void {\n try {\n // A request without an in-flight map entry can never release, so its\n // stash entry would sit unconsumed until the cap evicts it.\n if (!this.requests.has(serverSpanId)) {\n return;\n }\n let entry = this.stash.get(serverSpanId);\n if (!entry) {\n if (this.stash.size >= MAX_STASHED_REQUESTS) {\n const oldestKey = this.stash.keys().next().value;\n if (oldestKey !== undefined) {\n this.stash.delete(oldestKey);\n logDebug(\"Apitally payload stash cap reached, dropping the oldest entry\");\n }\n }\n entry = {};\n this.stash.set(serverSpanId, entry);\n }\n if (update.requestHeaders !== undefined) {\n entry.requestHeaders = update.requestHeaders;\n }\n if (update.requestBody !== undefined) {\n entry.requestBody = update.requestBody;\n }\n if (update.requestContentEncoding !== undefined) {\n entry.requestContentEncoding = update.requestContentEncoding;\n }\n if (update.responseHeaders !== undefined) {\n entry.responseHeaders = update.responseHeaders;\n }\n if (update.responseBody !== undefined) {\n entry.responseBody = update.responseBody;\n }\n if (update.responseContentEncoding !== undefined) {\n entry.responseContentEncoding = update.responseContentEncoding;\n }\n } catch (error) {\n logDebug(`Error updating the request stash: ${String(error)}`);\n }\n }\n\n // Kept requests remain resolvable after release so late logs retain their\n // request association.\n resolveServerSpanId(spanId: string): string | undefined {\n return this.requests.get(spanId)?.serverSpanId ?? this.keptSpanIds.get(spanId)?.serverSpanId;\n }\n\n isRequestInFlight(serverSpanId: string): boolean {\n return this.requests.has(serverSpanId);\n }\n\n // Exclusion answers \"never wanted\" and runs strictly before sampling; an\n // excluded request never invokes a user sampling callback.\n resolveRequestDropReasonBeforeSampling(attributes: Attributes): RequestDropReason | undefined {\n const transportDropReason = resolveTransportDropReason(attributes);\n if (transportDropReason !== undefined) {\n return transportDropReason;\n }\n const path = attributes[\"url.path\"] ?? attributes[\"http.target\"];\n if (typeof path === \"string\" && matchesAny(this.excludePathPatterns, path.split(\"?\")[0])) {\n return \"excluded\";\n }\n const userAgent = attributes[\"user_agent.original\"] ?? attributes[\"http.user_agent\"];\n if (typeof userAgent === \"string\" && matchesAny(EXCLUDE_USER_AGENT_PATTERNS, userAgent)) {\n return \"excluded\";\n }\n return undefined;\n }\n\n async forceFlush(): Promise<void> {\n await this.downstream.forceFlush();\n // OTel flushes queued batches but does not await already-running exports.\n await this.flushExporter?.();\n }\n\n // Shutdown releases requests with completed transport observation once and\n // discards incomplete requests, which cannot complete afterward.\n async shutdown(): Promise<void> {\n try {\n for (const entry of new Set(this.requests.values())) {\n if (!entry.released && entry.transportCompleted) {\n this.releaseRequest(entry, entry.endedServerSpan ?? entry.serverSpan);\n }\n }\n this.requests.clear();\n this.stash.clear();\n this.demotedSpanIds.clear();\n this.keptSpanIds.clear();\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n await this.downstream.shutdown();\n }\n\n private startRequest(span: Span, parentContext: Context): void {\n const spanId = span.spanContext().spanId;\n // The handle is filled for every local-root SERVER span, independent of the\n // keep decision, so runtime writes always reach the current request's span.\n const handle = parentContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n if (handle) {\n handle.span = span;\n }\n const record = parentContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined;\n if (record) {\n record.serverSpanId = spanId;\n }\n writeConsumerAttributesFromContext(parentContext, span, record);\n writeUrlAttributesFromFullUrl(span);\n const dropReason =\n this.resolveRequestDropReasonBeforeSampling(span.attributes) ??\n (this.isRequestSampledIn(span) ? undefined : \"sampled-out\");\n if (dropReason) {\n if (record) {\n record.dropReason = dropReason;\n }\n return;\n }\n this.requests.set(spanId, {\n serverSpanId: spanId,\n serverSpan: span,\n spanIds: new Set([spanId]),\n record,\n buffered: [],\n transportCompleted: false,\n released: false,\n });\n }\n\n private isRequestSampledIn(span: Span): boolean {\n const callback = this.config.sampleOnRequest;\n const rate = callback\n ? resolveCallbackSampleRate(callback, span, \"sampleOnRequest\")\n : undefined;\n const bound = rate === undefined ? this.sampleRateBound : boundForSampleRate(rate);\n return isTraceSampledIn(span.spanContext().traceId, bound);\n }\n\n // Abstention leaves the request-stage decision standing; it never re-tests the\n // static sample rate.\n private isResponseSampledIn(entry: RequestEntry): boolean {\n const callback = this.config.sampleOnResponse;\n if (!callback) {\n return true;\n }\n const serverSpan = entry.endedServerSpan ?? entry.serverSpan;\n let snapshot: ReadableSpan = serverSpan;\n if (entry.record && Object.keys(entry.record.attributes).length > 0) {\n // The callback sees transport-observed values even when they were learned\n // after the span ended (final route, response size).\n const copy = copySpan(serverSpan);\n copy.attributes = {\n ...serverSpan.attributes,\n ...entry.record.attributes,\n };\n snapshot = copy;\n }\n const rate = resolveCallbackSampleRate(callback, snapshot, \"sampleOnResponse\");\n if (rate === undefined) {\n return true;\n }\n return isTraceSampledIn(serverSpan.spanContext().traceId, boundForSampleRate(rate));\n }\n\n private dropRequestOnResponse(entry: RequestEntry, record: RequestRecord): void {\n // Removing every span id sends the dropped request's late telemetry to the\n // lookup-miss rule, so it is discarded locally.\n entry.released = true;\n entry.buffered = [];\n record.dropReason = \"sampled-out\";\n if (entry.record) {\n entry.record.dropReason = \"sampled-out\";\n }\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n }\n\n private releaseIfComplete(entry: RequestEntry): void {\n if (entry.released || !entry.transportCompleted || !entry.endedServerSpan) {\n return;\n }\n this.releaseRequest(entry, entry.endedServerSpan);\n }\n\n // Descendants, the SERVER span, and logs enter downstream processing once, in\n // that order. The exporter applies the request record and stash later.\n private releaseRequest(entry: RequestEntry, serverSpan: ReadableSpan): void {\n entry.released = true;\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.addKeptSpanId(spanId, {\n serverSpanId: entry.serverSpanId,\n demoted: this.demotedSpanIds.delete(spanId),\n });\n }\n for (const bufferedSpan of entry.buffered) {\n this.downstream.onEnd(bufferedSpan);\n }\n entry.buffered = [];\n const stash = this.stash.get(entry.serverSpanId);\n this.stash.delete(entry.serverSpanId);\n let exportSpan = serverSpan;\n if (entry.record || stash || !serverSpan.ended) {\n const copy = copySpan(serverSpan);\n if (!serverSpan.ended) {\n // A SERVER span released during shutdown uses shutdown as its end time.\n copy.endTime = hrTime();\n copy.duration = hrTimeDuration(copy.startTime, copy.endTime);\n copy.ended = true;\n }\n copy.apitallyData = { record: entry.record, stash };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n this.onRequestFinished?.(entry.serverSpanId, true);\n }\n\n private removeCompletedRequestSpanIds(entry: RequestEntry): void {\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.demotedSpanIds.delete(spanId);\n }\n }\n\n private addKeptSpanId(spanId: string, kept: KeptSpanEntry): void {\n if (this.keptSpanIds.size >= MAX_KEPT_SPAN_IDS) {\n const oldestId = this.keptSpanIds.keys().next().value;\n if (oldestId !== undefined) {\n this.keptSpanIds.delete(oldestId);\n }\n }\n this.keptSpanIds.set(spanId, kept);\n }\n}\n\nexport function copySpan(span: ReadableSpan): SpanCopy {\n const spanContext = span.spanContext();\n return {\n name: span.name,\n kind: span.kind,\n spanContext: () => spanContext,\n parentSpanContext: span.parentSpanContext,\n startTime: span.startTime,\n endTime: span.endTime,\n status: span.status,\n attributes: span.attributes,\n links: span.links,\n events: span.events,\n duration: span.duration,\n ended: span.ended,\n resource: span.resource,\n instrumentationScope: span.instrumentationScope,\n droppedAttributesCount: span.droppedAttributesCount,\n droppedEventsCount: span.droppedEventsCount,\n droppedLinksCount: span.droppedLinksCount,\n };\n}\n\nfunction resolveTransportDropReason(attributes: Attributes): RequestDropReason | undefined {\n const method = attributes[\"http.request.method\"] ?? attributes[\"http.method\"];\n if (method === \"OPTIONS\") {\n return \"method\";\n }\n const scheme = attributes[\"url.scheme\"] ?? attributes[\"http.scheme\"];\n return scheme === \"ws\" || scheme === \"wss\" ? \"scheme\" : undefined;\n}\n\n// Missing path, query, and target attributes are derived from the full URL\n// because exclusion, display, and redaction require them.\nfunction writeUrlAttributesFromFullUrl(span: Span): void {\n const attributes = span.attributes;\n if (attributes[\"url.path\"] !== undefined || attributes[\"http.target\"] !== undefined) {\n return;\n }\n const url = attributes[\"url.full\"] ?? attributes[\"http.url\"];\n if (typeof url !== \"string\") {\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n const query = parsed.search.replace(/^\\?/, \"\");\n span.setAttribute(\"url.path\", parsed.pathname);\n if (query) {\n span.setAttribute(\"url.query\", query);\n }\n span.setAttribute(\"http.target\", query ? `${parsed.pathname}?${query}` : parsed.pathname);\n}\n\nconst TRACE_ID_LOW_64_BITS_MASK = (1n << 64n) - 1n;\n\nfunction boundForSampleRate(rate: number): bigint {\n return BigInt(Math.round(rate * 2 ** 64));\n}\n\n// Low 64-bit ratio sampling is deterministic per trace. Comparing the same\n// value at both stages makes the lower rate decisive.\nfunction isTraceSampledIn(traceId: string, bound: bigint): boolean {\n return (BigInt(`0x${traceId}`) & TRACE_ID_LOW_64_BITS_MASK) < bound;\n}\n\n// A throwing or invalid-returning callback resolves to keep: a user bug must\n// never lose data without a warning. Undefined means the callback abstained.\nfunction resolveCallbackSampleRate(\n callback: SamplingCallback,\n span: ReadableSpan,\n optionName: string,\n): number | undefined {\n let result: unknown;\n try {\n result = callback(span);\n } catch {\n logWarning(`The Apitally ${optionName} callback threw an error, so the request was captured`);\n return 1;\n }\n if (result === undefined || result === null) {\n return undefined;\n }\n if (typeof result === \"boolean\") {\n return result ? 1 : 0;\n }\n if (typeof result === \"number\" && result >= 0 && result <= 1) {\n return result;\n }\n logWarning(\n `The Apitally ${optionName} callback returned an invalid value, so the request was captured. Sampling callbacks must synchronously return a number between 0 and 1, a boolean, or undefined.`,\n );\n return 1;\n}\n\nfunction isContribPerMessageSpan(span: Span): boolean {\n const scopeName = span.instrumentationScope?.name ?? \"\";\n return (\n span.kind === SpanKind.INTERNAL &&\n [\" http send\", \" http receive\", \" websocket send\", \" websocket receive\"].some((suffix) =>\n span.name.endsWith(suffix),\n ) &&\n (scopeName.startsWith(\"@opentelemetry/instrumentation\") ||\n scopeName.startsWith(\"opentelemetry.instrumentation.\"))\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"spanProcessor.cjs","names":["compilePatterns","EXCLUDE_USER_AGENTS","SpanKind","getConfig","DEFAULT_EXCLUDE_PATHS","matchesAny","SPAN_HANDLE_KEY","REQUEST_RECORD_KEY","hrTime","hrTimeDuration"],"sources":["../src/spanProcessor.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind } from \"@opentelemetry/api\";\nimport { hrTime, hrTimeDuration } from \"@opentelemetry/core\";\nimport type { ReadableSpan, Span, SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport {\n type ApitallyConfig,\n compilePatterns,\n DEFAULT_EXCLUDE_PATHS,\n EXCLUDE_USER_AGENTS,\n getConfig,\n matchesAny,\n type SamplingCallback,\n} from \"./config.js\";\nimport {\n REQUEST_RECORD_KEY,\n type RequestDropReason,\n type RequestRecord,\n SPAN_HANDLE_KEY,\n type SpanHandle,\n} from \"./context.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\n\nconst MAX_BUFFERED_SPANS = 1_000;\nconst MAX_TRACKED_SPAN_IDS = MAX_BUFFERED_SPANS + 1;\nconst MAX_STASHED_REQUESTS = 2_048;\nconst MAX_KEPT_SPAN_IDS = 10_000;\nconst EXCLUDE_USER_AGENT_PATTERNS = compilePatterns(EXCLUDE_USER_AGENTS);\n\n// Transport-captured headers and bodies are attached only to Apitally's export\n// copy.\nexport interface RequestStash {\n requestHeaders?: Record<string, string | string[]>;\n requestBody?: Buffer;\n requestContentEncoding?: string;\n responseHeaders?: Record<string, string | string[]>;\n responseBody?: Buffer;\n responseContentEncoding?: string;\n}\n\nexport interface ApitallySpanData {\n record?: RequestRecord;\n stash?: RequestStash;\n demoteToInternal?: boolean;\n}\n\n// Mutable ReadableSpan-shaped copy on Apitally's private export path; the span\n// handed to user-attached processors is never mutated.\nexport type SpanCopy = {\n -readonly [Key in keyof ReadableSpan]: ReadableSpan[Key];\n} & { apitallyData?: ApitallySpanData };\n\n// The public processor delegates to the process-global pipeline. Construction\n// only declares that the application owns the tracer provider.\nexport class ApitallySpanProcessor implements SpanProcessor {\n constructor() {\n setApitallySpanProcessorDeclared();\n }\n\n onStart(span: Span, parentContext: Context): void {\n let pipeline = getActiveSpanPipeline();\n if (!pipeline && span.kind === SpanKind.SERVER) {\n getServerSpanActivationCallback()?.();\n pipeline = getActiveSpanPipeline();\n }\n pipeline?.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n getActiveSpanPipeline()?.onEnd(span);\n }\n\n // Flush failures resolve instead of rejecting into the user's provider.\n forceFlush(): Promise<void> {\n const pipeline = getActiveSpanPipeline();\n if (!pipeline) {\n return Promise.resolve();\n }\n return pipeline.forceFlush().catch((error: unknown) => {\n logDebug(`Error flushing spans: ${String(error)}`);\n });\n }\n\n // A user provider's shutdown only flushes released requests; SDK shutdown owns\n // teardown.\n shutdown(): Promise<void> {\n return this.forceFlush();\n }\n}\n\ntype ServerSpanActivationCallback = () => void;\n\n// Symbol.for keys let ESM and CJS builds share processor coordination.\nconst ACTIVE_SPAN_PIPELINE_KEY = Symbol.for(\"apitally.activeSpanPipeline\");\nconst APITALLY_SPAN_PROCESSOR_DECLARED_KEY = Symbol.for(\"apitally.spanProcessorDeclared\");\nconst SERVER_SPAN_ACTIVATION_CALLBACK_KEY = Symbol.for(\"apitally.serverSpanActivationCallback\");\n\nexport function setActiveSpanPipeline(pipeline: SpanPipeline | undefined): void {\n (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY] = pipeline;\n}\n\nexport function getActiveSpanPipeline(): SpanPipeline | undefined {\n return (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY];\n}\n\nexport function setServerSpanActivationCallback(callback: ServerSpanActivationCallback): void {\n const holder = globalThis as Record<symbol, ServerSpanActivationCallback | undefined>;\n holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY] ??= callback;\n}\n\nexport function isApitallySpanProcessorDeclared(): boolean {\n return (\n (globalThis as Record<symbol, boolean | undefined>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] ===\n true\n );\n}\n\nexport function resetSpanProcessorState(): void {\n const holder = globalThis as Record<symbol, unknown>;\n delete holder[ACTIVE_SPAN_PIPELINE_KEY];\n delete holder[APITALLY_SPAN_PROCESSOR_DECLARED_KEY];\n delete holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY];\n}\n\nfunction setApitallySpanProcessorDeclared(): void {\n (globalThis as Record<symbol, boolean>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] = true;\n}\n\nfunction getServerSpanActivationCallback(): ServerSpanActivationCallback | undefined {\n return (globalThis as Record<symbol, ServerSpanActivationCallback | undefined>)[\n SERVER_SPAN_ACTIVATION_CALLBACK_KEY\n ];\n}\n\ninterface RequestEntry {\n readonly serverSpanId: string;\n readonly serverSpan: Span;\n readonly spanIds: Set<string>;\n record?: RequestRecord;\n buffered: ReadableSpan[];\n endedServerSpan?: ReadableSpan;\n transportCompleted: boolean;\n released: boolean;\n}\n\ninterface KeptSpanEntry {\n readonly serverSpanId: string;\n // Carries the demotion decision across release for spans that end late.\n readonly demoted: boolean;\n}\n\n// Requests remain buffered until the SERVER span and transport complete, then\n// one keep/drop decision releases them once.\nexport class SpanPipeline implements SpanProcessor {\n // Callbacks are optional because metrics and log pipelines start at activation.\n metricsRecorder?: (record: RequestRecord) => void;\n onRequestFinished?: (serverSpanId: string, kept: boolean) => void;\n private readonly downstream: SpanProcessor;\n private readonly config: ApitallyConfig;\n private readonly sampleRateBound: bigint;\n private readonly excludePathPatterns: RegExp[];\n // Every span ID remains mapped to one request until completion so late logs\n // resolve. A miss means dropped, completed, or non-request telemetry.\n private readonly requests = new Map<string, RequestEntry>();\n private readonly stash = new Map<string, RequestStash>();\n private readonly demotedSpanIds = new Set<string>();\n // Kept span IDs remain resolvable after release for late spans and logs; the\n // oldest are evicted at the cap.\n private readonly keptSpanIds = new Map<string, KeptSpanEntry>();\n\n constructor(\n downstream: SpanProcessor,\n private readonly flushExporter?: () => Promise<void>,\n ) {\n this.downstream = downstream;\n this.config = getConfig();\n this.sampleRateBound = boundForSampleRate(this.config.sampleRate);\n this.excludePathPatterns = compilePatterns(DEFAULT_EXCLUDE_PATHS, this.config.excludePaths);\n }\n\n onStart(span: Span, parentContext: Context): void {\n try {\n if (isContribPerMessageSpan(span)) {\n return;\n }\n const parent = span.parentSpanContext;\n if (!parent || parent.isRemote) {\n if (span.kind === SpanKind.SERVER) {\n this.startRequest(span, parentContext);\n }\n return;\n }\n const entry = this.requests.get(parent.spanId);\n const spanId = span.spanContext().spanId;\n if (!entry) {\n // A span started under a request already released as kept exports at\n // its end, like the released request's other late telemetry.\n const kept = this.keptSpanIds.get(parent.spanId);\n if (kept) {\n this.addKeptSpanId(spanId, {\n serverSpanId: kept.serverSpanId,\n demoted: false,\n });\n }\n return;\n }\n if (entry.spanIds.size >= MAX_TRACKED_SPAN_IDS) {\n logDebug(\"Apitally span tracking cap reached, dropping the span\");\n return;\n }\n if (span.kind === SpanKind.SERVER) {\n // A second middleware produced a SERVER span for this request. Apitally\n // demotes its copy, but user exporters retain the duplicate.\n this.demotedSpanIds.add(spanId);\n logWarning(\n `Detected a duplicate SERVER span produced by the instrumentation scope \"${span.instrumentationScope?.name ?? \"unknown\"}\" inside an active request. Apitally exports it as an INTERNAL span, but your own OpenTelemetry exporters still receive the duplicate. To resolve this, remove the middleware that produces it.`,\n );\n }\n this.requests.set(spanId, entry);\n entry.spanIds.add(spanId);\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n try {\n const spanId = span.spanContext().spanId;\n const isDemoted = this.demotedSpanIds.delete(spanId);\n const entry = this.requests.get(spanId);\n if (!entry) {\n const kept = this.keptSpanIds.get(spanId);\n if (kept) {\n let exportSpan = span;\n if (kept.demoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n }\n return;\n }\n if (spanId === entry.serverSpanId) {\n if (!entry.record) {\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n return;\n }\n entry.endedServerSpan = span;\n this.releaseIfComplete(entry);\n return;\n }\n // The span id stays mapped so log records emitted after this span ended\n // still resolve to the request until it completes.\n let exportSpan = span;\n if (isDemoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n if (entry.buffered.length < MAX_BUFFERED_SPANS) {\n entry.buffered.push(exportSpan);\n } else {\n logDebug(\"Apitally span buffer cap reached, dropping the span\");\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n attachRequestRecord(serverSpanId: string, record: RequestRecord): void {\n const entry = this.requests.get(serverSpanId);\n if (entry) {\n entry.record ??= record;\n }\n }\n\n // Transport completion triggers response sampling and metrics independently\n // of span-end timing. Map misses still record metrics and discard other data.\n handleTransportCompletion(record: RequestRecord): void {\n try {\n record.dropReason = resolveTransportDropReason(record.attributes) ?? record.dropReason;\n const entry =\n record.serverSpanId !== undefined ? this.requests.get(record.serverSpanId) : undefined;\n if (entry && !entry.transportCompleted && !entry.released) {\n entry.record ??= record;\n entry.transportCompleted = true;\n if (!this.isResponseSampledIn(entry)) {\n this.dropRequestOnResponse(entry, record);\n }\n }\n this.metricsRecorder?.(record);\n if (entry) {\n this.releaseIfComplete(entry);\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n // Holds captured headers and bodies until the exporter attaches them to the\n // exported SERVER span copy. Fields already stashed are kept unless replaced.\n updateStash(serverSpanId: string, update: RequestStash): void {\n try {\n // A request without an in-flight map entry can never release, so its\n // stash entry would sit unconsumed until the cap evicts it.\n if (!this.requests.has(serverSpanId)) {\n return;\n }\n let entry = this.stash.get(serverSpanId);\n if (!entry) {\n if (this.stash.size >= MAX_STASHED_REQUESTS) {\n const oldestKey = this.stash.keys().next().value;\n if (oldestKey !== undefined) {\n this.stash.delete(oldestKey);\n logDebug(\"Apitally payload stash cap reached, dropping the oldest entry\");\n }\n }\n entry = {};\n this.stash.set(serverSpanId, entry);\n }\n if (update.requestHeaders !== undefined) {\n entry.requestHeaders = update.requestHeaders;\n }\n if (update.requestBody !== undefined) {\n entry.requestBody = update.requestBody;\n }\n if (update.requestContentEncoding !== undefined) {\n entry.requestContentEncoding = update.requestContentEncoding;\n }\n if (update.responseHeaders !== undefined) {\n entry.responseHeaders = update.responseHeaders;\n }\n if (update.responseBody !== undefined) {\n entry.responseBody = update.responseBody;\n }\n if (update.responseContentEncoding !== undefined) {\n entry.responseContentEncoding = update.responseContentEncoding;\n }\n } catch (error) {\n logDebug(`Error updating the request stash: ${String(error)}`);\n }\n }\n\n // Kept requests remain resolvable after release so late logs retain their\n // request association.\n resolveServerSpanId(spanId: string): string | undefined {\n return this.requests.get(spanId)?.serverSpanId ?? this.keptSpanIds.get(spanId)?.serverSpanId;\n }\n\n isRequestInFlight(serverSpanId: string): boolean {\n return this.requests.has(serverSpanId);\n }\n\n // Exclusion answers \"never wanted\" and runs strictly before sampling; an\n // excluded request never invokes a user sampling callback.\n resolveRequestDropReasonBeforeSampling(attributes: Attributes): RequestDropReason | undefined {\n const transportDropReason = resolveTransportDropReason(attributes);\n if (transportDropReason !== undefined) {\n return transportDropReason;\n }\n const path = attributes[\"url.path\"] ?? attributes[\"http.target\"];\n if (typeof path === \"string\" && matchesAny(this.excludePathPatterns, path.split(\"?\")[0])) {\n return \"excluded\";\n }\n const userAgent = attributes[\"user_agent.original\"] ?? attributes[\"http.user_agent\"];\n if (typeof userAgent === \"string\" && matchesAny(EXCLUDE_USER_AGENT_PATTERNS, userAgent)) {\n return \"excluded\";\n }\n return undefined;\n }\n\n async forceFlush(): Promise<void> {\n await this.downstream.forceFlush();\n // OTel flushes queued batches but does not await already-running exports.\n await this.flushExporter?.();\n }\n\n // Shutdown releases requests with completed transport observation once and\n // discards incomplete requests, which cannot complete afterward.\n async shutdown(): Promise<void> {\n try {\n for (const entry of new Set(this.requests.values())) {\n if (!entry.released && entry.transportCompleted) {\n this.releaseRequest(entry, entry.endedServerSpan ?? entry.serverSpan);\n }\n }\n this.requests.clear();\n this.stash.clear();\n this.demotedSpanIds.clear();\n this.keptSpanIds.clear();\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n await this.downstream.shutdown();\n }\n\n private startRequest(span: Span, parentContext: Context): void {\n const spanId = span.spanContext().spanId;\n // The handle is filled for every local-root SERVER span, independent of the\n // keep decision, so runtime writes always reach the current request's span.\n const handle = parentContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n if (handle) {\n handle.span = span;\n }\n const record = parentContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined;\n if (record) {\n record.serverSpanId = spanId;\n }\n writeUrlAttributesFromFullUrl(span);\n const dropReason =\n this.resolveRequestDropReasonBeforeSampling(span.attributes) ??\n (this.isRequestSampledIn(span) ? undefined : \"sampled-out\");\n if (dropReason) {\n if (record) {\n record.dropReason = dropReason;\n }\n return;\n }\n this.requests.set(spanId, {\n serverSpanId: spanId,\n serverSpan: span,\n spanIds: new Set([spanId]),\n record,\n buffered: [],\n transportCompleted: false,\n released: false,\n });\n }\n\n private isRequestSampledIn(span: Span): boolean {\n const callback = this.config.sampleOnRequest;\n const rate = callback\n ? resolveCallbackSampleRate(callback, span, \"sampleOnRequest\")\n : undefined;\n const bound = rate === undefined ? this.sampleRateBound : boundForSampleRate(rate);\n return isTraceSampledIn(span.spanContext().traceId, bound);\n }\n\n // Abstention leaves the request-stage decision standing; it never re-tests the\n // static sample rate.\n private isResponseSampledIn(entry: RequestEntry): boolean {\n const callback = this.config.sampleOnResponse;\n if (!callback) {\n return true;\n }\n const serverSpan = entry.endedServerSpan ?? entry.serverSpan;\n let snapshot: ReadableSpan = serverSpan;\n if (entry.record && Object.keys(entry.record.attributes).length > 0) {\n // The callback sees transport-observed values even when they were learned\n // after the span ended (final route, response size).\n const copy = copySpan(serverSpan);\n copy.attributes = {\n ...serverSpan.attributes,\n ...entry.record.attributes,\n };\n snapshot = copy;\n }\n const rate = resolveCallbackSampleRate(callback, snapshot, \"sampleOnResponse\");\n if (rate === undefined) {\n return true;\n }\n return isTraceSampledIn(serverSpan.spanContext().traceId, boundForSampleRate(rate));\n }\n\n private dropRequestOnResponse(entry: RequestEntry, record: RequestRecord): void {\n // Removing every span id sends the dropped request's late telemetry to the\n // lookup-miss rule, so it is discarded locally.\n entry.released = true;\n entry.buffered = [];\n record.dropReason = \"sampled-out\";\n if (entry.record) {\n entry.record.dropReason = \"sampled-out\";\n }\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n }\n\n private releaseIfComplete(entry: RequestEntry): void {\n if (entry.released || !entry.transportCompleted || !entry.endedServerSpan) {\n return;\n }\n this.releaseRequest(entry, entry.endedServerSpan);\n }\n\n // Descendants, the SERVER span, and logs enter downstream processing once, in\n // that order. The exporter applies the request record and stash later.\n private releaseRequest(entry: RequestEntry, serverSpan: ReadableSpan): void {\n entry.released = true;\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.addKeptSpanId(spanId, {\n serverSpanId: entry.serverSpanId,\n demoted: this.demotedSpanIds.delete(spanId),\n });\n }\n for (const bufferedSpan of entry.buffered) {\n this.downstream.onEnd(bufferedSpan);\n }\n entry.buffered = [];\n const stash = this.stash.get(entry.serverSpanId);\n this.stash.delete(entry.serverSpanId);\n let exportSpan = serverSpan;\n if (entry.record || stash || !serverSpan.ended) {\n const copy = copySpan(serverSpan);\n if (!serverSpan.ended) {\n // A SERVER span released during shutdown uses shutdown as its end time.\n copy.endTime = hrTime();\n copy.duration = hrTimeDuration(copy.startTime, copy.endTime);\n copy.ended = true;\n }\n copy.apitallyData = { record: entry.record, stash };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n this.onRequestFinished?.(entry.serverSpanId, true);\n }\n\n private removeCompletedRequestSpanIds(entry: RequestEntry): void {\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.demotedSpanIds.delete(spanId);\n }\n }\n\n private addKeptSpanId(spanId: string, kept: KeptSpanEntry): void {\n if (this.keptSpanIds.size >= MAX_KEPT_SPAN_IDS) {\n const oldestId = this.keptSpanIds.keys().next().value;\n if (oldestId !== undefined) {\n this.keptSpanIds.delete(oldestId);\n }\n }\n this.keptSpanIds.set(spanId, kept);\n }\n}\n\nexport function copySpan(span: ReadableSpan): SpanCopy {\n const spanContext = span.spanContext();\n return {\n name: span.name,\n kind: span.kind,\n spanContext: () => spanContext,\n parentSpanContext: span.parentSpanContext,\n startTime: span.startTime,\n endTime: span.endTime,\n status: span.status,\n attributes: span.attributes,\n links: span.links,\n events: span.events,\n duration: span.duration,\n ended: span.ended,\n resource: span.resource,\n instrumentationScope: span.instrumentationScope,\n droppedAttributesCount: span.droppedAttributesCount,\n droppedEventsCount: span.droppedEventsCount,\n droppedLinksCount: span.droppedLinksCount,\n };\n}\n\nfunction resolveTransportDropReason(attributes: Attributes): RequestDropReason | undefined {\n const method = attributes[\"http.request.method\"] ?? attributes[\"http.method\"];\n if (method === \"OPTIONS\") {\n return \"method\";\n }\n const scheme = attributes[\"url.scheme\"] ?? attributes[\"http.scheme\"];\n return scheme === \"ws\" || scheme === \"wss\" ? \"scheme\" : undefined;\n}\n\n// Missing path, query, and target attributes are derived from the full URL\n// because exclusion, display, and redaction require them.\nfunction writeUrlAttributesFromFullUrl(span: Span): void {\n const attributes = span.attributes;\n if (attributes[\"url.path\"] !== undefined || attributes[\"http.target\"] !== undefined) {\n return;\n }\n const url = attributes[\"url.full\"] ?? attributes[\"http.url\"];\n if (typeof url !== \"string\") {\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n const query = parsed.search.replace(/^\\?/, \"\");\n span.setAttribute(\"url.path\", parsed.pathname);\n if (query) {\n span.setAttribute(\"url.query\", query);\n }\n span.setAttribute(\"http.target\", query ? `${parsed.pathname}?${query}` : parsed.pathname);\n}\n\nconst TRACE_ID_LOW_64_BITS_MASK = (1n << 64n) - 1n;\n\nfunction boundForSampleRate(rate: number): bigint {\n return BigInt(Math.round(rate * 2 ** 64));\n}\n\n// Low 64-bit ratio sampling is deterministic per trace. Comparing the same\n// value at both stages makes the lower rate decisive.\nfunction isTraceSampledIn(traceId: string, bound: bigint): boolean {\n return (BigInt(`0x${traceId}`) & TRACE_ID_LOW_64_BITS_MASK) < bound;\n}\n\n// A throwing or invalid-returning callback resolves to keep: a user bug must\n// never lose data without a warning. Undefined means the callback abstained.\nfunction resolveCallbackSampleRate(\n callback: SamplingCallback,\n span: ReadableSpan,\n optionName: string,\n): number | undefined {\n let result: unknown;\n try {\n result = callback(span);\n } catch {\n logWarning(`The Apitally ${optionName} callback threw an error, so the request was captured`);\n return 1;\n }\n if (result === undefined || result === null) {\n return undefined;\n }\n if (typeof result === \"boolean\") {\n return result ? 1 : 0;\n }\n if (typeof result === \"number\" && result >= 0 && result <= 1) {\n return result;\n }\n logWarning(\n `The Apitally ${optionName} callback returned an invalid value, so the request was captured. Sampling callbacks must synchronously return a number between 0 and 1, a boolean, or undefined.`,\n );\n return 1;\n}\n\nfunction isContribPerMessageSpan(span: Span): boolean {\n const scopeName = span.instrumentationScope?.name ?? \"\";\n return (\n span.kind === SpanKind.INTERNAL &&\n [\" http send\", \" http receive\", \" websocket send\", \" websocket receive\"].some((suffix) =>\n span.name.endsWith(suffix),\n ) &&\n (scopeName.startsWith(\"@opentelemetry/instrumentation\") ||\n scopeName.startsWith(\"opentelemetry.instrumentation.\"))\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { logDebug, logWarning } from "./logger.js";
2
2
  import { DEFAULT_EXCLUDE_PATHS, EXCLUDE_USER_AGENTS, compilePatterns, getConfig, matchesAny } from "./config.js";
3
3
  import { REQUEST_RECORD_KEY, SPAN_HANDLE_KEY } from "./context.js";
4
- import { writeConsumerAttributesFromContext } from "./consumer.js";
5
4
  import { SpanKind } from "@opentelemetry/api";
6
5
  import { hrTime, hrTimeDuration } from "@opentelemetry/core";
7
6
  //#region src/spanProcessor.ts
@@ -236,7 +235,6 @@ var SpanPipeline = class {
236
235
  if (handle) handle.span = span;
237
236
  const record = parentContext.getValue(REQUEST_RECORD_KEY);
238
237
  if (record) record.serverSpanId = spanId;
239
- writeConsumerAttributesFromContext(parentContext, span, record);
240
238
  writeUrlAttributesFromFullUrl(span);
241
239
  const dropReason = this.resolveRequestDropReasonBeforeSampling(span.attributes) ?? (this.isRequestSampledIn(span) ? void 0 : "sampled-out");
242
240
  if (dropReason) {
@@ -1 +1 @@
1
- {"version":3,"file":"spanProcessor.js","names":[],"sources":["../src/spanProcessor.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind } from \"@opentelemetry/api\";\nimport { hrTime, hrTimeDuration } from \"@opentelemetry/core\";\nimport type { ReadableSpan, Span, SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport {\n type ApitallyConfig,\n compilePatterns,\n DEFAULT_EXCLUDE_PATHS,\n EXCLUDE_USER_AGENTS,\n getConfig,\n matchesAny,\n type SamplingCallback,\n} from \"./config.js\";\nimport { writeConsumerAttributesFromContext } from \"./consumer.js\";\nimport {\n REQUEST_RECORD_KEY,\n type RequestDropReason,\n type RequestRecord,\n SPAN_HANDLE_KEY,\n type SpanHandle,\n} from \"./context.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\n\nconst MAX_BUFFERED_SPANS = 1_000;\nconst MAX_TRACKED_SPAN_IDS = MAX_BUFFERED_SPANS + 1;\nconst MAX_STASHED_REQUESTS = 2_048;\nconst MAX_KEPT_SPAN_IDS = 10_000;\nconst EXCLUDE_USER_AGENT_PATTERNS = compilePatterns(EXCLUDE_USER_AGENTS);\n\n// Transport-captured headers and bodies are attached only to Apitally's export\n// copy.\nexport interface RequestStash {\n requestHeaders?: Record<string, string | string[]>;\n requestBody?: Buffer;\n requestContentEncoding?: string;\n responseHeaders?: Record<string, string | string[]>;\n responseBody?: Buffer;\n responseContentEncoding?: string;\n}\n\nexport interface ApitallySpanData {\n record?: RequestRecord;\n stash?: RequestStash;\n demoteToInternal?: boolean;\n}\n\n// Mutable ReadableSpan-shaped copy on Apitally's private export path; the span\n// handed to user-attached processors is never mutated.\nexport type SpanCopy = {\n -readonly [Key in keyof ReadableSpan]: ReadableSpan[Key];\n} & { apitallyData?: ApitallySpanData };\n\n// The public processor delegates to the process-global pipeline. Construction\n// only declares that the application owns the tracer provider.\nexport class ApitallySpanProcessor implements SpanProcessor {\n constructor() {\n setApitallySpanProcessorDeclared();\n }\n\n onStart(span: Span, parentContext: Context): void {\n let pipeline = getActiveSpanPipeline();\n if (!pipeline && span.kind === SpanKind.SERVER) {\n getServerSpanActivationCallback()?.();\n pipeline = getActiveSpanPipeline();\n }\n pipeline?.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n getActiveSpanPipeline()?.onEnd(span);\n }\n\n // Flush failures resolve instead of rejecting into the user's provider.\n forceFlush(): Promise<void> {\n const pipeline = getActiveSpanPipeline();\n if (!pipeline) {\n return Promise.resolve();\n }\n return pipeline.forceFlush().catch((error: unknown) => {\n logDebug(`Error flushing spans: ${String(error)}`);\n });\n }\n\n // A user provider's shutdown only flushes released requests; SDK shutdown owns\n // teardown.\n shutdown(): Promise<void> {\n return this.forceFlush();\n }\n}\n\ntype ServerSpanActivationCallback = () => void;\n\n// Symbol.for keys let ESM and CJS builds share processor coordination.\nconst ACTIVE_SPAN_PIPELINE_KEY = Symbol.for(\"apitally.activeSpanPipeline\");\nconst APITALLY_SPAN_PROCESSOR_DECLARED_KEY = Symbol.for(\"apitally.spanProcessorDeclared\");\nconst SERVER_SPAN_ACTIVATION_CALLBACK_KEY = Symbol.for(\"apitally.serverSpanActivationCallback\");\n\nexport function setActiveSpanPipeline(pipeline: SpanPipeline | undefined): void {\n (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY] = pipeline;\n}\n\nexport function getActiveSpanPipeline(): SpanPipeline | undefined {\n return (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY];\n}\n\nexport function setServerSpanActivationCallback(callback: ServerSpanActivationCallback): void {\n const holder = globalThis as Record<symbol, ServerSpanActivationCallback | undefined>;\n holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY] ??= callback;\n}\n\nexport function isApitallySpanProcessorDeclared(): boolean {\n return (\n (globalThis as Record<symbol, boolean | undefined>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] ===\n true\n );\n}\n\nexport function resetSpanProcessorState(): void {\n const holder = globalThis as Record<symbol, unknown>;\n delete holder[ACTIVE_SPAN_PIPELINE_KEY];\n delete holder[APITALLY_SPAN_PROCESSOR_DECLARED_KEY];\n delete holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY];\n}\n\nfunction setApitallySpanProcessorDeclared(): void {\n (globalThis as Record<symbol, boolean>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] = true;\n}\n\nfunction getServerSpanActivationCallback(): ServerSpanActivationCallback | undefined {\n return (globalThis as Record<symbol, ServerSpanActivationCallback | undefined>)[\n SERVER_SPAN_ACTIVATION_CALLBACK_KEY\n ];\n}\n\ninterface RequestEntry {\n readonly serverSpanId: string;\n readonly serverSpan: Span;\n readonly spanIds: Set<string>;\n record?: RequestRecord;\n buffered: ReadableSpan[];\n endedServerSpan?: ReadableSpan;\n transportCompleted: boolean;\n released: boolean;\n}\n\ninterface KeptSpanEntry {\n readonly serverSpanId: string;\n // Carries the demotion decision across release for spans that end late.\n readonly demoted: boolean;\n}\n\n// Requests remain buffered until the SERVER span and transport complete, then\n// one keep/drop decision releases them once.\nexport class SpanPipeline implements SpanProcessor {\n // Callbacks are optional because metrics and log pipelines start at activation.\n metricsRecorder?: (record: RequestRecord) => void;\n onRequestFinished?: (serverSpanId: string, kept: boolean) => void;\n private readonly downstream: SpanProcessor;\n private readonly config: ApitallyConfig;\n private readonly sampleRateBound: bigint;\n private readonly excludePathPatterns: RegExp[];\n // Every span ID remains mapped to one request until completion so late logs\n // resolve. A miss means dropped, completed, or non-request telemetry.\n private readonly requests = new Map<string, RequestEntry>();\n private readonly stash = new Map<string, RequestStash>();\n private readonly demotedSpanIds = new Set<string>();\n // Kept span IDs remain resolvable after release for late spans and logs; the\n // oldest are evicted at the cap.\n private readonly keptSpanIds = new Map<string, KeptSpanEntry>();\n\n constructor(\n downstream: SpanProcessor,\n private readonly flushExporter?: () => Promise<void>,\n ) {\n this.downstream = downstream;\n this.config = getConfig();\n this.sampleRateBound = boundForSampleRate(this.config.sampleRate);\n this.excludePathPatterns = compilePatterns(DEFAULT_EXCLUDE_PATHS, this.config.excludePaths);\n }\n\n onStart(span: Span, parentContext: Context): void {\n try {\n if (isContribPerMessageSpan(span)) {\n return;\n }\n const parent = span.parentSpanContext;\n if (!parent || parent.isRemote) {\n if (span.kind === SpanKind.SERVER) {\n this.startRequest(span, parentContext);\n }\n return;\n }\n const entry = this.requests.get(parent.spanId);\n const spanId = span.spanContext().spanId;\n if (!entry) {\n // A span started under a request already released as kept exports at\n // its end, like the released request's other late telemetry.\n const kept = this.keptSpanIds.get(parent.spanId);\n if (kept) {\n this.addKeptSpanId(spanId, {\n serverSpanId: kept.serverSpanId,\n demoted: false,\n });\n }\n return;\n }\n if (entry.spanIds.size >= MAX_TRACKED_SPAN_IDS) {\n logDebug(\"Apitally span tracking cap reached, dropping the span\");\n return;\n }\n if (span.kind === SpanKind.SERVER) {\n // A second middleware produced a SERVER span for this request. Apitally\n // demotes its copy, but user exporters retain the duplicate.\n this.demotedSpanIds.add(spanId);\n logWarning(\n `Detected a duplicate SERVER span produced by the instrumentation scope \"${span.instrumentationScope?.name ?? \"unknown\"}\" inside an active request. Apitally exports it as an INTERNAL span, but your own OpenTelemetry exporters still receive the duplicate. To resolve this, remove the middleware that produces it.`,\n );\n }\n this.requests.set(spanId, entry);\n entry.spanIds.add(spanId);\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n try {\n const spanId = span.spanContext().spanId;\n const isDemoted = this.demotedSpanIds.delete(spanId);\n const entry = this.requests.get(spanId);\n if (!entry) {\n const kept = this.keptSpanIds.get(spanId);\n if (kept) {\n let exportSpan = span;\n if (kept.demoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n }\n return;\n }\n if (spanId === entry.serverSpanId) {\n if (!entry.record) {\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n return;\n }\n entry.endedServerSpan = span;\n this.releaseIfComplete(entry);\n return;\n }\n // The span id stays mapped so log records emitted after this span ended\n // still resolve to the request until it completes.\n let exportSpan = span;\n if (isDemoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n if (entry.buffered.length < MAX_BUFFERED_SPANS) {\n entry.buffered.push(exportSpan);\n } else {\n logDebug(\"Apitally span buffer cap reached, dropping the span\");\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n attachRequestRecord(serverSpanId: string, record: RequestRecord): void {\n const entry = this.requests.get(serverSpanId);\n if (entry) {\n entry.record ??= record;\n }\n }\n\n // Transport completion triggers response sampling and metrics independently\n // of span-end timing. Map misses still record metrics and discard other data.\n handleTransportCompletion(record: RequestRecord): void {\n try {\n record.dropReason = resolveTransportDropReason(record.attributes) ?? record.dropReason;\n const entry =\n record.serverSpanId !== undefined ? this.requests.get(record.serverSpanId) : undefined;\n if (entry && !entry.transportCompleted && !entry.released) {\n entry.record ??= record;\n entry.transportCompleted = true;\n if (!this.isResponseSampledIn(entry)) {\n this.dropRequestOnResponse(entry, record);\n }\n }\n this.metricsRecorder?.(record);\n if (entry) {\n this.releaseIfComplete(entry);\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n // Holds captured headers and bodies until the exporter attaches them to the\n // exported SERVER span copy. Fields already stashed are kept unless replaced.\n updateStash(serverSpanId: string, update: RequestStash): void {\n try {\n // A request without an in-flight map entry can never release, so its\n // stash entry would sit unconsumed until the cap evicts it.\n if (!this.requests.has(serverSpanId)) {\n return;\n }\n let entry = this.stash.get(serverSpanId);\n if (!entry) {\n if (this.stash.size >= MAX_STASHED_REQUESTS) {\n const oldestKey = this.stash.keys().next().value;\n if (oldestKey !== undefined) {\n this.stash.delete(oldestKey);\n logDebug(\"Apitally payload stash cap reached, dropping the oldest entry\");\n }\n }\n entry = {};\n this.stash.set(serverSpanId, entry);\n }\n if (update.requestHeaders !== undefined) {\n entry.requestHeaders = update.requestHeaders;\n }\n if (update.requestBody !== undefined) {\n entry.requestBody = update.requestBody;\n }\n if (update.requestContentEncoding !== undefined) {\n entry.requestContentEncoding = update.requestContentEncoding;\n }\n if (update.responseHeaders !== undefined) {\n entry.responseHeaders = update.responseHeaders;\n }\n if (update.responseBody !== undefined) {\n entry.responseBody = update.responseBody;\n }\n if (update.responseContentEncoding !== undefined) {\n entry.responseContentEncoding = update.responseContentEncoding;\n }\n } catch (error) {\n logDebug(`Error updating the request stash: ${String(error)}`);\n }\n }\n\n // Kept requests remain resolvable after release so late logs retain their\n // request association.\n resolveServerSpanId(spanId: string): string | undefined {\n return this.requests.get(spanId)?.serverSpanId ?? this.keptSpanIds.get(spanId)?.serverSpanId;\n }\n\n isRequestInFlight(serverSpanId: string): boolean {\n return this.requests.has(serverSpanId);\n }\n\n // Exclusion answers \"never wanted\" and runs strictly before sampling; an\n // excluded request never invokes a user sampling callback.\n resolveRequestDropReasonBeforeSampling(attributes: Attributes): RequestDropReason | undefined {\n const transportDropReason = resolveTransportDropReason(attributes);\n if (transportDropReason !== undefined) {\n return transportDropReason;\n }\n const path = attributes[\"url.path\"] ?? attributes[\"http.target\"];\n if (typeof path === \"string\" && matchesAny(this.excludePathPatterns, path.split(\"?\")[0])) {\n return \"excluded\";\n }\n const userAgent = attributes[\"user_agent.original\"] ?? attributes[\"http.user_agent\"];\n if (typeof userAgent === \"string\" && matchesAny(EXCLUDE_USER_AGENT_PATTERNS, userAgent)) {\n return \"excluded\";\n }\n return undefined;\n }\n\n async forceFlush(): Promise<void> {\n await this.downstream.forceFlush();\n // OTel flushes queued batches but does not await already-running exports.\n await this.flushExporter?.();\n }\n\n // Shutdown releases requests with completed transport observation once and\n // discards incomplete requests, which cannot complete afterward.\n async shutdown(): Promise<void> {\n try {\n for (const entry of new Set(this.requests.values())) {\n if (!entry.released && entry.transportCompleted) {\n this.releaseRequest(entry, entry.endedServerSpan ?? entry.serverSpan);\n }\n }\n this.requests.clear();\n this.stash.clear();\n this.demotedSpanIds.clear();\n this.keptSpanIds.clear();\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n await this.downstream.shutdown();\n }\n\n private startRequest(span: Span, parentContext: Context): void {\n const spanId = span.spanContext().spanId;\n // The handle is filled for every local-root SERVER span, independent of the\n // keep decision, so runtime writes always reach the current request's span.\n const handle = parentContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n if (handle) {\n handle.span = span;\n }\n const record = parentContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined;\n if (record) {\n record.serverSpanId = spanId;\n }\n writeConsumerAttributesFromContext(parentContext, span, record);\n writeUrlAttributesFromFullUrl(span);\n const dropReason =\n this.resolveRequestDropReasonBeforeSampling(span.attributes) ??\n (this.isRequestSampledIn(span) ? undefined : \"sampled-out\");\n if (dropReason) {\n if (record) {\n record.dropReason = dropReason;\n }\n return;\n }\n this.requests.set(spanId, {\n serverSpanId: spanId,\n serverSpan: span,\n spanIds: new Set([spanId]),\n record,\n buffered: [],\n transportCompleted: false,\n released: false,\n });\n }\n\n private isRequestSampledIn(span: Span): boolean {\n const callback = this.config.sampleOnRequest;\n const rate = callback\n ? resolveCallbackSampleRate(callback, span, \"sampleOnRequest\")\n : undefined;\n const bound = rate === undefined ? this.sampleRateBound : boundForSampleRate(rate);\n return isTraceSampledIn(span.spanContext().traceId, bound);\n }\n\n // Abstention leaves the request-stage decision standing; it never re-tests the\n // static sample rate.\n private isResponseSampledIn(entry: RequestEntry): boolean {\n const callback = this.config.sampleOnResponse;\n if (!callback) {\n return true;\n }\n const serverSpan = entry.endedServerSpan ?? entry.serverSpan;\n let snapshot: ReadableSpan = serverSpan;\n if (entry.record && Object.keys(entry.record.attributes).length > 0) {\n // The callback sees transport-observed values even when they were learned\n // after the span ended (final route, response size).\n const copy = copySpan(serverSpan);\n copy.attributes = {\n ...serverSpan.attributes,\n ...entry.record.attributes,\n };\n snapshot = copy;\n }\n const rate = resolveCallbackSampleRate(callback, snapshot, \"sampleOnResponse\");\n if (rate === undefined) {\n return true;\n }\n return isTraceSampledIn(serverSpan.spanContext().traceId, boundForSampleRate(rate));\n }\n\n private dropRequestOnResponse(entry: RequestEntry, record: RequestRecord): void {\n // Removing every span id sends the dropped request's late telemetry to the\n // lookup-miss rule, so it is discarded locally.\n entry.released = true;\n entry.buffered = [];\n record.dropReason = \"sampled-out\";\n if (entry.record) {\n entry.record.dropReason = \"sampled-out\";\n }\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n }\n\n private releaseIfComplete(entry: RequestEntry): void {\n if (entry.released || !entry.transportCompleted || !entry.endedServerSpan) {\n return;\n }\n this.releaseRequest(entry, entry.endedServerSpan);\n }\n\n // Descendants, the SERVER span, and logs enter downstream processing once, in\n // that order. The exporter applies the request record and stash later.\n private releaseRequest(entry: RequestEntry, serverSpan: ReadableSpan): void {\n entry.released = true;\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.addKeptSpanId(spanId, {\n serverSpanId: entry.serverSpanId,\n demoted: this.demotedSpanIds.delete(spanId),\n });\n }\n for (const bufferedSpan of entry.buffered) {\n this.downstream.onEnd(bufferedSpan);\n }\n entry.buffered = [];\n const stash = this.stash.get(entry.serverSpanId);\n this.stash.delete(entry.serverSpanId);\n let exportSpan = serverSpan;\n if (entry.record || stash || !serverSpan.ended) {\n const copy = copySpan(serverSpan);\n if (!serverSpan.ended) {\n // A SERVER span released during shutdown uses shutdown as its end time.\n copy.endTime = hrTime();\n copy.duration = hrTimeDuration(copy.startTime, copy.endTime);\n copy.ended = true;\n }\n copy.apitallyData = { record: entry.record, stash };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n this.onRequestFinished?.(entry.serverSpanId, true);\n }\n\n private removeCompletedRequestSpanIds(entry: RequestEntry): void {\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.demotedSpanIds.delete(spanId);\n }\n }\n\n private addKeptSpanId(spanId: string, kept: KeptSpanEntry): void {\n if (this.keptSpanIds.size >= MAX_KEPT_SPAN_IDS) {\n const oldestId = this.keptSpanIds.keys().next().value;\n if (oldestId !== undefined) {\n this.keptSpanIds.delete(oldestId);\n }\n }\n this.keptSpanIds.set(spanId, kept);\n }\n}\n\nexport function copySpan(span: ReadableSpan): SpanCopy {\n const spanContext = span.spanContext();\n return {\n name: span.name,\n kind: span.kind,\n spanContext: () => spanContext,\n parentSpanContext: span.parentSpanContext,\n startTime: span.startTime,\n endTime: span.endTime,\n status: span.status,\n attributes: span.attributes,\n links: span.links,\n events: span.events,\n duration: span.duration,\n ended: span.ended,\n resource: span.resource,\n instrumentationScope: span.instrumentationScope,\n droppedAttributesCount: span.droppedAttributesCount,\n droppedEventsCount: span.droppedEventsCount,\n droppedLinksCount: span.droppedLinksCount,\n };\n}\n\nfunction resolveTransportDropReason(attributes: Attributes): RequestDropReason | undefined {\n const method = attributes[\"http.request.method\"] ?? attributes[\"http.method\"];\n if (method === \"OPTIONS\") {\n return \"method\";\n }\n const scheme = attributes[\"url.scheme\"] ?? attributes[\"http.scheme\"];\n return scheme === \"ws\" || scheme === \"wss\" ? \"scheme\" : undefined;\n}\n\n// Missing path, query, and target attributes are derived from the full URL\n// because exclusion, display, and redaction require them.\nfunction writeUrlAttributesFromFullUrl(span: Span): void {\n const attributes = span.attributes;\n if (attributes[\"url.path\"] !== undefined || attributes[\"http.target\"] !== undefined) {\n return;\n }\n const url = attributes[\"url.full\"] ?? attributes[\"http.url\"];\n if (typeof url !== \"string\") {\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n const query = parsed.search.replace(/^\\?/, \"\");\n span.setAttribute(\"url.path\", parsed.pathname);\n if (query) {\n span.setAttribute(\"url.query\", query);\n }\n span.setAttribute(\"http.target\", query ? `${parsed.pathname}?${query}` : parsed.pathname);\n}\n\nconst TRACE_ID_LOW_64_BITS_MASK = (1n << 64n) - 1n;\n\nfunction boundForSampleRate(rate: number): bigint {\n return BigInt(Math.round(rate * 2 ** 64));\n}\n\n// Low 64-bit ratio sampling is deterministic per trace. Comparing the same\n// value at both stages makes the lower rate decisive.\nfunction isTraceSampledIn(traceId: string, bound: bigint): boolean {\n return (BigInt(`0x${traceId}`) & TRACE_ID_LOW_64_BITS_MASK) < bound;\n}\n\n// A throwing or invalid-returning callback resolves to keep: a user bug must\n// never lose data without a warning. Undefined means the callback abstained.\nfunction resolveCallbackSampleRate(\n callback: SamplingCallback,\n span: ReadableSpan,\n optionName: string,\n): number | undefined {\n let result: unknown;\n try {\n result = callback(span);\n } catch {\n logWarning(`The Apitally ${optionName} callback threw an error, so the request was captured`);\n return 1;\n }\n if (result === undefined || result === null) {\n return undefined;\n }\n if (typeof result === \"boolean\") {\n return result ? 1 : 0;\n }\n if (typeof result === \"number\" && result >= 0 && result <= 1) {\n return result;\n }\n logWarning(\n `The Apitally ${optionName} callback returned an invalid value, so the request was captured. Sampling callbacks must synchronously return a number between 0 and 1, a boolean, or undefined.`,\n );\n return 1;\n}\n\nfunction isContribPerMessageSpan(span: Span): boolean {\n const scopeName = span.instrumentationScope?.name ?? \"\";\n return (\n span.kind === SpanKind.INTERNAL &&\n [\" http send\", \" http receive\", \" websocket send\", \" websocket receive\"].some((suffix) =>\n span.name.endsWith(suffix),\n ) &&\n (scopeName.startsWith(\"@opentelemetry/instrumentation\") ||\n scopeName.startsWith(\"opentelemetry.instrumentation.\"))\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"spanProcessor.js","names":[],"sources":["../src/spanProcessor.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind } from \"@opentelemetry/api\";\nimport { hrTime, hrTimeDuration } from \"@opentelemetry/core\";\nimport type { ReadableSpan, Span, SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport {\n type ApitallyConfig,\n compilePatterns,\n DEFAULT_EXCLUDE_PATHS,\n EXCLUDE_USER_AGENTS,\n getConfig,\n matchesAny,\n type SamplingCallback,\n} from \"./config.js\";\nimport {\n REQUEST_RECORD_KEY,\n type RequestDropReason,\n type RequestRecord,\n SPAN_HANDLE_KEY,\n type SpanHandle,\n} from \"./context.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\n\nconst MAX_BUFFERED_SPANS = 1_000;\nconst MAX_TRACKED_SPAN_IDS = MAX_BUFFERED_SPANS + 1;\nconst MAX_STASHED_REQUESTS = 2_048;\nconst MAX_KEPT_SPAN_IDS = 10_000;\nconst EXCLUDE_USER_AGENT_PATTERNS = compilePatterns(EXCLUDE_USER_AGENTS);\n\n// Transport-captured headers and bodies are attached only to Apitally's export\n// copy.\nexport interface RequestStash {\n requestHeaders?: Record<string, string | string[]>;\n requestBody?: Buffer;\n requestContentEncoding?: string;\n responseHeaders?: Record<string, string | string[]>;\n responseBody?: Buffer;\n responseContentEncoding?: string;\n}\n\nexport interface ApitallySpanData {\n record?: RequestRecord;\n stash?: RequestStash;\n demoteToInternal?: boolean;\n}\n\n// Mutable ReadableSpan-shaped copy on Apitally's private export path; the span\n// handed to user-attached processors is never mutated.\nexport type SpanCopy = {\n -readonly [Key in keyof ReadableSpan]: ReadableSpan[Key];\n} & { apitallyData?: ApitallySpanData };\n\n// The public processor delegates to the process-global pipeline. Construction\n// only declares that the application owns the tracer provider.\nexport class ApitallySpanProcessor implements SpanProcessor {\n constructor() {\n setApitallySpanProcessorDeclared();\n }\n\n onStart(span: Span, parentContext: Context): void {\n let pipeline = getActiveSpanPipeline();\n if (!pipeline && span.kind === SpanKind.SERVER) {\n getServerSpanActivationCallback()?.();\n pipeline = getActiveSpanPipeline();\n }\n pipeline?.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n getActiveSpanPipeline()?.onEnd(span);\n }\n\n // Flush failures resolve instead of rejecting into the user's provider.\n forceFlush(): Promise<void> {\n const pipeline = getActiveSpanPipeline();\n if (!pipeline) {\n return Promise.resolve();\n }\n return pipeline.forceFlush().catch((error: unknown) => {\n logDebug(`Error flushing spans: ${String(error)}`);\n });\n }\n\n // A user provider's shutdown only flushes released requests; SDK shutdown owns\n // teardown.\n shutdown(): Promise<void> {\n return this.forceFlush();\n }\n}\n\ntype ServerSpanActivationCallback = () => void;\n\n// Symbol.for keys let ESM and CJS builds share processor coordination.\nconst ACTIVE_SPAN_PIPELINE_KEY = Symbol.for(\"apitally.activeSpanPipeline\");\nconst APITALLY_SPAN_PROCESSOR_DECLARED_KEY = Symbol.for(\"apitally.spanProcessorDeclared\");\nconst SERVER_SPAN_ACTIVATION_CALLBACK_KEY = Symbol.for(\"apitally.serverSpanActivationCallback\");\n\nexport function setActiveSpanPipeline(pipeline: SpanPipeline | undefined): void {\n (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY] = pipeline;\n}\n\nexport function getActiveSpanPipeline(): SpanPipeline | undefined {\n return (globalThis as Record<symbol, SpanPipeline | undefined>)[ACTIVE_SPAN_PIPELINE_KEY];\n}\n\nexport function setServerSpanActivationCallback(callback: ServerSpanActivationCallback): void {\n const holder = globalThis as Record<symbol, ServerSpanActivationCallback | undefined>;\n holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY] ??= callback;\n}\n\nexport function isApitallySpanProcessorDeclared(): boolean {\n return (\n (globalThis as Record<symbol, boolean | undefined>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] ===\n true\n );\n}\n\nexport function resetSpanProcessorState(): void {\n const holder = globalThis as Record<symbol, unknown>;\n delete holder[ACTIVE_SPAN_PIPELINE_KEY];\n delete holder[APITALLY_SPAN_PROCESSOR_DECLARED_KEY];\n delete holder[SERVER_SPAN_ACTIVATION_CALLBACK_KEY];\n}\n\nfunction setApitallySpanProcessorDeclared(): void {\n (globalThis as Record<symbol, boolean>)[APITALLY_SPAN_PROCESSOR_DECLARED_KEY] = true;\n}\n\nfunction getServerSpanActivationCallback(): ServerSpanActivationCallback | undefined {\n return (globalThis as Record<symbol, ServerSpanActivationCallback | undefined>)[\n SERVER_SPAN_ACTIVATION_CALLBACK_KEY\n ];\n}\n\ninterface RequestEntry {\n readonly serverSpanId: string;\n readonly serverSpan: Span;\n readonly spanIds: Set<string>;\n record?: RequestRecord;\n buffered: ReadableSpan[];\n endedServerSpan?: ReadableSpan;\n transportCompleted: boolean;\n released: boolean;\n}\n\ninterface KeptSpanEntry {\n readonly serverSpanId: string;\n // Carries the demotion decision across release for spans that end late.\n readonly demoted: boolean;\n}\n\n// Requests remain buffered until the SERVER span and transport complete, then\n// one keep/drop decision releases them once.\nexport class SpanPipeline implements SpanProcessor {\n // Callbacks are optional because metrics and log pipelines start at activation.\n metricsRecorder?: (record: RequestRecord) => void;\n onRequestFinished?: (serverSpanId: string, kept: boolean) => void;\n private readonly downstream: SpanProcessor;\n private readonly config: ApitallyConfig;\n private readonly sampleRateBound: bigint;\n private readonly excludePathPatterns: RegExp[];\n // Every span ID remains mapped to one request until completion so late logs\n // resolve. A miss means dropped, completed, or non-request telemetry.\n private readonly requests = new Map<string, RequestEntry>();\n private readonly stash = new Map<string, RequestStash>();\n private readonly demotedSpanIds = new Set<string>();\n // Kept span IDs remain resolvable after release for late spans and logs; the\n // oldest are evicted at the cap.\n private readonly keptSpanIds = new Map<string, KeptSpanEntry>();\n\n constructor(\n downstream: SpanProcessor,\n private readonly flushExporter?: () => Promise<void>,\n ) {\n this.downstream = downstream;\n this.config = getConfig();\n this.sampleRateBound = boundForSampleRate(this.config.sampleRate);\n this.excludePathPatterns = compilePatterns(DEFAULT_EXCLUDE_PATHS, this.config.excludePaths);\n }\n\n onStart(span: Span, parentContext: Context): void {\n try {\n if (isContribPerMessageSpan(span)) {\n return;\n }\n const parent = span.parentSpanContext;\n if (!parent || parent.isRemote) {\n if (span.kind === SpanKind.SERVER) {\n this.startRequest(span, parentContext);\n }\n return;\n }\n const entry = this.requests.get(parent.spanId);\n const spanId = span.spanContext().spanId;\n if (!entry) {\n // A span started under a request already released as kept exports at\n // its end, like the released request's other late telemetry.\n const kept = this.keptSpanIds.get(parent.spanId);\n if (kept) {\n this.addKeptSpanId(spanId, {\n serverSpanId: kept.serverSpanId,\n demoted: false,\n });\n }\n return;\n }\n if (entry.spanIds.size >= MAX_TRACKED_SPAN_IDS) {\n logDebug(\"Apitally span tracking cap reached, dropping the span\");\n return;\n }\n if (span.kind === SpanKind.SERVER) {\n // A second middleware produced a SERVER span for this request. Apitally\n // demotes its copy, but user exporters retain the duplicate.\n this.demotedSpanIds.add(spanId);\n logWarning(\n `Detected a duplicate SERVER span produced by the instrumentation scope \"${span.instrumentationScope?.name ?? \"unknown\"}\" inside an active request. Apitally exports it as an INTERNAL span, but your own OpenTelemetry exporters still receive the duplicate. To resolve this, remove the middleware that produces it.`,\n );\n }\n this.requests.set(spanId, entry);\n entry.spanIds.add(spanId);\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n try {\n const spanId = span.spanContext().spanId;\n const isDemoted = this.demotedSpanIds.delete(spanId);\n const entry = this.requests.get(spanId);\n if (!entry) {\n const kept = this.keptSpanIds.get(spanId);\n if (kept) {\n let exportSpan = span;\n if (kept.demoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n }\n return;\n }\n if (spanId === entry.serverSpanId) {\n if (!entry.record) {\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n return;\n }\n entry.endedServerSpan = span;\n this.releaseIfComplete(entry);\n return;\n }\n // The span id stays mapped so log records emitted after this span ended\n // still resolve to the request until it completes.\n let exportSpan = span;\n if (isDemoted) {\n const copy = copySpan(span);\n copy.apitallyData = { demoteToInternal: true };\n exportSpan = copy;\n }\n if (entry.buffered.length < MAX_BUFFERED_SPANS) {\n entry.buffered.push(exportSpan);\n } else {\n logDebug(\"Apitally span buffer cap reached, dropping the span\");\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n attachRequestRecord(serverSpanId: string, record: RequestRecord): void {\n const entry = this.requests.get(serverSpanId);\n if (entry) {\n entry.record ??= record;\n }\n }\n\n // Transport completion triggers response sampling and metrics independently\n // of span-end timing. Map misses still record metrics and discard other data.\n handleTransportCompletion(record: RequestRecord): void {\n try {\n record.dropReason = resolveTransportDropReason(record.attributes) ?? record.dropReason;\n const entry =\n record.serverSpanId !== undefined ? this.requests.get(record.serverSpanId) : undefined;\n if (entry && !entry.transportCompleted && !entry.released) {\n entry.record ??= record;\n entry.transportCompleted = true;\n if (!this.isResponseSampledIn(entry)) {\n this.dropRequestOnResponse(entry, record);\n }\n }\n this.metricsRecorder?.(record);\n if (entry) {\n this.releaseIfComplete(entry);\n }\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n }\n\n // Holds captured headers and bodies until the exporter attaches them to the\n // exported SERVER span copy. Fields already stashed are kept unless replaced.\n updateStash(serverSpanId: string, update: RequestStash): void {\n try {\n // A request without an in-flight map entry can never release, so its\n // stash entry would sit unconsumed until the cap evicts it.\n if (!this.requests.has(serverSpanId)) {\n return;\n }\n let entry = this.stash.get(serverSpanId);\n if (!entry) {\n if (this.stash.size >= MAX_STASHED_REQUESTS) {\n const oldestKey = this.stash.keys().next().value;\n if (oldestKey !== undefined) {\n this.stash.delete(oldestKey);\n logDebug(\"Apitally payload stash cap reached, dropping the oldest entry\");\n }\n }\n entry = {};\n this.stash.set(serverSpanId, entry);\n }\n if (update.requestHeaders !== undefined) {\n entry.requestHeaders = update.requestHeaders;\n }\n if (update.requestBody !== undefined) {\n entry.requestBody = update.requestBody;\n }\n if (update.requestContentEncoding !== undefined) {\n entry.requestContentEncoding = update.requestContentEncoding;\n }\n if (update.responseHeaders !== undefined) {\n entry.responseHeaders = update.responseHeaders;\n }\n if (update.responseBody !== undefined) {\n entry.responseBody = update.responseBody;\n }\n if (update.responseContentEncoding !== undefined) {\n entry.responseContentEncoding = update.responseContentEncoding;\n }\n } catch (error) {\n logDebug(`Error updating the request stash: ${String(error)}`);\n }\n }\n\n // Kept requests remain resolvable after release so late logs retain their\n // request association.\n resolveServerSpanId(spanId: string): string | undefined {\n return this.requests.get(spanId)?.serverSpanId ?? this.keptSpanIds.get(spanId)?.serverSpanId;\n }\n\n isRequestInFlight(serverSpanId: string): boolean {\n return this.requests.has(serverSpanId);\n }\n\n // Exclusion answers \"never wanted\" and runs strictly before sampling; an\n // excluded request never invokes a user sampling callback.\n resolveRequestDropReasonBeforeSampling(attributes: Attributes): RequestDropReason | undefined {\n const transportDropReason = resolveTransportDropReason(attributes);\n if (transportDropReason !== undefined) {\n return transportDropReason;\n }\n const path = attributes[\"url.path\"] ?? attributes[\"http.target\"];\n if (typeof path === \"string\" && matchesAny(this.excludePathPatterns, path.split(\"?\")[0])) {\n return \"excluded\";\n }\n const userAgent = attributes[\"user_agent.original\"] ?? attributes[\"http.user_agent\"];\n if (typeof userAgent === \"string\" && matchesAny(EXCLUDE_USER_AGENT_PATTERNS, userAgent)) {\n return \"excluded\";\n }\n return undefined;\n }\n\n async forceFlush(): Promise<void> {\n await this.downstream.forceFlush();\n // OTel flushes queued batches but does not await already-running exports.\n await this.flushExporter?.();\n }\n\n // Shutdown releases requests with completed transport observation once and\n // discards incomplete requests, which cannot complete afterward.\n async shutdown(): Promise<void> {\n try {\n for (const entry of new Set(this.requests.values())) {\n if (!entry.released && entry.transportCompleted) {\n this.releaseRequest(entry, entry.endedServerSpan ?? entry.serverSpan);\n }\n }\n this.requests.clear();\n this.stash.clear();\n this.demotedSpanIds.clear();\n this.keptSpanIds.clear();\n } catch (error) {\n logWarning(`Error in the Apitally span processor: ${String(error)}`);\n }\n await this.downstream.shutdown();\n }\n\n private startRequest(span: Span, parentContext: Context): void {\n const spanId = span.spanContext().spanId;\n // The handle is filled for every local-root SERVER span, independent of the\n // keep decision, so runtime writes always reach the current request's span.\n const handle = parentContext.getValue(SPAN_HANDLE_KEY) as SpanHandle | undefined;\n if (handle) {\n handle.span = span;\n }\n const record = parentContext.getValue(REQUEST_RECORD_KEY) as RequestRecord | undefined;\n if (record) {\n record.serverSpanId = spanId;\n }\n writeUrlAttributesFromFullUrl(span);\n const dropReason =\n this.resolveRequestDropReasonBeforeSampling(span.attributes) ??\n (this.isRequestSampledIn(span) ? undefined : \"sampled-out\");\n if (dropReason) {\n if (record) {\n record.dropReason = dropReason;\n }\n return;\n }\n this.requests.set(spanId, {\n serverSpanId: spanId,\n serverSpan: span,\n spanIds: new Set([spanId]),\n record,\n buffered: [],\n transportCompleted: false,\n released: false,\n });\n }\n\n private isRequestSampledIn(span: Span): boolean {\n const callback = this.config.sampleOnRequest;\n const rate = callback\n ? resolveCallbackSampleRate(callback, span, \"sampleOnRequest\")\n : undefined;\n const bound = rate === undefined ? this.sampleRateBound : boundForSampleRate(rate);\n return isTraceSampledIn(span.spanContext().traceId, bound);\n }\n\n // Abstention leaves the request-stage decision standing; it never re-tests the\n // static sample rate.\n private isResponseSampledIn(entry: RequestEntry): boolean {\n const callback = this.config.sampleOnResponse;\n if (!callback) {\n return true;\n }\n const serverSpan = entry.endedServerSpan ?? entry.serverSpan;\n let snapshot: ReadableSpan = serverSpan;\n if (entry.record && Object.keys(entry.record.attributes).length > 0) {\n // The callback sees transport-observed values even when they were learned\n // after the span ended (final route, response size).\n const copy = copySpan(serverSpan);\n copy.attributes = {\n ...serverSpan.attributes,\n ...entry.record.attributes,\n };\n snapshot = copy;\n }\n const rate = resolveCallbackSampleRate(callback, snapshot, \"sampleOnResponse\");\n if (rate === undefined) {\n return true;\n }\n return isTraceSampledIn(serverSpan.spanContext().traceId, boundForSampleRate(rate));\n }\n\n private dropRequestOnResponse(entry: RequestEntry, record: RequestRecord): void {\n // Removing every span id sends the dropped request's late telemetry to the\n // lookup-miss rule, so it is discarded locally.\n entry.released = true;\n entry.buffered = [];\n record.dropReason = \"sampled-out\";\n if (entry.record) {\n entry.record.dropReason = \"sampled-out\";\n }\n this.removeCompletedRequestSpanIds(entry);\n this.stash.delete(entry.serverSpanId);\n this.onRequestFinished?.(entry.serverSpanId, false);\n }\n\n private releaseIfComplete(entry: RequestEntry): void {\n if (entry.released || !entry.transportCompleted || !entry.endedServerSpan) {\n return;\n }\n this.releaseRequest(entry, entry.endedServerSpan);\n }\n\n // Descendants, the SERVER span, and logs enter downstream processing once, in\n // that order. The exporter applies the request record and stash later.\n private releaseRequest(entry: RequestEntry, serverSpan: ReadableSpan): void {\n entry.released = true;\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.addKeptSpanId(spanId, {\n serverSpanId: entry.serverSpanId,\n demoted: this.demotedSpanIds.delete(spanId),\n });\n }\n for (const bufferedSpan of entry.buffered) {\n this.downstream.onEnd(bufferedSpan);\n }\n entry.buffered = [];\n const stash = this.stash.get(entry.serverSpanId);\n this.stash.delete(entry.serverSpanId);\n let exportSpan = serverSpan;\n if (entry.record || stash || !serverSpan.ended) {\n const copy = copySpan(serverSpan);\n if (!serverSpan.ended) {\n // A SERVER span released during shutdown uses shutdown as its end time.\n copy.endTime = hrTime();\n copy.duration = hrTimeDuration(copy.startTime, copy.endTime);\n copy.ended = true;\n }\n copy.apitallyData = { record: entry.record, stash };\n exportSpan = copy;\n }\n this.downstream.onEnd(exportSpan);\n this.onRequestFinished?.(entry.serverSpanId, true);\n }\n\n private removeCompletedRequestSpanIds(entry: RequestEntry): void {\n for (const spanId of entry.spanIds) {\n this.requests.delete(spanId);\n this.demotedSpanIds.delete(spanId);\n }\n }\n\n private addKeptSpanId(spanId: string, kept: KeptSpanEntry): void {\n if (this.keptSpanIds.size >= MAX_KEPT_SPAN_IDS) {\n const oldestId = this.keptSpanIds.keys().next().value;\n if (oldestId !== undefined) {\n this.keptSpanIds.delete(oldestId);\n }\n }\n this.keptSpanIds.set(spanId, kept);\n }\n}\n\nexport function copySpan(span: ReadableSpan): SpanCopy {\n const spanContext = span.spanContext();\n return {\n name: span.name,\n kind: span.kind,\n spanContext: () => spanContext,\n parentSpanContext: span.parentSpanContext,\n startTime: span.startTime,\n endTime: span.endTime,\n status: span.status,\n attributes: span.attributes,\n links: span.links,\n events: span.events,\n duration: span.duration,\n ended: span.ended,\n resource: span.resource,\n instrumentationScope: span.instrumentationScope,\n droppedAttributesCount: span.droppedAttributesCount,\n droppedEventsCount: span.droppedEventsCount,\n droppedLinksCount: span.droppedLinksCount,\n };\n}\n\nfunction resolveTransportDropReason(attributes: Attributes): RequestDropReason | undefined {\n const method = attributes[\"http.request.method\"] ?? attributes[\"http.method\"];\n if (method === \"OPTIONS\") {\n return \"method\";\n }\n const scheme = attributes[\"url.scheme\"] ?? attributes[\"http.scheme\"];\n return scheme === \"ws\" || scheme === \"wss\" ? \"scheme\" : undefined;\n}\n\n// Missing path, query, and target attributes are derived from the full URL\n// because exclusion, display, and redaction require them.\nfunction writeUrlAttributesFromFullUrl(span: Span): void {\n const attributes = span.attributes;\n if (attributes[\"url.path\"] !== undefined || attributes[\"http.target\"] !== undefined) {\n return;\n }\n const url = attributes[\"url.full\"] ?? attributes[\"http.url\"];\n if (typeof url !== \"string\") {\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n const query = parsed.search.replace(/^\\?/, \"\");\n span.setAttribute(\"url.path\", parsed.pathname);\n if (query) {\n span.setAttribute(\"url.query\", query);\n }\n span.setAttribute(\"http.target\", query ? `${parsed.pathname}?${query}` : parsed.pathname);\n}\n\nconst TRACE_ID_LOW_64_BITS_MASK = (1n << 64n) - 1n;\n\nfunction boundForSampleRate(rate: number): bigint {\n return BigInt(Math.round(rate * 2 ** 64));\n}\n\n// Low 64-bit ratio sampling is deterministic per trace. Comparing the same\n// value at both stages makes the lower rate decisive.\nfunction isTraceSampledIn(traceId: string, bound: bigint): boolean {\n return (BigInt(`0x${traceId}`) & TRACE_ID_LOW_64_BITS_MASK) < bound;\n}\n\n// A throwing or invalid-returning callback resolves to keep: a user bug must\n// never lose data without a warning. Undefined means the callback abstained.\nfunction resolveCallbackSampleRate(\n callback: SamplingCallback,\n span: ReadableSpan,\n optionName: string,\n): number | undefined {\n let result: unknown;\n try {\n result = callback(span);\n } catch {\n logWarning(`The Apitally ${optionName} callback threw an error, so the request was captured`);\n return 1;\n }\n if (result === undefined || result === null) {\n return undefined;\n }\n if (typeof result === \"boolean\") {\n return result ? 1 : 0;\n }\n if (typeof result === \"number\" && result >= 0 && result <= 1) {\n return result;\n }\n logWarning(\n `The Apitally ${optionName} callback returned an invalid value, so the request was captured. Sampling callbacks must synchronously return a number between 0 and 1, a boolean, or undefined.`,\n );\n return 1;\n}\n\nfunction isContribPerMessageSpan(span: Span): boolean {\n const scopeName = span.instrumentationScope?.name ?? \"\";\n return (\n span.kind === SpanKind.INTERNAL &&\n [\" http send\", \" http receive\", \" websocket send\", \" websocket receive\"].some((suffix) =>\n span.name.endsWith(suffix),\n ) &&\n (scopeName.startsWith(\"@opentelemetry/instrumentation\") ||\n scopeName.startsWith(\"opentelemetry.instrumentation.\"))\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apitally",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "Simple API monitoring, analytics and request logging for REST APIs built with AdonisJS, Elysia, Express, Fastify, H3, Hapi, Hono, Koa and NestJS.",
5
5
  "author": "Apitally <hello@apitally.io>",
6
6
  "license": "MIT",
package/dist/consumer.cjs DELETED
@@ -1,61 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_logger = require("./logger.cjs");
3
- const require_context = require("./context.cjs");
4
- const require_requestAttributes = require("./requestAttributes.cjs");
5
- //#region src/consumer.ts
6
- const CONSUMER_IDENTIFIER_ATTRIBUTE = "apitally.consumer.identifier";
7
- const CONSUMER_NAME_ATTRIBUTE = "apitally.consumer.name";
8
- const CONSUMER_GROUP_ATTRIBUTE = "apitally.consumer.group";
9
- function setConsumer(consumer) {
10
- try {
11
- const holder = require_context.getConsumerHolder();
12
- const record = require_context.getRequestRecord();
13
- const normalized = normalizeConsumer(consumer);
14
- if (!holder && !record || !normalized) return;
15
- if (holder) {
16
- holder.identifier = normalized.identifier;
17
- holder.name = normalized.name;
18
- holder.group = normalized.group;
19
- }
20
- writeConsumerAttributes(require_context.getServerSpan(), record, normalized);
21
- } catch (error) {
22
- require_logger.logDebug(`Error setting consumer: ${String(error)}`);
23
- }
24
- }
25
- function writeConsumerAttributesFromContext(parentContext, span, record) {
26
- const holder = parentContext.getValue(require_context.CONSUMER_HOLDER_KEY);
27
- if (!holder?.identifier) return;
28
- const consumer = normalizeConsumer({
29
- identifier: holder.identifier,
30
- name: holder.name,
31
- group: holder.group
32
- });
33
- if (consumer) writeConsumerAttributes(span, record, consumer);
34
- }
35
- function writeConsumerAttributes(span, record, consumer) {
36
- require_requestAttributes.writeRequestAttribute(span, record, CONSUMER_IDENTIFIER_ATTRIBUTE, consumer.identifier);
37
- if (consumer.name) require_requestAttributes.writeRequestAttribute(span, record, CONSUMER_NAME_ATTRIBUTE, consumer.name);
38
- if (consumer.group) require_requestAttributes.writeRequestAttribute(span, record, CONSUMER_GROUP_ATTRIBUTE, consumer.group);
39
- }
40
- function normalizeConsumer(consumer) {
41
- if (typeof consumer === "object" && consumer !== null) {
42
- const identifier = trimAndCap(consumer.identifier, 128);
43
- if (!identifier) return;
44
- return {
45
- identifier,
46
- name: trimAndCap(consumer.name, 64),
47
- group: trimAndCap(consumer.group, 64)
48
- };
49
- }
50
- const identifier = trimAndCap(consumer, 128);
51
- return identifier ? { identifier } : void 0;
52
- }
53
- function trimAndCap(value, maxLength) {
54
- if (typeof value !== "string" && typeof value !== "number") return;
55
- return String(value).trim().slice(0, maxLength) || void 0;
56
- }
57
- //#endregion
58
- exports.setConsumer = setConsumer;
59
- exports.writeConsumerAttributesFromContext = writeConsumerAttributesFromContext;
60
-
61
- //# sourceMappingURL=consumer.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"consumer.cjs","names":["getConsumerHolder","getRequestRecord","getServerSpan","CONSUMER_HOLDER_KEY"],"sources":["../src/consumer.ts"],"sourcesContent":["import type { Context, Span } from \"@opentelemetry/api\";\nimport {\n CONSUMER_HOLDER_KEY,\n type ConsumerHolder,\n getConsumerHolder,\n getRequestRecord,\n getServerSpan,\n type RequestRecord,\n} from \"./context.js\";\nimport { logDebug } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\n\nexport interface ApitallyConsumer {\n identifier: string;\n name?: string | null;\n group?: string | null;\n}\n\nconst CONSUMER_IDENTIFIER_ATTRIBUTE = \"apitally.consumer.identifier\";\nconst CONSUMER_NAME_ATTRIBUTE = \"apitally.consumer.name\";\nconst CONSUMER_GROUP_ATTRIBUTE = \"apitally.consumer.group\";\n\nexport function setConsumer(consumer: ApitallyConsumer | string | null | undefined): void {\n try {\n const holder = getConsumerHolder();\n const record = getRequestRecord();\n const normalized = normalizeConsumer(consumer);\n if ((!holder && !record) || !normalized) {\n return;\n }\n if (holder) {\n holder.identifier = normalized.identifier;\n holder.name = normalized.name;\n holder.group = normalized.group;\n }\n writeConsumerAttributes(getServerSpan(), record, normalized);\n } catch (error) {\n logDebug(`Error setting consumer: ${String(error)}`);\n }\n}\n\n// Applies a consumer inherited in the parent context when a SERVER span starts.\nexport function writeConsumerAttributesFromContext(\n parentContext: Context,\n span: Span,\n record: RequestRecord | undefined,\n): void {\n const holder = parentContext.getValue(CONSUMER_HOLDER_KEY) as ConsumerHolder | undefined;\n if (!holder?.identifier) {\n return;\n }\n const consumer = normalizeConsumer({\n identifier: holder.identifier,\n name: holder.name,\n group: holder.group,\n });\n if (consumer) {\n writeConsumerAttributes(span, record, consumer);\n }\n}\n\nfunction writeConsumerAttributes(\n span: Span | undefined,\n record: RequestRecord | undefined,\n consumer: ApitallyConsumer,\n): void {\n writeRequestAttribute(span, record, CONSUMER_IDENTIFIER_ATTRIBUTE, consumer.identifier);\n if (consumer.name) {\n writeRequestAttribute(span, record, CONSUMER_NAME_ATTRIBUTE, consumer.name);\n }\n if (consumer.group) {\n writeRequestAttribute(span, record, CONSUMER_GROUP_ATTRIBUTE, consumer.group);\n }\n}\n\nfunction normalizeConsumer(consumer: ApitallyConsumer | string | number | null | undefined) {\n if (typeof consumer === \"object\" && consumer !== null) {\n const identifier = trimAndCap(consumer.identifier, 128);\n if (!identifier) {\n return undefined;\n }\n return {\n identifier,\n name: trimAndCap(consumer.name, 64),\n group: trimAndCap(consumer.group, 64),\n };\n }\n const identifier = trimAndCap(consumer, 128);\n return identifier ? { identifier } : undefined;\n}\n\nfunction trimAndCap(value: unknown, maxLength: number): string | undefined {\n if (typeof value !== \"string\" && typeof value !== \"number\") {\n return undefined;\n }\n return String(value).trim().slice(0, maxLength) || undefined;\n}\n"],"mappings":";;;;;AAkBA,MAAM,gCAAgC;AACtC,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AAEjC,SAAgB,YAAY,UAA8D;CACxF,IAAI;EACF,MAAM,SAASA,gBAAAA,kBAAkB;EACjC,MAAM,SAASC,gBAAAA,iBAAiB;EAChC,MAAM,aAAa,kBAAkB,QAAQ;EAC7C,IAAK,CAAC,UAAU,CAAC,UAAW,CAAC,YAC3B;EAEF,IAAI,QAAQ;GACV,OAAO,aAAa,WAAW;GAC/B,OAAO,OAAO,WAAW;GACzB,OAAO,QAAQ,WAAW;EAC5B;EACA,wBAAwBC,gBAAAA,cAAc,GAAG,QAAQ,UAAU;CAC7D,SAAS,OAAO;EACd,eAAA,SAAS,2BAA2B,OAAO,KAAK,GAAG;CACrD;AACF;AAGA,SAAgB,mCACd,eACA,MACA,QACM;CACN,MAAM,SAAS,cAAc,SAASC,gBAAAA,mBAAmB;CACzD,IAAI,CAAC,QAAQ,YACX;CAEF,MAAM,WAAW,kBAAkB;EACjC,YAAY,OAAO;EACnB,MAAM,OAAO;EACb,OAAO,OAAO;CAChB,CAAC;CACD,IAAI,UACF,wBAAwB,MAAM,QAAQ,QAAQ;AAElD;AAEA,SAAS,wBACP,MACA,QACA,UACM;CACN,0BAAA,sBAAsB,MAAM,QAAQ,+BAA+B,SAAS,UAAU;CACtF,IAAI,SAAS,MACX,0BAAA,sBAAsB,MAAM,QAAQ,yBAAyB,SAAS,IAAI;CAE5E,IAAI,SAAS,OACX,0BAAA,sBAAsB,MAAM,QAAQ,0BAA0B,SAAS,KAAK;AAEhF;AAEA,SAAS,kBAAkB,UAAiE;CAC1F,IAAI,OAAO,aAAa,YAAY,aAAa,MAAM;EACrD,MAAM,aAAa,WAAW,SAAS,YAAY,GAAG;EACtD,IAAI,CAAC,YACH;EAEF,OAAO;GACL;GACA,MAAM,WAAW,SAAS,MAAM,EAAE;GAClC,OAAO,WAAW,SAAS,OAAO,EAAE;EACtC;CACF;CACA,MAAM,aAAa,WAAW,UAAU,GAAG;CAC3C,OAAO,aAAa,EAAE,WAAW,IAAI,KAAA;AACvC;AAEA,SAAS,WAAW,OAAgB,WAAuC;CACzE,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD;CAEF,OAAO,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS,KAAK,KAAA;AACrD"}
@@ -1,12 +0,0 @@
1
- import { RequestRecord } from "./context.cjs";
2
- import { Context, Span } from "@opentelemetry/api";
3
- //#region src/consumer.d.ts
4
- export interface ApitallyConsumer {
5
- identifier: string;
6
- name?: string | null;
7
- group?: string | null;
8
- }
9
- export declare function setConsumer(consumer: ApitallyConsumer | string | null | undefined): void;
10
- export declare function writeConsumerAttributesFromContext(parentContext: Context, span: Span, record: RequestRecord | undefined): void;
11
- //#endregion
12
- //# sourceMappingURL=consumer.d.cts.map
@@ -1,12 +0,0 @@
1
- import { RequestRecord } from "./context.js";
2
- import { Context, Span } from "@opentelemetry/api";
3
- //#region src/consumer.d.ts
4
- export interface ApitallyConsumer {
5
- identifier: string;
6
- name?: string | null;
7
- group?: string | null;
8
- }
9
- export declare function setConsumer(consumer: ApitallyConsumer | string | null | undefined): void;
10
- export declare function writeConsumerAttributesFromContext(parentContext: Context, span: Span, record: RequestRecord | undefined): void;
11
- //#endregion
12
- //# sourceMappingURL=consumer.d.ts.map