@pingops/otel 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +95 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -31
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +29 -31
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +96 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["logger","OTLPTraceExporter","SimpleSpanProcessor","BatchSpanProcessor","url","trace","DEFAULT_MAX_REQUEST_BODY_SIZE","DEFAULT_MAX_RESPONSE_BODY_SIZE","extractDomainFromUrl","url","getDomainRule","shouldCaptureRequestBody","context","PINGOPS_CAPTURE_REQUEST_BODY","globalConfig","shouldCaptureResponseBody","PINGOPS_CAPTURE_RESPONSE_BODY","HttpInstrumentation","ClientRequest","IncomingMessage","context","PINGOPS_HTTP_ENABLED","URL","url","context","PINGOPS_CAPTURE_REQUEST_BODY","globalConfig","PINGOPS_CAPTURE_RESPONSE_BODY","InstrumentationBase","METRIC_HTTP_CLIENT_REQUEST_DURATION","ValueType","diagch","ATTR_HTTP_REQUEST_METHOD","ATTR_HTTP_REQUEST_METHOD_ORIGINAL","ATTR_URL_FULL","ATTR_URL_PATH","ATTR_URL_QUERY","ATTR_URL_SCHEME","ATTR_SERVER_ADDRESS","ATTR_SERVER_PORT","ATTR_USER_AGENT_ORIGINAL","trace","INVALID_SPAN_CONTEXT","SpanKind","ATTR_NETWORK_PEER_ADDRESS","ATTR_NETWORK_PEER_PORT","ATTR_HTTP_RESPONSE_STATUS_CODE","SpanStatusCode","ATTR_ERROR_TYPE","context","PINGOPS_HTTP_ENABLED"],"sources":["../src/config-store.ts","../src/span-processor.ts","../src/tracer-provider.ts","../src/instrumentations/http/pingops-http.ts","../src/instrumentations/http/http.ts","../src/instrumentations/undici/pingops-undici.ts","../src/instrumentations/undici/undici.ts","../src/instrumentations/index.ts"],"sourcesContent":["/**\n * Global configuration store for PingOps processor\n * Allows instrumentations to access processor configuration without direct coupling\n */\n\nimport type { DomainRule } from \"@pingops/core\";\n\ninterface GlobalConfig {\n captureRequestBody?: boolean;\n captureResponseBody?: boolean;\n domainAllowList?: DomainRule[];\n}\n\nlet globalConfig: GlobalConfig | null = null;\n\n/**\n * Sets the global processor configuration\n * @param config - Configuration to store\n */\nexport function setGlobalConfig(config: GlobalConfig): void {\n globalConfig = config;\n}\n\n/**\n * Gets the global processor configuration\n * @returns The stored configuration or null if not set\n */\nexport function getGlobalConfig(): GlobalConfig | null {\n return globalConfig;\n}\n\n/**\n * Clears the global configuration (useful for testing)\n */\nexport function clearGlobalConfig(): void {\n globalConfig = null;\n}\n","/**\n * PingopsSpanProcessor - OpenTelemetry SpanProcessor implementation\n * Observes finished spans and sends eligible ones to PingOps backend\n *\n * This processor provides:\n * - Automatic filtering of spans (CLIENT spans with HTTP/GenAI attributes only)\n * - Domain and header filtering based on configuration\n * - Batched or immediate export modes using OTLP exporters\n * - Fire-and-forget transport (never blocks application)\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { PingopsSpanProcessor } from '@pingops/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new PingopsSpanProcessor({\n * apiKey: 'your-api-key',\n * baseUrl: 'https://api.pingops.com',\n * serviceName: 'my-service',\n * exportMode: 'batched', // or 'immediate'\n * domainAllowList: [\n * { domain: 'api.example.com' }\n * ]\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n Span,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n BatchSpanProcessor,\n SimpleSpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport type { Context, Attributes } from \"@opentelemetry/api\";\nimport {\n isSpanEligible,\n shouldCaptureSpan,\n type DomainRule,\n type HeaderRedactionConfig,\n createLogger,\n getPropagatedAttributesFromContext,\n extractSpanPayload,\n} from \"@pingops/core\";\nimport type { PingopsProcessorConfig } from \"./config\";\nimport { setGlobalConfig } from \"./config-store\";\n\nconst logger = createLogger(\"[PingOps Processor]\");\n\n/**\n * Creates a filtered span wrapper that applies header filtering to attributes\n *\n * This wrapper applies both domain-specific and global header filtering:\n * - Uses domain allow list to determine domain-specific header rules\n * - Applies global header allow/deny lists\n * - Filters headers from http.request.header and http.response.header attributes\n *\n * Uses a Proxy to automatically forward all properties and methods to the original span,\n * except for 'attributes' which returns the filtered version. This approach is future-proof\n * and will work with any new methods or properties added to ReadableSpan.\n *\n * This allows us to filter headers before the span is serialized by OTLP exporter\n */\nfunction createFilteredSpan(\n span: ReadableSpan,\n domainAllowList?: DomainRule[],\n globalHeadersAllowList?: string[],\n globalHeadersDenyList?: string[],\n globalCaptureRequestBody?: boolean,\n globalCaptureResponseBody?: boolean,\n headerRedaction?: HeaderRedactionConfig\n): ReadableSpan {\n // Use extractSpanPayload to get filtered attributes\n // This handles both domain-specific header rules and global header filtering\n // as well as body capture filtering and header redaction\n const payload = extractSpanPayload(\n span,\n domainAllowList,\n globalHeadersAllowList,\n globalHeadersDenyList,\n globalCaptureRequestBody,\n globalCaptureResponseBody,\n headerRedaction\n );\n const filteredAttributes = (payload?.attributes ??\n span.attributes) as Attributes;\n logger.debug(\"Payload\", { payload });\n\n // Create a Proxy that intercepts 'attributes' access and forwards everything else\n return new Proxy(span, {\n get(target, prop) {\n // Intercept 'attributes' to return filtered version\n if (prop === \"attributes\") {\n return filteredAttributes;\n }\n // Forward all other property/method access to the original span\n const value = (target as ReadableSpan & Record<string, unknown>)[\n prop as string\n ];\n // If it's a function, bind it to the original target to preserve 'this' context\n if (typeof value === \"function\") {\n return (value as (...args: unknown[]) => unknown).bind(target);\n }\n return value;\n },\n });\n}\n\n/**\n * OpenTelemetry span processor for sending spans to PingOps backend.\n *\n * This processor wraps OpenTelemetry's built-in processors (BatchSpanProcessor or SimpleSpanProcessor)\n * and applies filtering before passing spans to the OTLP exporter.\n */\nexport class PingopsSpanProcessor implements SpanProcessor {\n private processor: SpanProcessor;\n private config: {\n debug: boolean;\n headersAllowList?: string[];\n headersDenyList?: string[];\n domainAllowList?: DomainRule[];\n domainDenyList?: DomainRule[];\n captureRequestBody?: boolean;\n captureResponseBody?: boolean;\n headerRedaction?: HeaderRedactionConfig;\n };\n\n /**\n * Creates a new PingopsSpanProcessor instance.\n *\n * @param config - Configuration parameters for the processor\n */\n constructor(config: PingopsProcessorConfig) {\n const exportMode = config.exportMode ?? \"batched\";\n\n // Get API key from config or environment\n const apiKey = config.apiKey || process.env.PINGOPS_API_KEY || \"\";\n\n // Create OTLP exporter pointing to PingOps backend\n const exporter = new OTLPTraceExporter({\n url: `${config.baseUrl}/v1/traces`,\n headers: {\n Authorization: apiKey ? `Bearer ${apiKey}` : \"\",\n \"Content-Type\": \"application/json\",\n },\n timeoutMillis: 5000,\n });\n\n // Create underlying processor based on export mode\n if (exportMode === \"immediate\") {\n this.processor = new SimpleSpanProcessor(exporter);\n } else {\n this.processor = new BatchSpanProcessor(exporter, {\n maxExportBatchSize: config.batchSize ?? 50,\n scheduledDelayMillis: config.batchTimeout ?? 5000,\n });\n }\n\n this.config = {\n debug: config.debug ?? false,\n headersAllowList: config.headersAllowList,\n headersDenyList: config.headersDenyList,\n domainAllowList: config.domainAllowList,\n domainDenyList: config.domainDenyList,\n captureRequestBody: config.captureRequestBody,\n captureResponseBody: config.captureResponseBody,\n headerRedaction: config.headerRedaction,\n };\n\n // Register global config for instrumentations to access\n setGlobalConfig({\n captureRequestBody: config.captureRequestBody,\n captureResponseBody: config.captureResponseBody,\n domainAllowList: config.domainAllowList,\n });\n\n logger.info(\"Initialized PingopsSpanProcessor\", {\n baseUrl: config.baseUrl,\n exportMode,\n batchSize: config.batchSize,\n batchTimeout: config.batchTimeout,\n hasDomainAllowList:\n !!config.domainAllowList && config.domainAllowList.length > 0,\n hasDomainDenyList:\n !!config.domainDenyList && config.domainDenyList.length > 0,\n hasHeadersAllowList:\n !!config.headersAllowList && config.headersAllowList.length > 0,\n hasHeadersDenyList:\n !!config.headersDenyList && config.headersDenyList.length > 0,\n });\n }\n\n /**\n * Called when a span starts - extracts parent attributes from context and adds them to the span\n */\n onStart(span: Span, parentContext: Context): void {\n const spanContext = span.spanContext();\n logger.debug(\"Span started\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n });\n\n // Extract propagated attributes from context and set them on the span\n const propagatedAttributes =\n getPropagatedAttributesFromContext(parentContext);\n if (Object.keys(propagatedAttributes).length > 0) {\n for (const [key, value] of Object.entries(propagatedAttributes)) {\n // Type guard: value must be string or string[] for OpenTelemetry attributes\n if (typeof value === \"string\" || Array.isArray(value)) {\n span.setAttribute(key, value);\n }\n }\n logger.debug(\"Set propagated attributes on span\", {\n spanName: span.name,\n attributeKeys: Object.keys(propagatedAttributes),\n });\n }\n\n this.processor.onStart(span, parentContext);\n }\n /**\n * Called when a span ends. Filters the span and passes it to the underlying processor if eligible.\n *\n * This method:\n * 1. Checks if the span is eligible (CLIENT + HTTP/GenAI attributes)\n * 2. Applies domain filtering (determines if span should be exported)\n * 3. Applies header filtering via FilteredSpan wrapper (domain-specific and global rules)\n * 4. If eligible, passes filtered span to underlying OTLP processor for export\n */\n onEnd(span: ReadableSpan): void {\n const spanContext = span.spanContext();\n logger.debug(\"Span ended, processing\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n spanKind: span.kind,\n });\n\n try {\n // Step 1: Check if span is eligible (CLIENT + HTTP/GenAI attributes)\n if (!isSpanEligible(span)) {\n logger.debug(\"Span not eligible, skipping\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n reason: \"not CLIENT or missing HTTP/GenAI attributes\",\n });\n return;\n }\n\n // Step 2: Extract URL for domain filtering\n const attributes = span.attributes;\n const url =\n (attributes[\"http.url\"] as string) ||\n (attributes[\"url.full\"] as string) ||\n (attributes[\"server.address\"]\n ? `https://${String(attributes[\"server.address\"])}`\n : \"\");\n\n logger.debug(\"Extracted URL for domain filtering\", {\n spanName: span.name,\n url,\n hasHttpUrl: !!attributes[\"http.url\"],\n hasUrlFull: !!attributes[\"url.full\"],\n hasServerAddress: !!attributes[\"server.address\"],\n });\n\n // Step 3: Apply domain filtering\n if (url) {\n const shouldCapture = shouldCaptureSpan(\n url,\n this.config.domainAllowList,\n this.config.domainDenyList\n );\n\n if (!shouldCapture) {\n logger.info(\"Span filtered out by domain rules\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n url,\n });\n return;\n }\n } else {\n logger.debug(\"No URL found for domain filtering, proceeding\", {\n spanName: span.name,\n });\n }\n\n // Step 4: Apply filtering (header filtering with domain-specific rules) by wrapping the span\n const filteredSpan = createFilteredSpan(\n span,\n this.config.domainAllowList,\n this.config.headersAllowList,\n this.config.headersDenyList,\n this.config.captureRequestBody,\n this.config.captureResponseBody,\n this.config.headerRedaction\n );\n\n // Step 5: Span passed all filters, pass filtered span to underlying processor for export\n this.processor.onEnd(filteredSpan);\n\n logger.info(\"Span passed all filters and queued for export\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n url,\n hasHeaderFiltering: !!(\n this.config.headersAllowList || this.config.headersDenyList\n ),\n });\n } catch (error) {\n // Defensive error handling - never crash the app\n logger.error(\"Error processing span\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n /**\n * Forces an immediate flush of all pending spans.\n *\n * @returns Promise that resolves when all pending operations are complete\n */\n public async forceFlush(): Promise<void> {\n logger.info(\"Force flushing spans\");\n try {\n await this.processor.forceFlush();\n logger.info(\"Force flush complete\");\n } catch (error) {\n logger.error(\"Error during force flush\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n */\n public async shutdown(): Promise<void> {\n logger.info(\"Shutting down processor\");\n try {\n await this.processor.shutdown();\n logger.info(\"Processor shutdown complete\");\n } catch (error) {\n logger.error(\"Error during processor shutdown\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n","/**\n * Tracer Provider with global state and isolated TracerProvider architecture\n *\n * This module provides an isolated TracerProvider that shares the same\n * span processors (like PingopsSpanProcessor) with the main OpenTelemetry SDK.\n * This ensures manual spans created via startSpan are properly processed.\n *\n * Architecture follows Langfuse's pattern with global state management.\n */\n\nimport type { TracerProvider } from \"@opentelemetry/api\";\nimport { trace } from \"@opentelemetry/api\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { createLogger } from \"@pingops/core\";\n\n/**\n * Global symbol for PingOps state\n */\nconst PINGOPS_GLOBAL_SYMBOL = Symbol.for(\"pingops\");\n\n/**\n * Logger instance for tracer provider\n */\nconst logger = createLogger(\"[PingOps TracerProvider]\");\n\n/**\n * Global state interface\n */\ntype PingopsGlobalState = {\n isolatedTracerProvider: TracerProvider | null;\n};\n\n/**\n * Creates initial global state\n */\nfunction createState(): PingopsGlobalState {\n return {\n isolatedTracerProvider: null,\n };\n}\n\n/**\n * Interface for globalThis with PingOps state\n */\ninterface GlobalThis {\n [PINGOPS_GLOBAL_SYMBOL]?: PingopsGlobalState;\n}\n\n/**\n * Gets the global state, creating it if it doesn't exist\n */\nfunction getGlobalState(): PingopsGlobalState {\n const initialState = createState();\n\n try {\n const g = globalThis as typeof globalThis & GlobalThis;\n\n if (typeof g !== \"object\" || g === null) {\n // Fallback if globalThis is not available\n logger.warn(\"globalThis is not available, using fallback state\");\n return initialState;\n }\n\n if (!g[PINGOPS_GLOBAL_SYMBOL]) {\n logger.debug(\"Creating new global state\");\n Object.defineProperty(g, PINGOPS_GLOBAL_SYMBOL, {\n value: initialState,\n writable: false, // lock the slot (not the contents)\n configurable: false,\n enumerable: false,\n });\n } else {\n logger.debug(\"Retrieved existing global state\");\n }\n\n return g[PINGOPS_GLOBAL_SYMBOL]!;\n } catch (err) {\n logger.error(\n \"Failed to access global state:\",\n err instanceof Error ? err.message : String(err)\n );\n // Fallback on error\n return initialState;\n }\n}\n\n/**\n * Sets an isolated TracerProvider for PingOps tracing operations.\n *\n * This allows PingOps to use its own TracerProvider instance, separate from\n * the global OpenTelemetry TracerProvider. This is useful for avoiding conflicts\n * with other OpenTelemetry instrumentation in the application.\n *\n * @param provider - The TracerProvider instance to use, or null to clear the isolated provider\n * @public\n */\nexport function setPingopsTracerProvider(\n provider: TracerProvider | null\n): void {\n const state = getGlobalState();\n const hadProvider = state.isolatedTracerProvider !== null;\n\n state.isolatedTracerProvider = provider;\n\n if (provider) {\n logger.info(\"Set isolated TracerProvider\", {\n hadPrevious: hadProvider,\n providerType: provider.constructor.name,\n });\n } else {\n logger.info(\"Cleared isolated TracerProvider\", {\n hadPrevious: hadProvider,\n });\n }\n}\n\n/**\n * Gets the TracerProvider for PingOps tracing operations.\n *\n * Returns the isolated TracerProvider if one has been set via setPingopsTracerProvider(),\n * otherwise falls back to the global OpenTelemetry TracerProvider.\n *\n * @returns The TracerProvider instance to use for PingOps tracing\n * @public\n */\nexport function getPingopsTracerProvider(): TracerProvider {\n const { isolatedTracerProvider } = getGlobalState();\n\n if (isolatedTracerProvider) {\n logger.debug(\"Using isolated TracerProvider\", {\n providerType: isolatedTracerProvider.constructor.name,\n });\n return isolatedTracerProvider;\n }\n\n const globalProvider = trace.getTracerProvider();\n logger.debug(\"Using global TracerProvider\", {\n providerType: globalProvider.constructor.name,\n });\n return globalProvider;\n}\n\n/**\n * Initializes the isolated TracerProvider with the given span processors\n *\n * This creates a separate TracerProvider that shares the same span processors\n * (like PingopsSpanProcessor) with the main SDK. This ensures manual spans\n * are processed correctly.\n *\n * @param spanProcessors - Array of span processors to use (e.g., PingopsSpanProcessor)\n * @param serviceName - Service name for resource attributes\n * @deprecated Use setPingopsTracerProvider instead\n */\nexport function initializeTracerProvider(\n spanProcessors: SpanProcessor[],\n serviceName: string\n): void {\n logger.info(\"Initializing TracerProvider\", {\n serviceName,\n spanProcessorCount: spanProcessors.length,\n });\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n });\n logger.debug(\"Created resource\", { serviceName });\n\n // In version 2.2.0, span processors are passed in the constructor\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors,\n });\n logger.debug(\"Created NodeTracerProvider\", {\n spanProcessorCount: spanProcessors.length,\n });\n\n // Register the provider globally\n tracerProvider.register();\n logger.info(\"Registered TracerProvider globally\");\n\n // Set it in global state\n setPingopsTracerProvider(tracerProvider);\n logger.info(\"TracerProvider initialization complete\");\n}\n\n/**\n * Gets the isolated TracerProvider instance\n *\n * @returns The TracerProvider instance, or null if not initialized\n * @deprecated Use getPingopsTracerProvider instead\n */\nexport function getTracerProvider(): NodeTracerProvider | null {\n const provider = getPingopsTracerProvider();\n return provider instanceof NodeTracerProvider ? provider : null;\n}\n\n/**\n * Shuts down the TracerProvider and flushes remaining spans\n */\nexport async function shutdownTracerProvider(): Promise<void> {\n logger.info(\"Shutting down TracerProvider\");\n const provider = getPingopsTracerProvider();\n\n // Check if provider has shutdown method (NodeTracerProvider and compatible providers)\n const providerWithShutdown = provider as TracerProvider & {\n shutdown?: () => Promise<void>;\n };\n if (\n providerWithShutdown &&\n typeof providerWithShutdown.shutdown === \"function\"\n ) {\n logger.debug(\"Calling provider.shutdown()\");\n try {\n await providerWithShutdown.shutdown();\n logger.info(\"TracerProvider shutdown complete\");\n } catch (error) {\n logger.error(\n \"Error during TracerProvider shutdown:\",\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n } else {\n logger.warn(\"TracerProvider does not have shutdown method, skipping\");\n }\n\n setPingopsTracerProvider(null);\n logger.info(\"TracerProvider shutdown finished\");\n}\n","/**\n * Pingops HTTP instrumentation that extends HttpInstrumentation\n * with request/response body capture and network timing metrics\n */\n\nimport { ClientRequest, IncomingMessage, ServerResponse } from \"http\";\nimport { Span, context } from \"@opentelemetry/api\";\nimport {\n HttpInstrumentation,\n HttpInstrumentationConfig,\n HttpRequestCustomAttributeFunction,\n HttpResponseCustomAttributeFunction,\n} from \"@opentelemetry/instrumentation-http\";\nimport { Socket } from \"net\";\nimport {\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"@pingops/core\";\nimport { getGlobalConfig } from \"../../config-store\";\nimport type { DomainRule } from \"@pingops/core\";\n\n// Constants\nconst DEFAULT_MAX_REQUEST_BODY_SIZE: number = 4 * 1024; // 4 KB\nconst DEFAULT_MAX_RESPONSE_BODY_SIZE: number = 4 * 1024; // 4 KB\nconst NETWORK_TIMINGS_PROP_NAME: string = \"__networkTimings\";\n\n// Semantic attributes\nexport const PingopsSemanticAttributes = {\n HTTP_REQUEST_BODY: \"http.request.body\",\n HTTP_RESPONSE_BODY: \"http.response.body\",\n NETWORK_DNS_LOOKUP_DURATION: \"net.dns.lookup.duration\",\n NETWORK_TCP_CONNECT_DURATION: \"net.tcp.connect.duration\",\n NETWORK_TLS_HANDSHAKE_DURATION: \"net.tls.handshake.duration\",\n NETWORK_TTFB_DURATION: \"net.ttfb.duration\",\n NETWORK_CONTENT_TRANSFER_DURATION: \"net.content.transfer.duration\",\n};\n\n// Types\nexport type NetworkTimings = {\n startAt?: number;\n dnsLookupAt?: number;\n tcpConnectionAt?: number;\n tlsHandshakeAt?: number;\n firstByteAt?: number;\n endAt?: number;\n};\n\nexport interface PingopsInstrumentationConfig {\n /**\n * Maximum size of request body to capture in bytes\n * @defaultValue 4096 (4 KB)\n */\n maxRequestBodySize?: number;\n\n /**\n * Maximum size of response body to capture in bytes\n * @defaultValue 4096 (4 KB)\n */\n maxResponseBodySize?: number;\n}\n\n/**\n * Manually flattens a nested object into dot-notation keys\n */\nfunction flatten(obj: Record<string, any>, prefix = \"\"): Record<string, any> {\n const result: Record<string, any> = {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n !(value instanceof Buffer)\n ) {\n // Recursively flatten nested objects\n Object.assign(result, flatten(value, newKey));\n } else {\n result[newKey] = value;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Sets an attribute value on a span, handling various types appropriately\n */\nfunction setAttributeValue(span: Span, attrName: string, attrValue: any): void {\n if (\n typeof attrValue === \"string\" ||\n typeof attrValue === \"number\" ||\n typeof attrValue === \"boolean\"\n ) {\n span.setAttribute(attrName, attrValue);\n } else if (attrValue instanceof Buffer) {\n span.setAttribute(attrName, attrValue.toString(\"utf8\"));\n } else if (typeof attrValue == \"object\") {\n span.setAttributes(\n flatten({\n [attrName]: attrValue,\n })\n );\n } else if (Array.isArray(attrValue)) {\n // Check whether there is any element\n if (attrValue.length) {\n // Try to resolve array type over first element.\n // Other elements might have different types but this is just best effort solution.\n const firstElement: any = attrValue[0];\n if (\n typeof firstElement === \"string\" ||\n typeof firstElement === \"number\" ||\n typeof firstElement === \"boolean\"\n ) {\n span.setAttribute(attrName, attrValue);\n } else {\n // TODO What should we do with other array types???\n }\n } else {\n span.setAttribute(attrName, attrValue);\n }\n }\n // TODO What should we do with other types???\n}\n\n/**\n * Processes network timings and sets them as span attributes (no spans created)\n */\nfunction processNetworkTimings(\n span: Span,\n networkTimings: NetworkTimings\n): void {\n // Calculate and set network timing attributes (no spans created)\n if (networkTimings.startAt && networkTimings.dnsLookupAt) {\n span.setAttribute(\n PingopsSemanticAttributes.NETWORK_DNS_LOOKUP_DURATION,\n networkTimings.dnsLookupAt - networkTimings.startAt\n );\n }\n\n if (networkTimings.dnsLookupAt && networkTimings.tcpConnectionAt) {\n span.setAttribute(\n PingopsSemanticAttributes.NETWORK_TCP_CONNECT_DURATION,\n networkTimings.tcpConnectionAt - networkTimings.dnsLookupAt\n );\n }\n\n if (networkTimings.tcpConnectionAt && networkTimings.tlsHandshakeAt) {\n span.setAttribute(\n PingopsSemanticAttributes.NETWORK_TLS_HANDSHAKE_DURATION,\n networkTimings.tlsHandshakeAt - networkTimings.tcpConnectionAt\n );\n }\n\n const startTTFB: number | undefined =\n networkTimings.tlsHandshakeAt || networkTimings.tcpConnectionAt;\n if (networkTimings.firstByteAt && startTTFB) {\n span.setAttribute(\n PingopsSemanticAttributes.NETWORK_TTFB_DURATION,\n networkTimings.firstByteAt - startTTFB\n );\n }\n\n if (networkTimings.firstByteAt && networkTimings.endAt) {\n span.setAttribute(\n PingopsSemanticAttributes.NETWORK_CONTENT_TRANSFER_DURATION,\n networkTimings.endAt - networkTimings.firstByteAt\n );\n }\n}\n\n/**\n * Initializes network timings on a span\n */\nfunction initializeNetworkTimings(span: Span): NetworkTimings {\n const networkTimings: NetworkTimings = {\n startAt: Date.now(),\n };\n Object.defineProperty(span, NETWORK_TIMINGS_PROP_NAME, {\n enumerable: false,\n configurable: true,\n writable: false,\n value: networkTimings,\n });\n return networkTimings;\n}\n\n/**\n * Extracts domain from URL\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n const match = url.match(/^(?:https?:\\/\\/)?([^/]+)/);\n return match ? match[1] : \"\";\n }\n}\n\n/**\n * Gets domain rule configuration for a given URL\n */\nfunction getDomainRule(\n url: string,\n domainAllowList?: DomainRule[]\n): DomainRule | undefined {\n if (!domainAllowList) {\n return undefined;\n }\n\n const domain = extractDomainFromUrl(url);\n for (const rule of domainAllowList) {\n if (\n domain === rule.domain ||\n domain.endsWith(`.${rule.domain}`) ||\n domain === rule.domain.slice(1)\n ) {\n return rule;\n }\n }\n return undefined;\n}\n\n/**\n * Determines if request body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureRequestBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from wrapHttp)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_REQUEST_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureRequestBody !== undefined) {\n return domainRule.captureRequestBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureRequestBody !== undefined) {\n return globalConfig.captureRequestBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Determines if response body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureResponseBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from wrapHttp)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_RESPONSE_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureResponseBody !== undefined) {\n return domainRule.captureResponseBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureResponseBody !== undefined) {\n return globalConfig.captureResponseBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Captures request body from string or Buffer data\n */\nfunction captureRequestBody(\n span: Span,\n data: string | Buffer,\n maxSize: number,\n semanticAttr: string,\n url?: string\n): void {\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(url)) {\n return;\n }\n\n if (data.length && data.length <= maxSize) {\n try {\n const requestBody: string =\n typeof data === \"string\" ? data : data.toString(\"utf-8\");\n if (requestBody) {\n setAttributeValue(span, semanticAttr, requestBody);\n }\n } catch (e) {\n console.error(\"Error occurred while capturing request body:\", e);\n }\n }\n}\n\n/**\n * Captures response body from chunks\n */\nfunction captureResponseBody(\n span: Span,\n chunks: Buffer[] | null,\n semanticAttr: string,\n url?: string\n): void {\n // Check if body capture is enabled\n if (!shouldCaptureResponseBody(url)) {\n return;\n }\n\n if (chunks && chunks.length) {\n try {\n const concatedChunks: Buffer = Buffer.concat(chunks);\n const responseBody: string = concatedChunks.toString(\"utf8\");\n if (responseBody) {\n setAttributeValue(span, semanticAttr, responseBody);\n }\n } catch (e) {\n console.error(\"Error occurred while capturing response body:\", e);\n }\n }\n}\n\n/**\n * Captures HTTP request headers as span attributes\n */\nfunction captureRequestHeaders(\n span: Span,\n headers: Record<string, string | string[] | undefined>\n): void {\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n span.setAttribute(\n `pingops.http.request.header.${key.toLowerCase()}`,\n Array.isArray(value) ? value.join(\",\") : String(value)\n );\n }\n }\n}\n\n/**\n * Captures HTTP response headers as span attributes\n */\nfunction captureResponseHeaders(\n span: Span,\n headers: Record<string, string | string[] | undefined>\n): void {\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n span.setAttribute(\n `pingops.http.response.header.${key.toLowerCase()}`,\n Array.isArray(value) ? value.join(\",\") : String(value)\n );\n }\n }\n}\n\n// Re-export semantic attributes for backward compatibility\nexport const PingopsHttpSemanticAttributes = PingopsSemanticAttributes;\n\nexport interface PingopsHttpInstrumentationConfig\n extends HttpInstrumentationConfig, PingopsInstrumentationConfig {}\n\nexport class PingopsHttpInstrumentation extends HttpInstrumentation {\n constructor(config?: PingopsHttpInstrumentationConfig) {\n super(config);\n this._config = this._createConfig(config);\n }\n\n private _createConfig(\n config?: PingopsHttpInstrumentationConfig\n ): PingopsHttpInstrumentationConfig {\n return {\n ...config,\n requestHook: this._createRequestHook(config?.requestHook, config),\n responseHook: this._createResponseHook(config?.responseHook, config),\n };\n }\n\n private _createRequestHook(\n originalRequestHook?: HttpRequestCustomAttributeFunction,\n config?: PingopsHttpInstrumentationConfig\n ): HttpRequestCustomAttributeFunction {\n return (span: Span, request: ClientRequest | IncomingMessage): void => {\n // Capture request headers\n const headers = (request as IncomingMessage).headers;\n if (headers) {\n captureRequestHeaders(span, headers);\n }\n if (request instanceof ClientRequest) {\n const networkTimings = initializeNetworkTimings(span);\n\n const maxRequestBodySize: number =\n config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE;\n\n // Extract URL from request\n const url =\n request.path && request.getHeader(\"host\")\n ? `${request.protocol || \"http:\"}//${request.getHeader(\"host\")}${request.path}`\n : undefined;\n\n const originalWrite = request.write.bind(request);\n const originalEnd = request.end.bind(request);\n\n // Capture request body\n request.write = (data: any): boolean => {\n if (typeof data === \"string\" || data instanceof Buffer) {\n captureRequestBody(\n span,\n data,\n maxRequestBodySize,\n PingopsSemanticAttributes.HTTP_REQUEST_BODY,\n url\n );\n }\n return originalWrite(data);\n };\n\n request.end = (data: any): ClientRequest => {\n if (typeof data === \"string\" || data instanceof Buffer) {\n captureRequestBody(\n span,\n data,\n maxRequestBodySize,\n PingopsSemanticAttributes.HTTP_REQUEST_BODY,\n url\n );\n }\n return originalEnd(data);\n };\n\n // Track network timings\n request.on(\"socket\", (socket: Socket) => {\n socket.on(\"lookup\", (): void => {\n networkTimings.dnsLookupAt = Date.now();\n });\n socket.on(\"connect\", (): void => {\n networkTimings.tcpConnectionAt = Date.now();\n });\n socket.on(\"secureConnect\", (): void => {\n networkTimings.tlsHandshakeAt = Date.now();\n });\n });\n }\n\n if (originalRequestHook) {\n originalRequestHook(span, request);\n }\n };\n }\n\n private _createResponseHook(\n originalResponseHook?: HttpResponseCustomAttributeFunction,\n config?: PingopsHttpInstrumentationConfig\n ): HttpResponseCustomAttributeFunction {\n return (span: Span, response: IncomingMessage | ServerResponse): void => {\n // Capture response headers\n const headers = (response as IncomingMessage).headers;\n if (headers) {\n captureResponseHeaders(span, headers);\n }\n\n if (response instanceof IncomingMessage) {\n const networkTimings: NetworkTimings = (span as any)[\n NETWORK_TIMINGS_PROP_NAME\n ];\n\n const maxResponseBodySize: number =\n config?.maxResponseBodySize || DEFAULT_MAX_RESPONSE_BODY_SIZE;\n\n // Extract URL from response (if available via request)\n // Note: We can't easily get URL from IncomingMessage, so we'll rely on\n // domain rules matching based on headers or skip domain-specific checks\n const url = response.url || undefined;\n\n let chunks: Buffer[] | null = [];\n let totalSize: number = 0;\n\n // Only capture response body if enabled\n const shouldCapture = shouldCaptureResponseBody(url);\n\n // Capture response body\n response.prependListener(\"data\", (chunk: any): void => {\n if (!chunk || !shouldCapture) {\n return;\n }\n if (typeof chunk === \"string\" || chunk instanceof Buffer) {\n totalSize += chunk.length;\n if (chunks && totalSize <= maxResponseBodySize) {\n chunks.push(\n typeof chunk === \"string\" ? Buffer.from(chunk) : chunk\n );\n } else {\n // No need to capture partial response body\n chunks = null;\n }\n }\n });\n\n response.prependOnceListener(\"end\", (): void => {\n if (networkTimings) {\n networkTimings.endAt = Date.now();\n processNetworkTimings(span, networkTimings);\n }\n\n captureResponseBody(\n span,\n chunks,\n PingopsSemanticAttributes.HTTP_RESPONSE_BODY,\n url\n );\n });\n\n if (networkTimings) {\n response.once(\"readable\", (): void => {\n networkTimings.firstByteAt = Date.now();\n });\n }\n }\n\n if (originalResponseHook) {\n originalResponseHook(span, response);\n }\n };\n }\n}\n","/**\n * HTTP instrumentation for OpenTelemetry\n */\n\nimport { context } from \"@opentelemetry/api\";\nimport { PINGOPS_HTTP_ENABLED } from \"@pingops/core\";\nimport {\n PingopsHttpInstrumentation,\n type PingopsHttpInstrumentationConfig,\n} from \"./pingops-http\";\n\n/**\n * Creates an HTTP instrumentation instance\n *\n * @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled\n * @param config - Optional configuration for the instrumentation\n * @returns PingopsHttpInstrumentation instance\n */\nexport function createHttpInstrumentation(\n isGlobalInstrumentationEnabled: () => boolean,\n config?: Partial<PingopsHttpInstrumentationConfig>\n): PingopsHttpInstrumentation {\n return new PingopsHttpInstrumentation({\n ignoreIncomingRequestHook: () => true, // Only instrument outgoing requests\n ignoreOutgoingRequestHook: () => {\n // If global instrumentation is enabled, instrument all outgoing requests\n if (isGlobalInstrumentationEnabled()) {\n return false;\n }\n // If global instrumentation is NOT enabled, only instrument when PINGOPS_HTTP_ENABLED is true\n return context.active().getValue(PINGOPS_HTTP_ENABLED) !== true;\n },\n ...config,\n });\n}\n","import * as diagch from \"diagnostics_channel\";\nimport { URL } from \"url\";\n\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from \"@opentelemetry/instrumentation\";\nimport {\n Attributes,\n context,\n Histogram,\n HrTime,\n INVALID_SPAN_CONTEXT,\n propagation,\n Span,\n SpanKind,\n SpanStatusCode,\n trace,\n ValueType,\n} from \"@opentelemetry/api\";\nimport {\n hrTime,\n hrTimeDuration,\n hrTimeToMilliseconds,\n} from \"@opentelemetry/core\";\nimport {\n ATTR_ERROR_TYPE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_HTTP_REQUEST_METHOD_ORIGINAL,\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_NETWORK_PEER_ADDRESS,\n ATTR_NETWORK_PEER_PORT,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_FULL,\n ATTR_URL_PATH,\n ATTR_URL_QUERY,\n ATTR_URL_SCHEME,\n ATTR_USER_AGENT_ORIGINAL,\n METRIC_HTTP_CLIENT_REQUEST_DURATION,\n} from \"@opentelemetry/semantic-conventions\";\n\nimport {\n ListenerRecord,\n RequestBodyChunkReceivedMessage,\n RequestBodyChunkSentMessage,\n RequestBodySentMessage,\n RequestHeadersMessage,\n RequestMessage,\n RequestTrailersMessage,\n ResponseHeadersMessage,\n UndiciInstrumentationConfig,\n UndiciRequest,\n} from \"./types\";\nimport {\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n type DomainRule,\n} from \"@pingops/core\";\nimport { getGlobalConfig } from \"../../config-store\";\n\n// Constants\nconst DEFAULT_MAX_REQUEST_BODY_SIZE: number = 4 * 1024; // 4 KB\nconst DEFAULT_MAX_RESPONSE_BODY_SIZE: number = 4 * 1024; // 4 KB\n\n// Semantic attributes\nconst HTTP_REQUEST_BODY = \"http.request.body\";\nconst HTTP_RESPONSE_BODY = \"http.response.body\";\n\n/**\n * Extracts domain from URL\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n const match = url.match(/^(?:https?:\\/\\/)?([^/]+)/);\n return match ? match[1] : \"\";\n }\n}\n\n/**\n * Gets domain rule configuration for a given URL\n */\nfunction getDomainRule(\n url: string,\n domainAllowList?: DomainRule[]\n): DomainRule | undefined {\n if (!domainAllowList) {\n return undefined;\n }\n\n const domain = extractDomainFromUrl(url);\n for (const rule of domainAllowList) {\n if (\n domain === rule.domain ||\n domain.endsWith(`.${rule.domain}`) ||\n domain === rule.domain.slice(1)\n ) {\n return rule;\n }\n }\n return undefined;\n}\n\n/**\n * Determines if request body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureRequestBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from wrapHttp)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_REQUEST_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureRequestBody !== undefined) {\n return domainRule.captureRequestBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureRequestBody !== undefined) {\n return globalConfig.captureRequestBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Determines if response body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureResponseBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from wrapHttp)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_RESPONSE_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureResponseBody !== undefined) {\n return domainRule.captureResponseBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureResponseBody !== undefined) {\n return globalConfig.captureResponseBody;\n }\n\n // Default to false\n return false;\n}\n\ninterface InstrumentationRecord {\n span: Span;\n attributes: Attributes;\n startTime: HrTime;\n requestBodyChunks: Buffer[];\n responseBodyChunks: Buffer[];\n requestBodySize: number;\n responseBodySize: number;\n url?: string;\n}\n\nexport class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumentationConfig> {\n declare private _channelSubs: Array<ListenerRecord>;\n private _recordFromReq = new WeakMap<UndiciRequest, InstrumentationRecord>();\n\n declare private _httpClientDurationHistogram: Histogram;\n\n constructor(config: UndiciInstrumentationConfig = {}) {\n super(\"pingops-undici\", \"0.1.0\", config);\n }\n\n // No need to instrument files/modules\n protected override init() {\n return undefined;\n }\n\n override disable(): void {\n super.disable();\n this._channelSubs.forEach((sub) => sub.unsubscribe());\n this._channelSubs.length = 0;\n }\n\n override enable(): void {\n // \"enabled\" handling is currently a bit messy with InstrumentationBase.\n // If constructed with `{enabled: false}`, this `.enable()` is still called,\n // and `this.getConfig().enabled !== this.isEnabled()`, creating confusion.\n //\n // For now, this class will setup for instrumenting if `.enable()` is\n // called, but use `this.getConfig().enabled` to determine if\n // instrumentation should be generated. This covers the more likely common\n // case of config being given a construction time, rather than later via\n // `instance.enable()`, `.disable()`, or `.setConfig()` calls.\n super.enable();\n\n // This method is called by the super-class constructor before ours is\n // called. So we need to ensure the property is initalized.\n this._channelSubs = this._channelSubs || [];\n\n // Avoid to duplicate subscriptions\n if (this._channelSubs.length > 0) {\n return;\n }\n\n this.subscribeToChannel(\n \"undici:request:create\",\n this.onRequestCreated.bind(this)\n );\n this.subscribeToChannel(\n \"undici:client:sendHeaders\",\n this.onRequestHeaders.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:headers\",\n this.onResponseHeaders.bind(this)\n );\n this.subscribeToChannel(\"undici:request:trailers\", this.onDone.bind(this));\n this.subscribeToChannel(\"undici:request:error\", this.onError.bind(this));\n this.subscribeToChannel(\n \"undici:request:bodyChunkSent\",\n this.onBodyChunkSent.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:bodySent\",\n this.onBodySent.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:bodyChunkReceived\",\n this.onBodyChunkReceived.bind(this)\n );\n }\n\n protected override _updateMetricInstruments() {\n this._httpClientDurationHistogram = this.meter.createHistogram(\n METRIC_HTTP_CLIENT_REQUEST_DURATION,\n {\n description: \"Measures the duration of outbound HTTP requests.\",\n unit: \"s\",\n valueType: ValueType.DOUBLE,\n advice: {\n explicitBucketBoundaries: [\n 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,\n 7.5, 10,\n ],\n },\n }\n );\n }\n\n private subscribeToChannel(\n diagnosticChannel: string,\n onMessage: (message: any, name: string | symbol) => void\n ) {\n // `diagnostics_channel` had a ref counting bug until v18.19.0.\n // https://github.com/nodejs/node/pull/47520\n const [major, minor] = process.version\n .replace(\"v\", \"\")\n .split(\".\")\n .map((n) => Number(n));\n const useNewSubscribe = major > 18 || (major === 18 && minor >= 19);\n\n let unsubscribe: () => void;\n if (useNewSubscribe) {\n diagch.subscribe?.(diagnosticChannel, onMessage);\n unsubscribe = () => diagch.unsubscribe?.(diagnosticChannel, onMessage);\n } else {\n const channel = diagch.channel(diagnosticChannel);\n channel.subscribe(onMessage);\n unsubscribe = () => channel.unsubscribe(onMessage);\n }\n\n this._channelSubs.push({\n name: diagnosticChannel,\n unsubscribe,\n });\n }\n\n private parseRequestHeaders(request: UndiciRequest) {\n const result = new Map<string, string | string[]>();\n\n if (Array.isArray(request.headers)) {\n // headers are an array [k1, v2, k2, v2] (undici v6+)\n // values could be string or a string[] for multiple values\n for (let i = 0; i < request.headers.length; i += 2) {\n const key = request.headers[i];\n const value = request.headers[i + 1];\n\n // Key should always be a string, but the types don't know that, and let's be safe\n if (typeof key === \"string\") {\n result.set(key.toLowerCase(), value);\n }\n }\n } else if (typeof request.headers === \"string\") {\n // headers are a raw string (undici v5)\n // headers could be repeated in several lines for multiple values\n const headers = request.headers.split(\"\\r\\n\");\n for (const line of headers) {\n if (!line) {\n continue;\n }\n const colonIndex = line.indexOf(\":\");\n if (colonIndex === -1) {\n // Invalid header? Probably this can't happen, but again let's be safe.\n continue;\n }\n const key = line.substring(0, colonIndex).toLowerCase();\n const value = line.substring(colonIndex + 1).trim();\n const allValues = result.get(key);\n\n if (allValues && Array.isArray(allValues)) {\n allValues.push(value);\n } else if (allValues) {\n result.set(key, [allValues, value]);\n } else {\n result.set(key, value);\n }\n }\n }\n return result;\n }\n\n // This is the 1st message we receive for each request (fired after request creation). Here we will\n // create the span and populate some atttributes, then link the span to the request for further\n // span processing\n private onRequestCreated({ request }: RequestMessage): void {\n // Ignore if:\n // - instrumentation is disabled\n // - ignored by config\n // - method is 'CONNECT'\n const config = this.getConfig();\n const enabled = config.enabled !== false;\n const shouldIgnoreReq = safeExecuteInTheMiddle(\n () =>\n !enabled ||\n request.method === \"CONNECT\" ||\n config.ignoreRequestHook?.(request),\n (e) => e && this._diag.error(\"caught ignoreRequestHook error: \", e),\n true\n );\n\n if (shouldIgnoreReq) {\n return;\n }\n\n const startTime = hrTime();\n let requestUrl;\n try {\n requestUrl = new URL(request.path, request.origin);\n } catch (err) {\n this._diag.warn(\"could not determine url.full:\", err);\n // Skip instrumenting this request.\n return;\n }\n const urlScheme = requestUrl.protocol.replace(\":\", \"\");\n const requestMethod = this.getRequestMethod(request.method);\n const attributes: Attributes = {\n [ATTR_HTTP_REQUEST_METHOD]: requestMethod,\n [ATTR_HTTP_REQUEST_METHOD_ORIGINAL]: request.method,\n [ATTR_URL_FULL]: requestUrl.toString(),\n [ATTR_URL_PATH]: requestUrl.pathname,\n [ATTR_URL_QUERY]: requestUrl.search,\n [ATTR_URL_SCHEME]: urlScheme,\n };\n\n const schemePorts: Record<string, string> = { https: \"443\", http: \"80\" };\n const serverAddress = requestUrl.hostname;\n const serverPort = requestUrl.port || schemePorts[urlScheme];\n\n attributes[ATTR_SERVER_ADDRESS] = serverAddress;\n if (serverPort && !isNaN(Number(serverPort))) {\n attributes[ATTR_SERVER_PORT] = Number(serverPort);\n }\n\n // Get user agent from headers\n const headersMap = this.parseRequestHeaders(request);\n const userAgentValues = headersMap.get(\"user-agent\");\n\n if (userAgentValues) {\n // NOTE: having multiple user agents is not expected so\n // we're going to take last one like `curl` does\n // ref: https://curl.se/docs/manpage.html#-A\n const userAgent = Array.isArray(userAgentValues)\n ? userAgentValues[userAgentValues.length - 1]\n : userAgentValues;\n attributes[ATTR_USER_AGENT_ORIGINAL] = userAgent;\n }\n\n // Get attributes from the hook if present\n const hookAttributes = safeExecuteInTheMiddle(\n () => config.startSpanHook?.(request),\n (e) => e && this._diag.error(\"caught startSpanHook error: \", e),\n true\n );\n if (hookAttributes) {\n Object.entries(hookAttributes).forEach(([key, val]) => {\n attributes[key] = val;\n });\n }\n\n // Check if parent span is required via config and:\n // - if a parent is required but not present, we use a `NoopSpan` to still\n // propagate context without recording it.\n // - create a span otherwise\n const activeCtx = context.active();\n const currentSpan = trace.getSpan(activeCtx);\n let span: Span;\n\n if (\n config.requireParentforSpans &&\n (!currentSpan || !trace.isSpanContextValid(currentSpan.spanContext()))\n ) {\n span = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n } else {\n span = this.tracer.startSpan(\n requestMethod === \"_OTHER\" ? \"HTTP\" : requestMethod,\n {\n kind: SpanKind.CLIENT,\n attributes: attributes,\n },\n activeCtx\n );\n }\n\n // Execute the request hook if defined\n safeExecuteInTheMiddle(\n () => config.requestHook?.(span, request),\n (e) => e && this._diag.error(\"caught requestHook error: \", e),\n true\n );\n\n // Context propagation goes last so no hook can tamper\n // the propagation headers\n const requestContext = trace.setSpan(context.active(), span);\n const addedHeaders: Record<string, string> = {};\n propagation.inject(requestContext, addedHeaders);\n\n const headerEntries = Object.entries(addedHeaders);\n\n for (let i = 0; i < headerEntries.length; i++) {\n const [k, v] = headerEntries[i];\n\n if (typeof request.addHeader === \"function\") {\n request.addHeader(k, v);\n } else if (typeof request.headers === \"string\") {\n request.headers += `${k}: ${v}\\r\\n`;\n } else if (Array.isArray(request.headers)) {\n // undici@6.11.0 accidentally, briefly removed `request.addHeader()`.\n request.headers.push(k, v);\n }\n }\n this._recordFromReq.set(request, {\n span,\n attributes,\n startTime,\n requestBodyChunks: [],\n responseBodyChunks: [],\n requestBodySize: 0,\n responseBodySize: 0,\n url: requestUrl.toString(),\n });\n }\n\n // This is the 2nd message we receive for each request. It is fired when connection with\n // the remote is established and about to send the first byte. Here we do have info about the\n // remote address and port so we can populate some `network.*` attributes into the span\n private onRequestHeaders({ request, socket }: RequestHeadersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span } = record;\n const { remoteAddress, remotePort } = socket;\n const spanAttributes: Attributes = {\n [ATTR_NETWORK_PEER_ADDRESS]: remoteAddress,\n [ATTR_NETWORK_PEER_PORT]: remotePort,\n };\n\n const headersMap = this.parseRequestHeaders(request);\n\n for (const [name, value] of headersMap.entries()) {\n const attrValue = Array.isArray(value) ? value.join(\", \") : value;\n spanAttributes[`http.request.header.${name}`] = attrValue;\n }\n\n span.setAttributes(spanAttributes);\n }\n\n // This is the 3rd message we get for each request and it's fired when the server\n // headers are received, body may not be accessible yet.\n // From the response headers we can set the status and content length\n private onResponseHeaders({\n request,\n response,\n }: ResponseHeadersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes } = record;\n const spanAttributes: Attributes = {\n [ATTR_HTTP_RESPONSE_STATUS_CODE]: response.statusCode,\n };\n\n const config = this.getConfig();\n\n // Execute the response hook if defined\n safeExecuteInTheMiddle(\n () => config.responseHook?.(span, { request, response }),\n (e) => e && this._diag.error(\"caught responseHook error: \", e),\n true\n );\n\n for (let idx = 0; idx < response.headers.length; idx = idx + 2) {\n const name = response.headers[idx].toString().toLowerCase();\n const value = response.headers[idx + 1];\n\n spanAttributes[`http.response.header.${name}`] = value.toString();\n\n if (name === \"content-length\") {\n const contentLength = Number(value.toString());\n if (!isNaN(contentLength)) {\n spanAttributes[\"http.response.header.content-length\"] = contentLength;\n }\n }\n }\n\n span.setAttributes(spanAttributes);\n span.setStatus({\n code:\n response.statusCode >= 400\n ? SpanStatusCode.ERROR\n : SpanStatusCode.UNSET,\n });\n record.attributes = Object.assign(attributes, spanAttributes);\n }\n\n // This is the last event we receive if the request went without any errors\n private onDone({ request }: RequestTrailersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes, startTime } = record;\n\n // Check if body capture is enabled before setting response body attribute\n if (shouldCaptureResponseBody(record.url)) {\n // Set response body attribute if we have chunks and haven't exceeded max size\n if (\n record.responseBodyChunks.length > 0 &&\n record.responseBodySize !== Infinity\n ) {\n try {\n const responseBody = Buffer.concat(\n record.responseBodyChunks\n ).toString(\"utf-8\");\n if (responseBody) {\n span.setAttribute(HTTP_RESPONSE_BODY, responseBody);\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing response body:\", e);\n }\n }\n }\n\n // End the span\n span.end();\n this._recordFromReq.delete(request);\n\n // Record metrics\n this.recordRequestDuration(attributes, startTime);\n }\n\n // This is the event we get when something is wrong in the request like\n // - invalid options when calling `fetch` global API or any undici method for request\n // - connectivity errors such as unreachable host\n // - requests aborted through an `AbortController.signal`\n // NOTE: server errors are considered valid responses and it's the lib consumer\n // who should deal with that.\n private onError({ request, error }: any): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes, startTime } = record;\n\n // Check if body capture is enabled before setting request body attribute\n // (in case body was sent before error occurred)\n if (shouldCaptureRequestBody(record.url)) {\n // Set request body attribute if we have chunks and haven't exceeded max size\n if (\n record.requestBodyChunks.length > 0 &&\n record.requestBodySize !== Infinity\n ) {\n try {\n const requestBody = Buffer.concat(record.requestBodyChunks).toString(\n \"utf-8\"\n );\n if (requestBody) {\n span.setAttribute(HTTP_REQUEST_BODY, requestBody);\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing request body:\", e);\n }\n }\n }\n\n // NOTE: in `undici@6.3.0` when request aborted the error type changes from\n // a custom error (`RequestAbortedError`) to a built-in `DOMException` carrying\n // some differences:\n // - `code` is from DOMEXception (ABORT_ERR: 20)\n // - `message` changes\n // - stacktrace is smaller and contains node internal frames\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n span.end();\n this._recordFromReq.delete(request);\n\n // Record metrics (with the error)\n attributes[ATTR_ERROR_TYPE] = error.message;\n this.recordRequestDuration(attributes, startTime);\n }\n\n private onBodyChunkSent({\n request,\n chunk,\n }: RequestBodyChunkSentMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(record.url)) {\n return;\n }\n\n const config = this.getConfig();\n const maxRequestBodySize =\n config.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE;\n\n // Only accumulate chunks if we haven't exceeded the max size\n if (record.requestBodySize + chunk.length <= maxRequestBodySize) {\n record.requestBodyChunks.push(chunk);\n record.requestBodySize += chunk.length;\n } else if (record.requestBodyChunks.length === 0) {\n // If first chunk exceeds max size, don't track at all\n record.requestBodySize = Infinity; // Mark as exceeded\n }\n }\n\n private onBodySent({ request }: RequestBodySentMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(record.url)) {\n // Clear request body chunks to free memory\n record.requestBodyChunks = [];\n return;\n }\n\n // Set request body attribute if we have chunks and haven't exceeded max size\n if (\n record.requestBodyChunks.length > 0 &&\n record.requestBodySize !== Infinity\n ) {\n try {\n const requestBody = Buffer.concat(record.requestBodyChunks).toString(\n \"utf-8\"\n );\n if (requestBody) {\n record.span.setAttribute(HTTP_REQUEST_BODY, requestBody);\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing request body:\", e);\n }\n }\n\n // Clear request body chunks to free memory\n record.requestBodyChunks = [];\n }\n\n private onBodyChunkReceived({\n request,\n chunk,\n }: RequestBodyChunkReceivedMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureResponseBody(record.url)) {\n return;\n }\n\n const config = this.getConfig();\n const maxResponseBodySize =\n config.maxResponseBodySize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE;\n\n // Only accumulate chunks if we haven't exceeded the max size\n if (record.responseBodySize + chunk.length <= maxResponseBodySize) {\n record.responseBodyChunks.push(chunk);\n record.responseBodySize += chunk.length;\n } else if (record.responseBodyChunks.length === 0) {\n // If first chunk exceeds max size, don't track at all\n record.responseBodySize = Infinity; // Mark as exceeded\n }\n }\n\n private recordRequestDuration(attributes: Attributes, startTime: HrTime) {\n // Time to record metrics\n const metricsAttributes: Attributes = {};\n // Get the attribs already in span attributes\n const keysToCopy = [\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_SCHEME,\n ATTR_ERROR_TYPE,\n ];\n keysToCopy.forEach((key) => {\n if (key in attributes) {\n metricsAttributes[key] = attributes[key];\n }\n });\n\n // Take the duration and record it\n const durationSeconds =\n hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime())) / 1000;\n this._httpClientDurationHistogram.record(\n durationSeconds,\n metricsAttributes\n );\n }\n\n private getRequestMethod(original: string): string {\n const knownMethods = {\n CONNECT: true,\n OPTIONS: true,\n HEAD: true,\n GET: true,\n POST: true,\n PUT: true,\n PATCH: true,\n DELETE: true,\n TRACE: true,\n };\n\n if (original.toUpperCase() in knownMethods) {\n return original.toUpperCase();\n }\n\n return \"_OTHER\";\n }\n}\n","/**\n * Undici instrumentation for OpenTelemetry\n */\n\nimport { UndiciInstrumentation } from \"./pingops-undici\";\nimport { context } from \"@opentelemetry/api\";\nimport { PINGOPS_HTTP_ENABLED } from \"@pingops/core\";\n\n/**\n * Creates an Undici instrumentation instance\n *\n * @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled\n * @returns UndiciInstrumentation instance\n */\nexport function createUndiciInstrumentation(\n isGlobalInstrumentationEnabled: () => boolean\n): UndiciInstrumentation {\n return new UndiciInstrumentation({\n enabled: true,\n ignoreRequestHook: () => {\n // If global instrumentation is enabled, instrument all requests\n if (isGlobalInstrumentationEnabled()) {\n return false;\n }\n // If global instrumentation is NOT enabled, only instrument when PINGOPS_HTTP_ENABLED is true\n return context.active().getValue(PINGOPS_HTTP_ENABLED) !== true;\n },\n });\n}\n","/**\n * Instrumentation setup for HTTP, fetch, undici, and GenAI\n */\n\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport { createHttpInstrumentation } from \"./http/http\";\nimport { createUndiciInstrumentation } from \"./undici/undici\";\n\nlet installed = false;\n\n/**\n * Registers instrumentations for Node.js environment.\n * This function is idempotent and can be called multiple times safely.\n *\n * Instrumentation behavior:\n * - If global instrumentation is enabled: all HTTP requests are instrumented\n * - If global instrumentation is NOT enabled: only requests within wrapHttp blocks are instrumented\n *\n * @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled\n * @returns Array of Instrumentation instances\n */\nexport function getInstrumentations(\n isGlobalInstrumentationEnabled: () => boolean\n): Instrumentation[] {\n if (installed) {\n return [];\n }\n\n installed = true;\n return [\n createHttpInstrumentation(isGlobalInstrumentationEnabled),\n createUndiciInstrumentation(isGlobalInstrumentationEnabled),\n ];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["logger","OTLPTraceExporter","SimpleSpanProcessor","BatchSpanProcessor","url","trace","DEFAULT_MAX_REQUEST_BODY_SIZE","DEFAULT_MAX_RESPONSE_BODY_SIZE","extractDomainFromUrl","url","getDomainRule","shouldCaptureRequestBody","context","PINGOPS_CAPTURE_REQUEST_BODY","globalConfig","shouldCaptureResponseBody","PINGOPS_CAPTURE_RESPONSE_BODY","HTTP_RESPONSE_CONTENT_ENCODING","HttpInstrumentation","ClientRequest","IncomingMessage","globalConfig","URL","url","context","PINGOPS_CAPTURE_REQUEST_BODY","globalConfig","PINGOPS_CAPTURE_RESPONSE_BODY","InstrumentationBase","METRIC_HTTP_CLIENT_REQUEST_DURATION","ValueType","diagch","ATTR_HTTP_REQUEST_METHOD","ATTR_HTTP_REQUEST_METHOD_ORIGINAL","ATTR_URL_FULL","ATTR_URL_PATH","ATTR_URL_QUERY","ATTR_URL_SCHEME","ATTR_SERVER_ADDRESS","ATTR_SERVER_PORT","ATTR_USER_AGENT_ORIGINAL","trace","INVALID_SPAN_CONTEXT","SpanKind","ATTR_NETWORK_PEER_ADDRESS","ATTR_NETWORK_PEER_PORT","ATTR_HTTP_RESPONSE_STATUS_CODE","SpanStatusCode","HTTP_RESPONSE_CONTENT_ENCODING","ATTR_ERROR_TYPE","globalConfig"],"sources":["../src/config-store.ts","../src/span-processor.ts","../src/tracer-provider.ts","../src/instrumentations/http/pingops-http.ts","../src/instrumentations/http/http.ts","../src/instrumentations/undici/pingops-undici.ts","../src/instrumentations/undici/undici.ts","../src/instrumentations/index.ts"],"sourcesContent":["/**\n * Global configuration store for PingOps processor\n * Allows instrumentations to access processor configuration without direct coupling\n */\n\nimport type { DomainRule } from \"@pingops/core\";\n\ninterface GlobalConfig {\n captureRequestBody?: boolean;\n captureResponseBody?: boolean;\n domainAllowList?: DomainRule[];\n maxRequestBodySize?: number;\n maxResponseBodySize?: number;\n}\n\nlet globalConfig: GlobalConfig | null = null;\n\n/**\n * Sets the global processor configuration\n * @param config - Configuration to store\n */\nexport function setGlobalConfig(config: GlobalConfig): void {\n globalConfig = config;\n}\n\n/**\n * Gets the global processor configuration\n * @returns The stored configuration or null if not set\n */\nexport function getGlobalConfig(): GlobalConfig | null {\n return globalConfig;\n}\n\n/**\n * Clears the global configuration (useful for testing)\n */\nexport function clearGlobalConfig(): void {\n globalConfig = null;\n}\n","/**\n * PingopsSpanProcessor - OpenTelemetry SpanProcessor implementation\n * Observes finished spans and sends eligible ones to PingOps backend\n *\n * This processor provides:\n * - Automatic filtering of spans (CLIENT spans with HTTP/GenAI attributes only)\n * - Domain and header filtering based on configuration\n * - Batched or immediate export modes using OTLP exporters\n * - Fire-and-forget transport (never blocks application)\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { PingopsSpanProcessor } from '@pingops/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new PingopsSpanProcessor({\n * apiKey: 'your-api-key',\n * baseUrl: 'https://api.pingops.com',\n * serviceName: 'my-service',\n * exportMode: 'batched', // or 'immediate'\n * domainAllowList: [\n * { domain: 'api.example.com' }\n * ]\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n Span,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n BatchSpanProcessor,\n SimpleSpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport type { Context, Attributes } from \"@opentelemetry/api\";\nimport {\n isSpanEligible,\n shouldCaptureSpan,\n type DomainRule,\n type HeaderRedactionConfig,\n createLogger,\n getPropagatedAttributesFromContext,\n extractSpanPayload,\n} from \"@pingops/core\";\nimport type { PingopsProcessorConfig } from \"./config\";\nimport { setGlobalConfig } from \"./config-store\";\n\nconst logger = createLogger(\"[PingOps Processor]\");\n\n/**\n * Creates a filtered span wrapper that applies header filtering to attributes\n *\n * This wrapper applies both domain-specific and global header filtering:\n * - Uses domain allow list to determine domain-specific header rules\n * - Applies global header allow/deny lists\n * - Filters headers from http.request.header and http.response.header attributes\n *\n * Uses a Proxy to automatically forward all properties and methods to the original span,\n * except for 'attributes' which returns the filtered version. This approach is future-proof\n * and will work with any new methods or properties added to ReadableSpan.\n *\n * This allows us to filter headers before the span is serialized by OTLP exporter\n */\nfunction createFilteredSpan(\n span: ReadableSpan,\n domainAllowList?: DomainRule[],\n globalHeadersAllowList?: string[],\n globalHeadersDenyList?: string[],\n globalCaptureRequestBody?: boolean,\n globalCaptureResponseBody?: boolean,\n headerRedaction?: HeaderRedactionConfig\n): ReadableSpan {\n // Use extractSpanPayload to get filtered attributes\n // This handles both domain-specific header rules and global header filtering\n // as well as body capture filtering and header redaction\n const payload = extractSpanPayload(\n span,\n domainAllowList,\n globalHeadersAllowList,\n globalHeadersDenyList,\n globalCaptureRequestBody,\n globalCaptureResponseBody,\n headerRedaction\n );\n const filteredAttributes = (payload?.attributes ??\n span.attributes) as Attributes;\n logger.debug(\"Payload\", { payload });\n\n // Create a Proxy that intercepts 'attributes' access and forwards everything else\n return new Proxy(span, {\n get(target, prop) {\n // Intercept 'attributes' to return filtered version\n if (prop === \"attributes\") {\n return filteredAttributes;\n }\n // Forward all other property/method access to the original span\n const value = (target as ReadableSpan & Record<string, unknown>)[\n prop as string\n ];\n // If it's a function, bind it to the original target to preserve 'this' context\n if (typeof value === \"function\") {\n return (value as (...args: unknown[]) => unknown).bind(target);\n }\n return value;\n },\n });\n}\n\n/**\n * OpenTelemetry span processor for sending spans to PingOps backend.\n *\n * This processor wraps OpenTelemetry's built-in processors (BatchSpanProcessor or SimpleSpanProcessor)\n * and applies filtering before passing spans to the OTLP exporter.\n */\nexport class PingopsSpanProcessor implements SpanProcessor {\n private processor: SpanProcessor;\n private config: {\n debug: boolean;\n headersAllowList?: string[];\n headersDenyList?: string[];\n domainAllowList?: DomainRule[];\n domainDenyList?: DomainRule[];\n captureRequestBody?: boolean;\n captureResponseBody?: boolean;\n headerRedaction?: HeaderRedactionConfig;\n };\n\n /**\n * Creates a new PingopsSpanProcessor instance.\n *\n * @param config - Configuration parameters for the processor\n */\n constructor(config: PingopsProcessorConfig) {\n const exportMode = config.exportMode ?? \"batched\";\n\n // Get API key from config or environment\n const apiKey = config.apiKey || process.env.PINGOPS_API_KEY || \"\";\n\n // Create OTLP exporter pointing to PingOps backend\n const exporter = new OTLPTraceExporter({\n url: `${config.baseUrl}/v1/traces`,\n headers: {\n Authorization: apiKey ? `Bearer ${apiKey}` : \"\",\n \"Content-Type\": \"application/json\",\n },\n timeoutMillis: 5000,\n });\n\n // Create underlying processor based on export mode\n if (exportMode === \"immediate\") {\n this.processor = new SimpleSpanProcessor(exporter);\n } else {\n this.processor = new BatchSpanProcessor(exporter, {\n maxExportBatchSize: config.batchSize ?? 50,\n scheduledDelayMillis: config.batchTimeout ?? 5000,\n });\n }\n\n this.config = {\n debug: config.debug ?? false,\n headersAllowList: config.headersAllowList,\n headersDenyList: config.headersDenyList,\n domainAllowList: config.domainAllowList,\n domainDenyList: config.domainDenyList,\n captureRequestBody: config.captureRequestBody,\n captureResponseBody: config.captureResponseBody,\n headerRedaction: config.headerRedaction,\n };\n\n // Register global config for instrumentations to access\n setGlobalConfig({\n captureRequestBody: config.captureRequestBody,\n captureResponseBody: config.captureResponseBody,\n domainAllowList: config.domainAllowList,\n maxRequestBodySize: config.maxRequestBodySize,\n maxResponseBodySize: config.maxResponseBodySize,\n });\n\n logger.info(\"Initialized PingopsSpanProcessor\", {\n baseUrl: config.baseUrl,\n exportMode,\n batchSize: config.batchSize,\n batchTimeout: config.batchTimeout,\n hasDomainAllowList:\n !!config.domainAllowList && config.domainAllowList.length > 0,\n hasDomainDenyList:\n !!config.domainDenyList && config.domainDenyList.length > 0,\n hasHeadersAllowList:\n !!config.headersAllowList && config.headersAllowList.length > 0,\n hasHeadersDenyList:\n !!config.headersDenyList && config.headersDenyList.length > 0,\n });\n }\n\n /**\n * Called when a span starts - extracts parent attributes from context and adds them to the span\n */\n onStart(span: Span, parentContext: Context): void {\n const spanContext = span.spanContext();\n logger.debug(\"Span started\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n });\n\n // Extract propagated attributes from context and set them on the span\n const propagatedAttributes =\n getPropagatedAttributesFromContext(parentContext);\n if (Object.keys(propagatedAttributes).length > 0) {\n for (const [key, value] of Object.entries(propagatedAttributes)) {\n // Type guard: value must be string or string[] for OpenTelemetry attributes\n if (typeof value === \"string\" || Array.isArray(value)) {\n span.setAttribute(key, value);\n }\n }\n logger.debug(\"Set propagated attributes on span\", {\n spanName: span.name,\n attributeKeys: Object.keys(propagatedAttributes),\n });\n }\n\n this.processor.onStart(span, parentContext);\n }\n /**\n * Called when a span ends. Filters the span and passes it to the underlying processor if eligible.\n *\n * This method:\n * 1. Checks if the span is eligible (CLIENT + HTTP/GenAI attributes)\n * 2. Applies domain filtering (determines if span should be exported)\n * 3. Applies header filtering via FilteredSpan wrapper (domain-specific and global rules)\n * 4. If eligible, passes filtered span to underlying OTLP processor for export\n */\n onEnd(span: ReadableSpan): void {\n const spanContext = span.spanContext();\n logger.debug(\"Span ended, processing\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n spanKind: span.kind,\n });\n\n try {\n // Step 1: Check if span is eligible (CLIENT + HTTP/GenAI attributes)\n if (!isSpanEligible(span)) {\n logger.debug(\"Span not eligible, skipping\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n reason: \"not CLIENT or missing HTTP/GenAI attributes\",\n });\n return;\n }\n\n // Step 2: Extract URL for domain filtering\n const attributes = span.attributes;\n const url =\n (attributes[\"http.url\"] as string) ||\n (attributes[\"url.full\"] as string) ||\n (attributes[\"server.address\"]\n ? `https://${String(attributes[\"server.address\"])}`\n : \"\");\n\n logger.debug(\"Extracted URL for domain filtering\", {\n spanName: span.name,\n url,\n hasHttpUrl: !!attributes[\"http.url\"],\n hasUrlFull: !!attributes[\"url.full\"],\n hasServerAddress: !!attributes[\"server.address\"],\n });\n\n // Step 3: Apply domain filtering\n if (url) {\n const shouldCapture = shouldCaptureSpan(\n url,\n this.config.domainAllowList,\n this.config.domainDenyList\n );\n\n if (!shouldCapture) {\n logger.info(\"Span filtered out by domain rules\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n url,\n });\n return;\n }\n } else {\n logger.debug(\"No URL found for domain filtering, proceeding\", {\n spanName: span.name,\n });\n }\n\n // Step 4: Apply filtering (header filtering with domain-specific rules) by wrapping the span\n const filteredSpan = createFilteredSpan(\n span,\n this.config.domainAllowList,\n this.config.headersAllowList,\n this.config.headersDenyList,\n this.config.captureRequestBody,\n this.config.captureResponseBody,\n this.config.headerRedaction\n );\n\n // Step 5: Span passed all filters, pass filtered span to underlying processor for export\n this.processor.onEnd(filteredSpan);\n\n logger.info(\"Span passed all filters and queued for export\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n url,\n hasHeaderFiltering: !!(\n this.config.headersAllowList || this.config.headersDenyList\n ),\n });\n } catch (error) {\n // Defensive error handling - never crash the app\n logger.error(\"Error processing span\", {\n spanName: span.name,\n spanId: spanContext.spanId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n /**\n * Forces an immediate flush of all pending spans.\n *\n * @returns Promise that resolves when all pending operations are complete\n */\n public async forceFlush(): Promise<void> {\n logger.info(\"Force flushing spans\");\n try {\n await this.processor.forceFlush();\n logger.info(\"Force flush complete\");\n } catch (error) {\n logger.error(\"Error during force flush\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n */\n public async shutdown(): Promise<void> {\n logger.info(\"Shutting down processor\");\n try {\n await this.processor.shutdown();\n logger.info(\"Processor shutdown complete\");\n } catch (error) {\n logger.error(\"Error during processor shutdown\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n","/**\n * Tracer Provider with global state and isolated TracerProvider architecture\n *\n * This module provides an isolated TracerProvider that shares the same\n * span processors (like PingopsSpanProcessor) with the main OpenTelemetry SDK.\n * This ensures manual spans created via startSpan are properly processed.\n *\n * Architecture follows Langfuse's pattern with global state management.\n */\n\nimport type { TracerProvider } from \"@opentelemetry/api\";\nimport { trace } from \"@opentelemetry/api\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport { createLogger } from \"@pingops/core\";\n\n/**\n * Global symbol for PingOps state\n */\nconst PINGOPS_GLOBAL_SYMBOL = Symbol.for(\"pingops\");\n\n/**\n * Logger instance for tracer provider\n */\nconst logger = createLogger(\"[PingOps TracerProvider]\");\n\n/**\n * Global state interface\n */\ntype PingopsGlobalState = {\n isolatedTracerProvider: TracerProvider | null;\n};\n\n/**\n * Creates initial global state\n */\nfunction createState(): PingopsGlobalState {\n return {\n isolatedTracerProvider: null,\n };\n}\n\n/**\n * Interface for globalThis with PingOps state\n */\ninterface GlobalThis {\n [PINGOPS_GLOBAL_SYMBOL]?: PingopsGlobalState;\n}\n\n/**\n * Gets the global state, creating it if it doesn't exist\n */\nfunction getGlobalState(): PingopsGlobalState {\n const initialState = createState();\n\n try {\n const g = globalThis as typeof globalThis & GlobalThis;\n\n if (typeof g !== \"object\" || g === null) {\n // Fallback if globalThis is not available\n logger.warn(\"globalThis is not available, using fallback state\");\n return initialState;\n }\n\n if (!g[PINGOPS_GLOBAL_SYMBOL]) {\n logger.debug(\"Creating new global state\");\n Object.defineProperty(g, PINGOPS_GLOBAL_SYMBOL, {\n value: initialState,\n writable: false, // lock the slot (not the contents)\n configurable: false,\n enumerable: false,\n });\n } else {\n logger.debug(\"Retrieved existing global state\");\n }\n\n return g[PINGOPS_GLOBAL_SYMBOL]!;\n } catch (err) {\n logger.error(\n \"Failed to access global state:\",\n err instanceof Error ? err.message : String(err)\n );\n // Fallback on error\n return initialState;\n }\n}\n\n/**\n * Sets an isolated TracerProvider for PingOps tracing operations.\n *\n * This allows PingOps to use its own TracerProvider instance, separate from\n * the global OpenTelemetry TracerProvider. This is useful for avoiding conflicts\n * with other OpenTelemetry instrumentation in the application.\n *\n * @param provider - The TracerProvider instance to use, or null to clear the isolated provider\n * @public\n */\nexport function setPingopsTracerProvider(\n provider: TracerProvider | null\n): void {\n const state = getGlobalState();\n const hadProvider = state.isolatedTracerProvider !== null;\n\n state.isolatedTracerProvider = provider;\n\n if (provider) {\n logger.info(\"Set isolated TracerProvider\", {\n hadPrevious: hadProvider,\n providerType: provider.constructor.name,\n });\n } else {\n logger.info(\"Cleared isolated TracerProvider\", {\n hadPrevious: hadProvider,\n });\n }\n}\n\n/**\n * Gets the TracerProvider for PingOps tracing operations.\n *\n * Returns the isolated TracerProvider if one has been set via setPingopsTracerProvider(),\n * otherwise falls back to the global OpenTelemetry TracerProvider.\n *\n * @returns The TracerProvider instance to use for PingOps tracing\n * @public\n */\nexport function getPingopsTracerProvider(): TracerProvider {\n const { isolatedTracerProvider } = getGlobalState();\n\n if (isolatedTracerProvider) {\n logger.debug(\"Using isolated TracerProvider\", {\n providerType: isolatedTracerProvider.constructor.name,\n });\n return isolatedTracerProvider;\n }\n\n const globalProvider = trace.getTracerProvider();\n logger.debug(\"Using global TracerProvider\", {\n providerType: globalProvider.constructor.name,\n });\n return globalProvider;\n}\n\n/**\n * Initializes the isolated TracerProvider with the given span processors\n *\n * This creates a separate TracerProvider that shares the same span processors\n * (like PingopsSpanProcessor) with the main SDK. This ensures manual spans\n * are processed correctly.\n *\n * @param spanProcessors - Array of span processors to use (e.g., PingopsSpanProcessor)\n * @param serviceName - Service name for resource attributes\n * @deprecated Use setPingopsTracerProvider instead\n */\nexport function initializeTracerProvider(\n spanProcessors: SpanProcessor[],\n serviceName: string\n): void {\n logger.info(\"Initializing TracerProvider\", {\n serviceName,\n spanProcessorCount: spanProcessors.length,\n });\n\n // Create resource with service name\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n });\n logger.debug(\"Created resource\", { serviceName });\n\n // In version 2.2.0, span processors are passed in the constructor\n const tracerProvider = new NodeTracerProvider({\n resource,\n spanProcessors,\n });\n logger.debug(\"Created NodeTracerProvider\", {\n spanProcessorCount: spanProcessors.length,\n });\n\n // Register the provider globally\n tracerProvider.register();\n logger.info(\"Registered TracerProvider globally\");\n\n // Set it in global state\n setPingopsTracerProvider(tracerProvider);\n logger.info(\"TracerProvider initialization complete\");\n}\n\n/**\n * Gets the isolated TracerProvider instance\n *\n * @returns The TracerProvider instance, or null if not initialized\n * @deprecated Use getPingopsTracerProvider instead\n */\nexport function getTracerProvider(): NodeTracerProvider | null {\n const provider = getPingopsTracerProvider();\n return provider instanceof NodeTracerProvider ? provider : null;\n}\n\n/**\n * Shuts down the TracerProvider and flushes remaining spans\n */\nexport async function shutdownTracerProvider(): Promise<void> {\n logger.info(\"Shutting down TracerProvider\");\n const provider = getPingopsTracerProvider();\n\n // Check if provider has shutdown method (NodeTracerProvider and compatible providers)\n const providerWithShutdown = provider as TracerProvider & {\n shutdown?: () => Promise<void>;\n };\n if (\n providerWithShutdown &&\n typeof providerWithShutdown.shutdown === \"function\"\n ) {\n logger.debug(\"Calling provider.shutdown()\");\n try {\n await providerWithShutdown.shutdown();\n logger.info(\"TracerProvider shutdown complete\");\n } catch (error) {\n logger.error(\n \"Error during TracerProvider shutdown:\",\n error instanceof Error ? error.message : String(error)\n );\n throw error;\n }\n } else {\n logger.warn(\"TracerProvider does not have shutdown method, skipping\");\n }\n\n setPingopsTracerProvider(null);\n logger.info(\"TracerProvider shutdown finished\");\n}\n","/**\n * Pingops HTTP instrumentation that extends HttpInstrumentation\n * with request/response body capture\n */\n\nimport { ClientRequest, IncomingMessage, ServerResponse } from \"http\";\nimport { Span, context } from \"@opentelemetry/api\";\nimport {\n HttpInstrumentation,\n HttpInstrumentationConfig,\n HttpRequestCustomAttributeFunction,\n HttpResponseCustomAttributeFunction,\n} from \"@opentelemetry/instrumentation-http\";\nimport {\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n bufferToBodyString,\n HTTP_RESPONSE_CONTENT_ENCODING,\n isCompressedContentEncoding,\n} from \"@pingops/core\";\nimport { getGlobalConfig } from \"../../config-store\";\nimport type { DomainRule } from \"@pingops/core\";\n\n// Constants\nconst DEFAULT_MAX_REQUEST_BODY_SIZE: number = 4 * 1024; // 4 KB\nconst DEFAULT_MAX_RESPONSE_BODY_SIZE: number = 4 * 1024; // 4 KB\n\n// Semantic attributes\nexport const PingopsSemanticAttributes = {\n HTTP_REQUEST_BODY: \"http.request.body\",\n HTTP_RESPONSE_BODY: \"http.response.body\",\n};\n\nexport interface PingopsInstrumentationConfig {\n /**\n * Maximum size of request body to capture in bytes\n * @defaultValue 4096 (4 KB)\n */\n maxRequestBodySize?: number;\n\n /**\n * Maximum size of response body to capture in bytes\n * @defaultValue 4096 (4 KB)\n */\n maxResponseBodySize?: number;\n}\n\n/**\n * Manually flattens a nested object into dot-notation keys\n */\nfunction flatten(obj: Record<string, any>, prefix = \"\"): Record<string, any> {\n const result: Record<string, any> = {};\n\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n !(value instanceof Buffer)\n ) {\n // Recursively flatten nested objects\n Object.assign(result, flatten(value, newKey));\n } else {\n result[newKey] = value;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Sets an attribute value on a span, handling various types appropriately\n */\nfunction setAttributeValue(span: Span, attrName: string, attrValue: any): void {\n if (\n typeof attrValue === \"string\" ||\n typeof attrValue === \"number\" ||\n typeof attrValue === \"boolean\"\n ) {\n span.setAttribute(attrName, attrValue);\n } else if (attrValue instanceof Buffer) {\n span.setAttribute(attrName, attrValue.toString(\"utf8\"));\n } else if (typeof attrValue == \"object\") {\n span.setAttributes(\n flatten({\n [attrName]: attrValue,\n })\n );\n } else if (Array.isArray(attrValue)) {\n // Check whether there is any element\n if (attrValue.length) {\n // Try to resolve array type over first element.\n // Other elements might have different types but this is just best effort solution.\n const firstElement: any = attrValue[0];\n if (\n typeof firstElement === \"string\" ||\n typeof firstElement === \"number\" ||\n typeof firstElement === \"boolean\"\n ) {\n span.setAttribute(attrName, attrValue);\n } else {\n // TODO What should we do with other array types???\n }\n } else {\n span.setAttribute(attrName, attrValue);\n }\n }\n // TODO What should we do with other types???\n}\n\n/**\n * Extracts domain from URL\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n const match = url.match(/^(?:https?:\\/\\/)?([^/]+)/);\n return match ? match[1] : \"\";\n }\n}\n\n/**\n * Gets domain rule configuration for a given URL\n */\nfunction getDomainRule(\n url: string,\n domainAllowList?: DomainRule[]\n): DomainRule | undefined {\n if (!domainAllowList) {\n return undefined;\n }\n\n const domain = extractDomainFromUrl(url);\n for (const rule of domainAllowList) {\n if (\n domain === rule.domain ||\n domain.endsWith(`.${rule.domain}`) ||\n domain === rule.domain.slice(1)\n ) {\n return rule;\n }\n }\n return undefined;\n}\n\n/**\n * Determines if request body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureRequestBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from startTrace)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_REQUEST_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureRequestBody !== undefined) {\n return domainRule.captureRequestBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureRequestBody !== undefined) {\n return globalConfig.captureRequestBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Determines if response body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureResponseBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from startTrace)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_RESPONSE_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureResponseBody !== undefined) {\n return domainRule.captureResponseBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureResponseBody !== undefined) {\n return globalConfig.captureResponseBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Captures request body from string or Buffer data\n */\nfunction captureRequestBody(\n span: Span,\n data: string | Buffer,\n maxSize: number,\n semanticAttr: string,\n url?: string\n): void {\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(url)) {\n return;\n }\n\n if (data.length && data.length <= maxSize) {\n try {\n const requestBody: string =\n typeof data === \"string\" ? data : data.toString(\"utf-8\");\n if (requestBody) {\n setAttributeValue(span, semanticAttr, requestBody);\n }\n } catch (e) {\n console.error(\"Error occurred while capturing request body:\", e);\n }\n }\n}\n\n/**\n * Captures response body from chunks\n */\nfunction captureResponseBody(\n span: Span,\n chunks: Buffer[] | null,\n semanticAttr: string,\n responseHeaders?: Record<string, string | string[] | undefined> | null,\n url?: string\n): void {\n // Check if body capture is enabled\n if (!shouldCaptureResponseBody(url)) {\n return;\n }\n\n if (chunks && chunks.length) {\n try {\n const concatedChunks: Buffer = Buffer.concat(chunks);\n const contentEncoding = responseHeaders?.[\"content-encoding\"];\n if (isCompressedContentEncoding(contentEncoding)) {\n setAttributeValue(\n span,\n semanticAttr,\n concatedChunks.toString(\"base64\")\n );\n const encStr =\n typeof contentEncoding === \"string\"\n ? contentEncoding\n : Array.isArray(contentEncoding)\n ? contentEncoding.map(String).join(\", \")\n : undefined;\n if (encStr) {\n setAttributeValue(span, HTTP_RESPONSE_CONTENT_ENCODING, encStr);\n }\n } else {\n const bodyStr = bufferToBodyString(concatedChunks);\n if (bodyStr != null) {\n setAttributeValue(span, semanticAttr, bodyStr);\n }\n }\n } catch (e) {\n console.error(\"Error occurred while capturing response body:\", e);\n }\n }\n}\n\n/**\n * Extracts headers from a request object (ClientRequest or IncomingMessage)\n * Handles both types efficiently by checking for available methods/properties\n */\nfunction extractRequestHeaders(\n request: ClientRequest | IncomingMessage\n): Record<string, string | string[] | undefined> | null {\n // IncomingMessage has a headers property (incoming requests)\n if (\"headers\" in request && request.headers) {\n return request.headers as Record<string, string | string[] | undefined>;\n }\n\n // ClientRequest uses getHeaders() method (outgoing requests)\n if (typeof (request as ClientRequest).getHeaders === \"function\") {\n try {\n const headers = (request as ClientRequest).getHeaders();\n // Convert OutgoingHttpHeaders to the expected format\n const result: Record<string, string | string[] | undefined> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key] =\n typeof value === \"number\"\n ? String(value)\n : Array.isArray(value)\n ? value.map(String)\n : String(value);\n }\n }\n return result;\n } catch {\n // getHeaders() might fail in some edge cases\n return null;\n }\n }\n\n return null;\n}\n\n/**\n * Extracts headers from a response object (ServerResponse or IncomingMessage)\n * Handles both types efficiently by checking for available methods/properties\n */\nfunction extractResponseHeaders(\n response: ServerResponse | IncomingMessage\n): Record<string, string | string[] | undefined> | null {\n // IncomingMessage has a headers property (incoming responses)\n if (\"headers\" in response && response.headers) {\n return response.headers as Record<string, string | string[] | undefined>;\n }\n\n // ServerResponse uses getHeaders() method (outgoing responses)\n if (typeof (response as ServerResponse).getHeaders === \"function\") {\n try {\n const headers = (response as ServerResponse).getHeaders();\n // Convert OutgoingHttpHeaders to the expected format\n const result: Record<string, string | string[] | undefined> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key] =\n typeof value === \"number\"\n ? String(value)\n : Array.isArray(value)\n ? value.map(String)\n : String(value);\n }\n }\n return result;\n } catch {\n // getHeaders() might fail in some edge cases\n return null;\n }\n }\n\n return null;\n}\n\n/**\n * Captures HTTP request headers as span attributes\n */\nfunction captureRequestHeaders(\n span: Span,\n headers: Record<string, string | string[] | undefined>\n): void {\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n span.setAttribute(\n `http.request.header.${key.toLowerCase()}`,\n Array.isArray(value) ? value.join(\",\") : String(value)\n );\n }\n }\n}\n\n/**\n * Captures HTTP response headers as span attributes\n */\nfunction captureResponseHeaders(\n span: Span,\n headers: Record<string, string | string[] | undefined>\n): void {\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n span.setAttribute(\n `http.response.header.${key.toLowerCase()}`,\n Array.isArray(value) ? value.join(\",\") : String(value)\n );\n }\n }\n}\n\n// Re-export semantic attributes for backward compatibility\nexport const PingopsHttpSemanticAttributes = PingopsSemanticAttributes;\n\nexport interface PingopsHttpInstrumentationConfig\n extends HttpInstrumentationConfig, PingopsInstrumentationConfig {}\n\nexport class PingopsHttpInstrumentation extends HttpInstrumentation {\n constructor(config?: PingopsHttpInstrumentationConfig) {\n super(config);\n this._config = this._createConfig(config);\n }\n\n private _createConfig(\n config?: PingopsHttpInstrumentationConfig\n ): PingopsHttpInstrumentationConfig {\n return {\n ...config,\n requestHook: this._createRequestHook(config?.requestHook, config),\n responseHook: this._createResponseHook(config?.responseHook, config),\n };\n }\n\n private _createRequestHook(\n originalRequestHook?: HttpRequestCustomAttributeFunction,\n config?: PingopsHttpInstrumentationConfig\n ): HttpRequestCustomAttributeFunction {\n return (span: Span, request: ClientRequest | IncomingMessage): void => {\n // Capture request headers\n const headers = extractRequestHeaders(request);\n if (headers) {\n captureRequestHeaders(span, headers);\n }\n if (request instanceof ClientRequest) {\n const maxRequestBodySize: number =\n config?.maxRequestBodySize || DEFAULT_MAX_REQUEST_BODY_SIZE;\n\n // Extract URL from request\n const url =\n request.path && request.getHeader(\"host\")\n ? `${request.protocol || \"http:\"}//${request.getHeader(\"host\")}${request.path}`\n : undefined;\n\n const originalWrite = request.write.bind(request);\n const originalEnd = request.end.bind(request);\n\n // Capture request body\n request.write = (data: any): boolean => {\n if (typeof data === \"string\" || data instanceof Buffer) {\n captureRequestBody(\n span,\n data,\n maxRequestBodySize,\n PingopsSemanticAttributes.HTTP_REQUEST_BODY,\n url\n );\n }\n return originalWrite(data);\n };\n\n request.end = (data: any): ClientRequest => {\n if (typeof data === \"string\" || data instanceof Buffer) {\n captureRequestBody(\n span,\n data,\n maxRequestBodySize,\n PingopsSemanticAttributes.HTTP_REQUEST_BODY,\n url\n );\n }\n return originalEnd(data);\n };\n }\n\n if (originalRequestHook) {\n originalRequestHook(span, request);\n }\n };\n }\n\n private _createResponseHook(\n originalResponseHook?: HttpResponseCustomAttributeFunction,\n config?: PingopsHttpInstrumentationConfig\n ): HttpResponseCustomAttributeFunction {\n return (span: Span, response: IncomingMessage | ServerResponse): void => {\n // Capture response headers\n const headers = extractResponseHeaders(response);\n if (headers) {\n captureResponseHeaders(span, headers);\n }\n\n if (response instanceof IncomingMessage) {\n const maxResponseBodySize: number =\n config?.maxResponseBodySize || DEFAULT_MAX_RESPONSE_BODY_SIZE;\n\n // Extract URL from response (if available via request)\n // Note: We can't easily get URL from IncomingMessage, so we'll rely on\n // domain rules matching based on headers or skip domain-specific checks\n const url = response.url || undefined;\n\n let chunks: Buffer[] | null = [];\n let totalSize: number = 0;\n\n // Only capture response body if enabled\n const shouldCapture = shouldCaptureResponseBody(url);\n\n // Capture response body\n response.prependListener(\"data\", (chunk: any): void => {\n if (!chunk || !shouldCapture) {\n return;\n }\n if (typeof chunk === \"string\" || chunk instanceof Buffer) {\n totalSize += chunk.length;\n if (chunks && totalSize <= maxResponseBodySize) {\n chunks.push(\n typeof chunk === \"string\" ? Buffer.from(chunk) : chunk\n );\n } else {\n // No need to capture partial response body\n chunks = null;\n }\n }\n });\n\n response.prependOnceListener(\"end\", (): void => {\n captureResponseBody(\n span,\n chunks,\n PingopsSemanticAttributes.HTTP_RESPONSE_BODY,\n headers,\n url\n );\n });\n }\n\n if (originalResponseHook) {\n originalResponseHook(span, response);\n }\n };\n }\n}\n","/**\n * HTTP instrumentation for OpenTelemetry\n */\n\nimport {\n PingopsHttpInstrumentation,\n type PingopsHttpInstrumentationConfig,\n} from \"./pingops-http\";\nimport { getGlobalConfig } from \"../../config-store\";\n\n/**\n * Creates an HTTP instrumentation instance.\n * All outgoing HTTP requests are instrumented when the SDK is initialized.\n *\n * @param config - Optional configuration for the instrumentation\n * @returns PingopsHttpInstrumentation instance\n */\nexport function createHttpInstrumentation(\n config?: Partial<PingopsHttpInstrumentationConfig>\n): PingopsHttpInstrumentation {\n const globalConfig = getGlobalConfig();\n\n return new PingopsHttpInstrumentation({\n ignoreIncomingRequestHook: () => true, // Only instrument outgoing requests\n ignoreOutgoingRequestHook: () => false, // Always instrument outgoing requests\n maxRequestBodySize: globalConfig?.maxRequestBodySize,\n maxResponseBodySize: globalConfig?.maxResponseBodySize,\n ...config,\n });\n}\n","import * as diagch from \"diagnostics_channel\";\nimport { URL } from \"url\";\n\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from \"@opentelemetry/instrumentation\";\nimport {\n Attributes,\n context,\n Histogram,\n HrTime,\n INVALID_SPAN_CONTEXT,\n propagation,\n Span,\n SpanKind,\n SpanStatusCode,\n trace,\n ValueType,\n} from \"@opentelemetry/api\";\nimport {\n hrTime,\n hrTimeDuration,\n hrTimeToMilliseconds,\n} from \"@opentelemetry/core\";\nimport {\n ATTR_ERROR_TYPE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_HTTP_REQUEST_METHOD_ORIGINAL,\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_NETWORK_PEER_ADDRESS,\n ATTR_NETWORK_PEER_PORT,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_FULL,\n ATTR_URL_PATH,\n ATTR_URL_QUERY,\n ATTR_URL_SCHEME,\n ATTR_USER_AGENT_ORIGINAL,\n METRIC_HTTP_CLIENT_REQUEST_DURATION,\n} from \"@opentelemetry/semantic-conventions\";\n\nimport {\n ListenerRecord,\n RequestBodyChunkReceivedMessage,\n RequestBodyChunkSentMessage,\n RequestBodySentMessage,\n RequestHeadersMessage,\n RequestMessage,\n RequestTrailersMessage,\n ResponseHeadersMessage,\n UndiciInstrumentationConfig,\n UndiciRequest,\n} from \"./types\";\nimport {\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n bufferToBodyString,\n HTTP_RESPONSE_CONTENT_ENCODING,\n isCompressedContentEncoding,\n type DomainRule,\n} from \"@pingops/core\";\nimport { getGlobalConfig } from \"../../config-store\";\n\n// Constants\nconst DEFAULT_MAX_REQUEST_BODY_SIZE: number = 4 * 1024; // 4 KB\nconst DEFAULT_MAX_RESPONSE_BODY_SIZE: number = 4 * 1024; // 4 KB\n\n// Semantic attributes\nconst HTTP_REQUEST_BODY = \"http.request.body\";\nconst HTTP_RESPONSE_BODY = \"http.response.body\";\n\n/**\n * Extracts domain from URL\n */\nfunction extractDomainFromUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n const match = url.match(/^(?:https?:\\/\\/)?([^/]+)/);\n return match ? match[1] : \"\";\n }\n}\n\n/**\n * Gets domain rule configuration for a given URL\n */\nfunction getDomainRule(\n url: string,\n domainAllowList?: DomainRule[]\n): DomainRule | undefined {\n if (!domainAllowList) {\n return undefined;\n }\n\n const domain = extractDomainFromUrl(url);\n for (const rule of domainAllowList) {\n if (\n domain === rule.domain ||\n domain.endsWith(`.${rule.domain}`) ||\n domain === rule.domain.slice(1)\n ) {\n return rule;\n }\n }\n return undefined;\n}\n\n/**\n * Determines if request body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureRequestBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from startTrace)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_REQUEST_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureRequestBody !== undefined) {\n return domainRule.captureRequestBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureRequestBody !== undefined) {\n return globalConfig.captureRequestBody;\n }\n\n // Default to false\n return false;\n}\n\n/**\n * Determines if response body should be captured based on priority:\n * context > domain rule > global config > default (false)\n */\nfunction shouldCaptureResponseBody(url?: string): boolean {\n const activeContext = context.active();\n\n // Check context value first (from startTrace)\n const contextValue = activeContext.getValue(PINGOPS_CAPTURE_RESPONSE_BODY) as\n | boolean\n | undefined;\n if (contextValue !== undefined) {\n return contextValue;\n }\n\n // Check domain-specific rule\n if (url) {\n const globalConfig = getGlobalConfig();\n const domainRule = getDomainRule(url, globalConfig?.domainAllowList);\n if (domainRule?.captureResponseBody !== undefined) {\n return domainRule.captureResponseBody;\n }\n }\n\n // Fall back to global config\n const globalConfig = getGlobalConfig();\n if (globalConfig?.captureResponseBody !== undefined) {\n return globalConfig.captureResponseBody;\n }\n\n // Default to false\n return false;\n}\n\ninterface InstrumentationRecord {\n span: Span;\n attributes: Attributes;\n startTime: HrTime;\n requestBodyChunks: Buffer[];\n responseBodyChunks: Buffer[];\n requestBodySize: number;\n responseBodySize: number;\n url?: string;\n}\n\nexport class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumentationConfig> {\n declare private _channelSubs: Array<ListenerRecord>;\n private _recordFromReq = new WeakMap<UndiciRequest, InstrumentationRecord>();\n\n declare private _httpClientDurationHistogram: Histogram;\n\n constructor(config: UndiciInstrumentationConfig = {}) {\n super(\"pingops-undici\", \"0.1.0\", config);\n }\n\n // No need to instrument files/modules\n protected override init() {\n return undefined;\n }\n\n override disable(): void {\n super.disable();\n this._channelSubs.forEach((sub) => sub.unsubscribe());\n this._channelSubs.length = 0;\n }\n\n override enable(): void {\n // \"enabled\" handling is currently a bit messy with InstrumentationBase.\n // If constructed with `{enabled: false}`, this `.enable()` is still called,\n // and `this.getConfig().enabled !== this.isEnabled()`, creating confusion.\n //\n // For now, this class will setup for instrumenting if `.enable()` is\n // called, but use `this.getConfig().enabled` to determine if\n // instrumentation should be generated. This covers the more likely common\n // case of config being given a construction time, rather than later via\n // `instance.enable()`, `.disable()`, or `.setConfig()` calls.\n super.enable();\n\n // This method is called by the super-class constructor before ours is\n // called. So we need to ensure the property is initalized.\n this._channelSubs = this._channelSubs || [];\n\n // Avoid to duplicate subscriptions\n if (this._channelSubs.length > 0) {\n return;\n }\n\n this.subscribeToChannel(\n \"undici:request:create\",\n this.onRequestCreated.bind(this)\n );\n this.subscribeToChannel(\n \"undici:client:sendHeaders\",\n this.onRequestHeaders.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:headers\",\n this.onResponseHeaders.bind(this)\n );\n this.subscribeToChannel(\"undici:request:trailers\", this.onDone.bind(this));\n this.subscribeToChannel(\"undici:request:error\", this.onError.bind(this));\n this.subscribeToChannel(\n \"undici:request:bodyChunkSent\",\n this.onBodyChunkSent.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:bodySent\",\n this.onBodySent.bind(this)\n );\n this.subscribeToChannel(\n \"undici:request:bodyChunkReceived\",\n this.onBodyChunkReceived.bind(this)\n );\n }\n\n protected override _updateMetricInstruments() {\n this._httpClientDurationHistogram = this.meter.createHistogram(\n METRIC_HTTP_CLIENT_REQUEST_DURATION,\n {\n description: \"Measures the duration of outbound HTTP requests.\",\n unit: \"s\",\n valueType: ValueType.DOUBLE,\n advice: {\n explicitBucketBoundaries: [\n 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,\n 7.5, 10,\n ],\n },\n }\n );\n }\n\n private subscribeToChannel(\n diagnosticChannel: string,\n onMessage: (message: any, name: string | symbol) => void\n ) {\n // `diagnostics_channel` had a ref counting bug until v18.19.0.\n // https://github.com/nodejs/node/pull/47520\n const [major, minor] = process.version\n .replace(\"v\", \"\")\n .split(\".\")\n .map((n) => Number(n));\n const useNewSubscribe = major > 18 || (major === 18 && minor >= 19);\n\n let unsubscribe: () => void;\n if (useNewSubscribe) {\n diagch.subscribe?.(diagnosticChannel, onMessage);\n unsubscribe = () => diagch.unsubscribe?.(diagnosticChannel, onMessage);\n } else {\n const channel = diagch.channel(diagnosticChannel);\n channel.subscribe(onMessage);\n unsubscribe = () => channel.unsubscribe(onMessage);\n }\n\n this._channelSubs.push({\n name: diagnosticChannel,\n unsubscribe,\n });\n }\n\n private parseRequestHeaders(request: UndiciRequest) {\n const result = new Map<string, string | string[]>();\n\n if (Array.isArray(request.headers)) {\n // headers are an array [k1, v2, k2, v2] (undici v6+)\n // values could be string or a string[] for multiple values\n for (let i = 0; i < request.headers.length; i += 2) {\n const key = request.headers[i];\n const value = request.headers[i + 1];\n\n // Key should always be a string, but the types don't know that, and let's be safe\n if (typeof key === \"string\") {\n result.set(key.toLowerCase(), value);\n }\n }\n } else if (typeof request.headers === \"string\") {\n // headers are a raw string (undici v5)\n // headers could be repeated in several lines for multiple values\n const headers = request.headers.split(\"\\r\\n\");\n for (const line of headers) {\n if (!line) {\n continue;\n }\n const colonIndex = line.indexOf(\":\");\n if (colonIndex === -1) {\n // Invalid header? Probably this can't happen, but again let's be safe.\n continue;\n }\n const key = line.substring(0, colonIndex).toLowerCase();\n const value = line.substring(colonIndex + 1).trim();\n const allValues = result.get(key);\n\n if (allValues && Array.isArray(allValues)) {\n allValues.push(value);\n } else if (allValues) {\n result.set(key, [allValues, value]);\n } else {\n result.set(key, value);\n }\n }\n }\n return result;\n }\n\n // This is the 1st message we receive for each request (fired after request creation). Here we will\n // create the span and populate some atttributes, then link the span to the request for further\n // span processing\n private onRequestCreated({ request }: RequestMessage): void {\n // Ignore if:\n // - instrumentation is disabled\n // - ignored by config\n // - method is 'CONNECT'\n const config = this.getConfig();\n const enabled = config.enabled !== false;\n const shouldIgnoreReq = safeExecuteInTheMiddle(\n () =>\n !enabled ||\n request.method === \"CONNECT\" ||\n config.ignoreRequestHook?.(request),\n (e) => e && this._diag.error(\"caught ignoreRequestHook error: \", e),\n true\n );\n\n if (shouldIgnoreReq) {\n return;\n }\n\n const startTime = hrTime();\n let requestUrl;\n try {\n requestUrl = new URL(request.path, request.origin);\n } catch (err) {\n this._diag.warn(\"could not determine url.full:\", err);\n // Skip instrumenting this request.\n return;\n }\n const urlScheme = requestUrl.protocol.replace(\":\", \"\");\n const requestMethod = this.getRequestMethod(request.method);\n const attributes: Attributes = {\n [ATTR_HTTP_REQUEST_METHOD]: requestMethod,\n [ATTR_HTTP_REQUEST_METHOD_ORIGINAL]: request.method,\n [ATTR_URL_FULL]: requestUrl.toString(),\n [ATTR_URL_PATH]: requestUrl.pathname,\n [ATTR_URL_QUERY]: requestUrl.search,\n [ATTR_URL_SCHEME]: urlScheme,\n };\n\n const schemePorts: Record<string, string> = { https: \"443\", http: \"80\" };\n const serverAddress = requestUrl.hostname;\n const serverPort = requestUrl.port || schemePorts[urlScheme];\n\n attributes[ATTR_SERVER_ADDRESS] = serverAddress;\n if (serverPort && !isNaN(Number(serverPort))) {\n attributes[ATTR_SERVER_PORT] = Number(serverPort);\n }\n\n // Get user agent from headers\n const headersMap = this.parseRequestHeaders(request);\n const userAgentValues = headersMap.get(\"user-agent\");\n\n if (userAgentValues) {\n // NOTE: having multiple user agents is not expected so\n // we're going to take last one like `curl` does\n // ref: https://curl.se/docs/manpage.html#-A\n const userAgent = Array.isArray(userAgentValues)\n ? userAgentValues[userAgentValues.length - 1]\n : userAgentValues;\n attributes[ATTR_USER_AGENT_ORIGINAL] = userAgent;\n }\n\n // Get attributes from the hook if present\n const hookAttributes = safeExecuteInTheMiddle(\n () => config.startSpanHook?.(request),\n (e) => e && this._diag.error(\"caught startSpanHook error: \", e),\n true\n );\n if (hookAttributes) {\n Object.entries(hookAttributes).forEach(([key, val]) => {\n attributes[key] = val;\n });\n }\n\n // Check if parent span is required via config and:\n // - if a parent is required but not present, we use a `NoopSpan` to still\n // propagate context without recording it.\n // - create a span otherwise\n const activeCtx = context.active();\n const currentSpan = trace.getSpan(activeCtx);\n let span: Span;\n\n if (\n config.requireParentforSpans &&\n (!currentSpan || !trace.isSpanContextValid(currentSpan.spanContext()))\n ) {\n span = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n } else {\n span = this.tracer.startSpan(\n requestMethod === \"_OTHER\" ? \"HTTP\" : requestMethod,\n {\n kind: SpanKind.CLIENT,\n attributes: attributes,\n },\n activeCtx\n );\n }\n\n // Execute the request hook if defined\n safeExecuteInTheMiddle(\n () => config.requestHook?.(span, request),\n (e) => e && this._diag.error(\"caught requestHook error: \", e),\n true\n );\n\n // Context propagation goes last so no hook can tamper\n // the propagation headers\n const requestContext = trace.setSpan(context.active(), span);\n const addedHeaders: Record<string, string> = {};\n propagation.inject(requestContext, addedHeaders);\n\n const headerEntries = Object.entries(addedHeaders);\n\n for (let i = 0; i < headerEntries.length; i++) {\n const [k, v] = headerEntries[i];\n\n if (typeof request.addHeader === \"function\") {\n request.addHeader(k, v);\n } else if (typeof request.headers === \"string\") {\n request.headers += `${k}: ${v}\\r\\n`;\n } else if (Array.isArray(request.headers)) {\n // undici@6.11.0 accidentally, briefly removed `request.addHeader()`.\n request.headers.push(k, v);\n }\n }\n this._recordFromReq.set(request, {\n span,\n attributes,\n startTime,\n requestBodyChunks: [],\n responseBodyChunks: [],\n requestBodySize: 0,\n responseBodySize: 0,\n url: requestUrl.toString(),\n });\n }\n\n // This is the 2nd message we receive for each request. It is fired when connection with\n // the remote is established and about to send the first byte. Here we do have info about the\n // remote address and port so we can populate some `network.*` attributes into the span\n private onRequestHeaders({ request, socket }: RequestHeadersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span } = record;\n const { remoteAddress, remotePort } = socket;\n const spanAttributes: Attributes = {\n [ATTR_NETWORK_PEER_ADDRESS]: remoteAddress,\n [ATTR_NETWORK_PEER_PORT]: remotePort,\n };\n\n const headersMap = this.parseRequestHeaders(request);\n\n for (const [name, value] of headersMap.entries()) {\n const attrValue = Array.isArray(value) ? value.join(\", \") : value;\n spanAttributes[`http.request.header.${name}`] = attrValue;\n }\n\n span.setAttributes(spanAttributes);\n }\n\n // This is the 3rd message we get for each request and it's fired when the server\n // headers are received, body may not be accessible yet.\n // From the response headers we can set the status and content length\n private onResponseHeaders({\n request,\n response,\n }: ResponseHeadersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes } = record;\n const spanAttributes: Attributes = {\n [ATTR_HTTP_RESPONSE_STATUS_CODE]: response.statusCode,\n };\n\n const config = this.getConfig();\n\n // Execute the response hook if defined\n safeExecuteInTheMiddle(\n () => config.responseHook?.(span, { request, response }),\n (e) => e && this._diag.error(\"caught responseHook error: \", e),\n true\n );\n\n for (let idx = 0; idx < response.headers.length; idx = idx + 2) {\n const name = response.headers[idx].toString().toLowerCase();\n const value = response.headers[idx + 1];\n\n spanAttributes[`http.response.header.${name}`] = value.toString();\n\n if (name === \"content-length\") {\n const contentLength = Number(value.toString());\n if (!isNaN(contentLength)) {\n spanAttributes[\"http.response.header.content-length\"] = contentLength;\n }\n }\n }\n\n span.setAttributes(spanAttributes);\n span.setStatus({\n code:\n response.statusCode >= 400\n ? SpanStatusCode.ERROR\n : SpanStatusCode.UNSET,\n });\n record.attributes = Object.assign(attributes, spanAttributes);\n }\n\n // This is the last event we receive if the request went without any errors\n private onDone({ request }: RequestTrailersMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes, startTime } = record;\n\n // Check if body capture is enabled before setting response body attribute\n if (shouldCaptureResponseBody(record.url)) {\n const config = this.getConfig();\n const maxResponseBodySize =\n config.maxResponseBodySize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE;\n\n const contentEncoding =\n (record.attributes?.[\"http.response.header.content-encoding\"] as\n | string\n | undefined) ?? undefined;\n const contentType =\n (record.attributes?.[\"http.response.header.content-type\"] as\n | string\n | undefined) ?? undefined;\n\n // If we exceeded the configured max, record a clear message rather than\n // storing partial (often-undecodable) bytes.\n if (record.responseBodySize === Infinity) {\n span.setAttribute(\n HTTP_RESPONSE_BODY,\n `[truncated response body; exceeded maxResponseBodySize=${maxResponseBodySize}; content-type=${contentType ?? \"unknown\"}; content-encoding=${contentEncoding ?? \"identity\"}]`\n );\n } else if (record.responseBodyChunks.length > 0) {\n // Set response body attribute if we have chunks and haven't exceeded max size\n try {\n const responseBodyBuffer = Buffer.concat(record.responseBodyChunks);\n if (isCompressedContentEncoding(contentEncoding)) {\n span.setAttribute(\n HTTP_RESPONSE_BODY,\n responseBodyBuffer.toString(\"base64\")\n );\n if (contentEncoding) {\n span.setAttribute(\n HTTP_RESPONSE_CONTENT_ENCODING,\n contentEncoding\n );\n }\n } else {\n const bodyStr = bufferToBodyString(responseBodyBuffer);\n if (bodyStr != null) {\n span.setAttribute(HTTP_RESPONSE_BODY, bodyStr);\n }\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing response body:\", e);\n }\n }\n }\n\n // End the span\n span.end();\n this._recordFromReq.delete(request);\n\n // Record metrics\n this.recordRequestDuration(attributes, startTime);\n }\n\n // This is the event we get when something is wrong in the request like\n // - invalid options when calling `fetch` global API or any undici method for request\n // - connectivity errors such as unreachable host\n // - requests aborted through an `AbortController.signal`\n // NOTE: server errors are considered valid responses and it's the lib consumer\n // who should deal with that.\n private onError({ request, error }: any): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n const { span, attributes, startTime } = record;\n\n // Check if body capture is enabled before setting request body attribute\n // (in case body was sent before error occurred)\n if (shouldCaptureRequestBody(record.url)) {\n // Set request body attribute if we have chunks and haven't exceeded max size\n if (\n record.requestBodyChunks.length > 0 &&\n record.requestBodySize !== Infinity\n ) {\n try {\n const requestBody = Buffer.concat(record.requestBodyChunks).toString(\n \"utf-8\"\n );\n if (requestBody) {\n span.setAttribute(HTTP_REQUEST_BODY, requestBody);\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing request body:\", e);\n }\n }\n }\n\n // NOTE: in `undici@6.3.0` when request aborted the error type changes from\n // a custom error (`RequestAbortedError`) to a built-in `DOMException` carrying\n // some differences:\n // - `code` is from DOMEXception (ABORT_ERR: 20)\n // - `message` changes\n // - stacktrace is smaller and contains node internal frames\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n span.end();\n this._recordFromReq.delete(request);\n\n // Record metrics (with the error)\n attributes[ATTR_ERROR_TYPE] = error.message;\n this.recordRequestDuration(attributes, startTime);\n }\n\n private onBodyChunkSent({\n request,\n chunk,\n }: RequestBodyChunkSentMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(record.url)) {\n return;\n }\n\n const config = this.getConfig();\n const maxRequestBodySize =\n config.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE;\n\n // Only accumulate chunks if we haven't exceeded the max size\n if (record.requestBodySize + chunk.length <= maxRequestBodySize) {\n record.requestBodyChunks.push(chunk);\n record.requestBodySize += chunk.length;\n } else {\n // No need to capture partial request body; mark as exceeded and drop what we have.\n record.requestBodySize = Infinity;\n record.requestBodyChunks = [];\n }\n }\n\n private onBodySent({ request }: RequestBodySentMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureRequestBody(record.url)) {\n // Clear request body chunks to free memory\n record.requestBodyChunks = [];\n return;\n }\n\n // Set request body attribute if we have chunks and haven't exceeded max size\n if (record.requestBodySize === Infinity) {\n const config = this.getConfig();\n const maxRequestBodySize =\n config.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY_SIZE;\n record.span.setAttribute(\n HTTP_REQUEST_BODY,\n `[truncated request body; exceeded maxRequestBodySize=${maxRequestBodySize}]`\n );\n } else if (record.requestBodyChunks.length > 0) {\n try {\n const requestBody = Buffer.concat(record.requestBodyChunks).toString(\n \"utf-8\"\n );\n if (requestBody) {\n record.span.setAttribute(HTTP_REQUEST_BODY, requestBody);\n }\n } catch (e) {\n this._diag.error(\"Error occurred while capturing request body:\", e);\n }\n }\n\n // Clear request body chunks to free memory\n record.requestBodyChunks = [];\n }\n\n private onBodyChunkReceived({\n request,\n chunk,\n }: RequestBodyChunkReceivedMessage): void {\n const record = this._recordFromReq.get(request);\n\n if (!record) {\n return;\n }\n\n // Check if body capture is enabled\n if (!shouldCaptureResponseBody(record.url)) {\n return;\n }\n\n const config = this.getConfig();\n const maxResponseBodySize =\n config.maxResponseBodySize ?? DEFAULT_MAX_RESPONSE_BODY_SIZE;\n\n // Only accumulate chunks if we haven't exceeded the max size\n if (record.responseBodySize + chunk.length <= maxResponseBodySize) {\n record.responseBodyChunks.push(chunk);\n record.responseBodySize += chunk.length;\n } else {\n // No need to capture partial response body (especially for compressed bodies\n // where partial data is not decodable). Mark as exceeded and drop what we have.\n record.responseBodySize = Infinity;\n record.responseBodyChunks = [];\n }\n }\n\n private recordRequestDuration(attributes: Attributes, startTime: HrTime) {\n // Time to record metrics\n const metricsAttributes: Attributes = {};\n // Get the attribs already in span attributes\n const keysToCopy = [\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_SCHEME,\n ATTR_ERROR_TYPE,\n ];\n keysToCopy.forEach((key) => {\n if (key in attributes) {\n metricsAttributes[key] = attributes[key];\n }\n });\n\n // Take the duration and record it\n const durationSeconds =\n hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime())) / 1000;\n this._httpClientDurationHistogram.record(\n durationSeconds,\n metricsAttributes\n );\n }\n\n private getRequestMethod(original: string): string {\n const knownMethods = {\n CONNECT: true,\n OPTIONS: true,\n HEAD: true,\n GET: true,\n POST: true,\n PUT: true,\n PATCH: true,\n DELETE: true,\n TRACE: true,\n };\n\n if (original.toUpperCase() in knownMethods) {\n return original.toUpperCase();\n }\n\n return \"_OTHER\";\n }\n}\n","/**\n * Undici instrumentation for OpenTelemetry\n */\n\nimport { UndiciInstrumentation } from \"./pingops-undici\";\nimport { getGlobalConfig } from \"../../config-store\";\n\n/**\n * Creates an Undici instrumentation instance.\n * All requests are instrumented when the SDK is initialized.\n *\n * @returns UndiciInstrumentation instance\n */\nexport function createUndiciInstrumentation(): UndiciInstrumentation {\n const globalConfig = getGlobalConfig();\n\n return new UndiciInstrumentation({\n enabled: true,\n ignoreRequestHook: () => false, // Always instrument requests\n maxRequestBodySize: globalConfig?.maxRequestBodySize,\n maxResponseBodySize: globalConfig?.maxResponseBodySize,\n });\n}\n","/**\n * Instrumentation setup for HTTP, fetch, undici, and GenAI\n */\n\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport { createHttpInstrumentation } from \"./http/http\";\nimport { createUndiciInstrumentation } from \"./undici/undici\";\n\nlet installed = false;\n\n/**\n * Registers instrumentations for Node.js environment.\n * This function is idempotent and can be called multiple times safely.\n * When the SDK is initialized, all HTTP requests are instrumented.\n *\n * @returns Array of Instrumentation instances\n */\nexport function getInstrumentations(): Instrumentation[] {\n if (installed) {\n return [];\n }\n\n installed = true;\n return [createHttpInstrumentation(), createUndiciInstrumentation()];\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -55,6 +55,26 @@ interface PingopsProcessorConfig {
|
|
|
55
55
|
* Capture response body.
|
|
56
56
|
*/
|
|
57
57
|
captureResponseBody?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Maximum size of request body to capture (bytes).
|
|
60
|
+
* Applies to both `http` and `undici` instrumentations.
|
|
61
|
+
*
|
|
62
|
+
* Note: this is the number of raw bytes observed at the instrumentation layer.
|
|
63
|
+
* For compressed bodies, this is the compressed size.
|
|
64
|
+
*
|
|
65
|
+
* @defaultValue 4096 (4 KB)
|
|
66
|
+
*/
|
|
67
|
+
maxRequestBodySize?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Maximum size of response body to capture (bytes).
|
|
70
|
+
* Applies to both `http` and `undici` instrumentations.
|
|
71
|
+
*
|
|
72
|
+
* Note: this is the number of raw bytes observed at the instrumentation layer.
|
|
73
|
+
* For compressed bodies, this is the compressed size.
|
|
74
|
+
*
|
|
75
|
+
* @defaultValue 4096 (4 KB)
|
|
76
|
+
*/
|
|
77
|
+
maxResponseBodySize?: number;
|
|
58
78
|
/**
|
|
59
79
|
* Domain allow list rules.
|
|
60
80
|
*/
|
|
@@ -167,33 +187,16 @@ declare function shutdownTracerProvider(): Promise<void>;
|
|
|
167
187
|
/**
|
|
168
188
|
* Registers instrumentations for Node.js environment.
|
|
169
189
|
* This function is idempotent and can be called multiple times safely.
|
|
190
|
+
* When the SDK is initialized, all HTTP requests are instrumented.
|
|
170
191
|
*
|
|
171
|
-
* Instrumentation behavior:
|
|
172
|
-
* - If global instrumentation is enabled: all HTTP requests are instrumented
|
|
173
|
-
* - If global instrumentation is NOT enabled: only requests within wrapHttp blocks are instrumented
|
|
174
|
-
*
|
|
175
|
-
* @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled
|
|
176
192
|
* @returns Array of Instrumentation instances
|
|
177
193
|
*/
|
|
178
|
-
declare function getInstrumentations(
|
|
194
|
+
declare function getInstrumentations(): Instrumentation[];
|
|
179
195
|
//#endregion
|
|
180
196
|
//#region src/instrumentations/http/pingops-http.d.ts
|
|
181
197
|
declare const PingopsSemanticAttributes: {
|
|
182
198
|
HTTP_REQUEST_BODY: string;
|
|
183
199
|
HTTP_RESPONSE_BODY: string;
|
|
184
|
-
NETWORK_DNS_LOOKUP_DURATION: string;
|
|
185
|
-
NETWORK_TCP_CONNECT_DURATION: string;
|
|
186
|
-
NETWORK_TLS_HANDSHAKE_DURATION: string;
|
|
187
|
-
NETWORK_TTFB_DURATION: string;
|
|
188
|
-
NETWORK_CONTENT_TRANSFER_DURATION: string;
|
|
189
|
-
};
|
|
190
|
-
type NetworkTimings = {
|
|
191
|
-
startAt?: number;
|
|
192
|
-
dnsLookupAt?: number;
|
|
193
|
-
tcpConnectionAt?: number;
|
|
194
|
-
tlsHandshakeAt?: number;
|
|
195
|
-
firstByteAt?: number;
|
|
196
|
-
endAt?: number;
|
|
197
200
|
};
|
|
198
201
|
interface PingopsInstrumentationConfig {
|
|
199
202
|
/**
|
|
@@ -210,11 +213,6 @@ interface PingopsInstrumentationConfig {
|
|
|
210
213
|
declare const PingopsHttpSemanticAttributes: {
|
|
211
214
|
HTTP_REQUEST_BODY: string;
|
|
212
215
|
HTTP_RESPONSE_BODY: string;
|
|
213
|
-
NETWORK_DNS_LOOKUP_DURATION: string;
|
|
214
|
-
NETWORK_TCP_CONNECT_DURATION: string;
|
|
215
|
-
NETWORK_TLS_HANDSHAKE_DURATION: string;
|
|
216
|
-
NETWORK_TTFB_DURATION: string;
|
|
217
|
-
NETWORK_CONTENT_TRANSFER_DURATION: string;
|
|
218
216
|
};
|
|
219
217
|
interface PingopsHttpInstrumentationConfig extends HttpInstrumentationConfig, PingopsInstrumentationConfig {}
|
|
220
218
|
declare class PingopsHttpInstrumentation extends HttpInstrumentation {
|
|
@@ -226,13 +224,13 @@ declare class PingopsHttpInstrumentation extends HttpInstrumentation {
|
|
|
226
224
|
//#endregion
|
|
227
225
|
//#region src/instrumentations/http/http.d.ts
|
|
228
226
|
/**
|
|
229
|
-
* Creates an HTTP instrumentation instance
|
|
227
|
+
* Creates an HTTP instrumentation instance.
|
|
228
|
+
* All outgoing HTTP requests are instrumented when the SDK is initialized.
|
|
230
229
|
*
|
|
231
|
-
* @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled
|
|
232
230
|
* @param config - Optional configuration for the instrumentation
|
|
233
231
|
* @returns PingopsHttpInstrumentation instance
|
|
234
232
|
*/
|
|
235
|
-
declare function createHttpInstrumentation(
|
|
233
|
+
declare function createHttpInstrumentation(config?: Partial<PingopsHttpInstrumentationConfig>): PingopsHttpInstrumentation;
|
|
236
234
|
//#endregion
|
|
237
235
|
//#region src/instrumentations/undici/types.d.ts
|
|
238
236
|
interface UndiciRequest {
|
|
@@ -331,12 +329,12 @@ declare class UndiciInstrumentation extends InstrumentationBase<UndiciInstrument
|
|
|
331
329
|
//#endregion
|
|
332
330
|
//#region src/instrumentations/undici/undici.d.ts
|
|
333
331
|
/**
|
|
334
|
-
* Creates an Undici instrumentation instance
|
|
332
|
+
* Creates an Undici instrumentation instance.
|
|
333
|
+
* All requests are instrumented when the SDK is initialized.
|
|
335
334
|
*
|
|
336
|
-
* @param isGlobalInstrumentationEnabled - Function that checks if global instrumentation is enabled
|
|
337
335
|
* @returns UndiciInstrumentation instance
|
|
338
336
|
*/
|
|
339
|
-
declare function createUndiciInstrumentation(
|
|
337
|
+
declare function createUndiciInstrumentation(): UndiciInstrumentation;
|
|
340
338
|
//#endregion
|
|
341
|
-
export {
|
|
339
|
+
export { PingopsHttpInstrumentation, type PingopsHttpInstrumentationConfig, PingopsHttpSemanticAttributes, type PingopsInstrumentationConfig, type PingopsProcessorConfig, PingopsSemanticAttributes, PingopsSpanProcessor, createHttpInstrumentation, createUndiciInstrumentation, getInstrumentations, getPingopsTracerProvider, setPingopsTracerProvider, shutdownTracerProvider };
|
|
342
340
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/config.ts","../src/span-processor.ts","../src/tracer-provider.ts","../src/instrumentations/index.ts","../src/instrumentations/http/pingops-http.ts","../src/instrumentations/http/http.ts","../src/instrumentations/undici/types.ts","../src/instrumentations/undici/pingops-undici.ts","../src/instrumentations/undici/undici.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAgBA;AAKA;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/config.ts","../src/span-processor.ts","../src/tracer-provider.ts","../src/instrumentations/index.ts","../src/instrumentations/http/pingops-http.ts","../src/instrumentations/http/http.ts","../src/instrumentations/undici/types.ts","../src/instrumentations/undici/pingops-undici.ts","../src/instrumentations/undici/undici.ts"],"sourcesContent":[],"mappings":"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"}
|