@pingops/core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/index.cjs +824 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +306 -0
  4. package/dist/index.d.cts.map +1 -0
  5. package/dist/index.d.mts +306 -0
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +804 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +17 -5
  10. package/dist/context-keys.d.ts +0 -42
  11. package/dist/context-keys.d.ts.map +0 -1
  12. package/dist/context-keys.js +0 -43
  13. package/dist/context-keys.js.map +0 -1
  14. package/dist/filtering/domain-filter.d.ts +0 -9
  15. package/dist/filtering/domain-filter.d.ts.map +0 -1
  16. package/dist/filtering/domain-filter.js +0 -136
  17. package/dist/filtering/domain-filter.js.map +0 -1
  18. package/dist/filtering/header-filter.d.ts +0 -31
  19. package/dist/filtering/header-filter.d.ts.map +0 -1
  20. package/dist/filtering/header-filter.js +0 -187
  21. package/dist/filtering/header-filter.js.map +0 -1
  22. package/dist/filtering/span-filter.d.ts +0 -13
  23. package/dist/filtering/span-filter.d.ts.map +0 -1
  24. package/dist/filtering/span-filter.js +0 -46
  25. package/dist/filtering/span-filter.js.map +0 -1
  26. package/dist/index.d.ts +0 -13
  27. package/dist/index.d.ts.map +0 -1
  28. package/dist/index.js +0 -13
  29. package/dist/index.js.map +0 -1
  30. package/dist/logger.d.ts +0 -21
  31. package/dist/logger.d.ts.map +0 -1
  32. package/dist/logger.js +0 -36
  33. package/dist/logger.js.map +0 -1
  34. package/dist/types.d.ts +0 -46
  35. package/dist/types.d.ts.map +0 -1
  36. package/dist/types.js +0 -5
  37. package/dist/types.js.map +0 -1
  38. package/dist/utils/context-extractor.d.ts +0 -13
  39. package/dist/utils/context-extractor.d.ts.map +0 -1
  40. package/dist/utils/context-extractor.js +0 -44
  41. package/dist/utils/context-extractor.js.map +0 -1
  42. package/dist/utils/span-extractor.d.ts +0 -10
  43. package/dist/utils/span-extractor.d.ts.map +0 -1
  44. package/dist/utils/span-extractor.js +0 -156
  45. package/dist/utils/span-extractor.js.map +0 -1
  46. package/dist/wrap-http.d.ts +0 -55
  47. package/dist/wrap-http.d.ts.map +0 -1
  48. package/dist/wrap-http.js +0 -135
  49. package/dist/wrap-http.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["log","SpanKind","log","context"],"sources":["../src/logger.ts","../src/filtering/span-filter.ts","../src/filtering/domain-filter.ts","../src/filtering/sensitive-headers.ts","../src/filtering/header-filter.ts","../src/utils/span-extractor.ts","../src/context-keys.ts","../src/utils/context-extractor.ts","../src/wrap-http.ts"],"sourcesContent":["/**\n * Global logger utility for PingOps Core\n *\n * Provides consistent logging across all core components with support for\n * different log levels and debug mode control via PINGOPS_DEBUG environment variable.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\n/**\n * Creates a logger instance with a specific prefix\n *\n * @param prefix - Prefix to add to all log messages (e.g., '[PingOps Filter]')\n * @returns Logger instance\n */\nexport function createLogger(prefix: string): Logger {\n const isDebugEnabled = process.env.PINGOPS_DEBUG === \"true\";\n\n const formatMessage = (level: LogLevel, message: string): string => {\n const timestamp = new Date().toISOString();\n return `[${timestamp}] ${prefix} [${level.toUpperCase()}] ${message}`;\n };\n\n return {\n debug(message: string, ...args: unknown[]): void {\n if (isDebugEnabled) {\n console.debug(formatMessage(\"debug\", message), ...args);\n }\n },\n info(message: string, ...args: unknown[]): void {\n console.log(formatMessage(\"info\", message), ...args);\n },\n warn(message: string, ...args: unknown[]): void {\n console.warn(formatMessage(\"warn\", message), ...args);\n },\n error(message: string, ...args: unknown[]): void {\n console.error(formatMessage(\"error\", message), ...args);\n },\n };\n}\n","/**\n * Span filtering logic - determines if a span is eligible for capture\n */\n\nimport { SpanKind } from \"@opentelemetry/api\";\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { createLogger } from \"../logger\";\n\nconst log = createLogger(\"[PingOps SpanFilter]\");\n\n/**\n * Checks if a span is eligible for capture based on span kind and attributes.\n * A span is eligible if:\n * 1. span.kind === SpanKind.CLIENT\n * 2. AND has HTTP attributes (http.method, http.url, or server.address)\n * OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)\n */\nexport function isSpanEligible(span: ReadableSpan): boolean {\n log.debug(\"Checking span eligibility\", {\n spanName: span.name,\n spanKind: span.kind,\n spanId: span.spanContext().spanId,\n traceId: span.spanContext().traceId,\n });\n\n // Must be a CLIENT span (outgoing request)\n if (span.kind !== SpanKind.CLIENT) {\n log.debug(\"Span not eligible: not CLIENT kind\", {\n spanName: span.name,\n spanKind: span.kind,\n });\n return false;\n }\n\n const attributes = span.attributes;\n\n // Check for HTTP attributes\n const hasHttpMethod = attributes[\"http.method\"] !== undefined;\n const hasHttpUrl = attributes[\"http.url\"] !== undefined;\n const hasServerAddress = attributes[\"server.address\"] !== undefined;\n\n const isEligible = hasHttpMethod || hasHttpUrl || hasServerAddress;\n\n log.debug(\"Span eligibility check result\", {\n spanName: span.name,\n isEligible,\n httpAttributes: {\n hasMethod: hasHttpMethod,\n hasUrl: hasHttpUrl,\n hasServerAddress,\n },\n });\n\n return isEligible;\n}\n","/**\n * Domain filtering logic - applies allow/deny list rules\n */\n\nimport type { DomainRule } from \"../types\";\nimport { createLogger } from \"../logger\";\n\nconst log = createLogger(\"[PingOps DomainFilter]\");\n\n/**\n * Extracts domain from a URL\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n const domain = urlObj.hostname;\n log.debug(\"Extracted domain from URL\", { url, domain });\n return domain;\n } catch {\n // If URL parsing fails, try to extract domain from string\n const match = url.match(/^(?:https?:\\/\\/)?([^/]+)/);\n const domain = match ? match[1] : \"\";\n log.debug(\"Extracted domain from URL (fallback)\", { url, domain });\n return domain;\n }\n}\n\n/**\n * Checks if a domain matches a rule (exact or suffix match)\n */\nfunction domainMatches(domain: string, ruleDomain: string): boolean {\n // Exact match\n if (domain === ruleDomain) {\n log.debug(\"Domain exact match\", { domain, ruleDomain });\n return true;\n }\n\n // Suffix match (e.g., .github.com matches api.github.com)\n if (ruleDomain.startsWith(\".\")) {\n const matches =\n domain.endsWith(ruleDomain) || domain === ruleDomain.slice(1);\n log.debug(\"Domain suffix match check\", { domain, ruleDomain, matches });\n return matches;\n }\n\n log.debug(\"Domain does not match\", { domain, ruleDomain });\n return false;\n}\n\n/**\n * Checks if a path matches any of the allowed paths (prefix match)\n */\nfunction pathMatches(path: string, allowedPaths?: string[]): boolean {\n if (!allowedPaths || allowedPaths.length === 0) {\n log.debug(\"No path restrictions, all paths match\", { path });\n return true; // No path restrictions means all paths match\n }\n\n const matches = allowedPaths.some((allowedPath) =>\n path.startsWith(allowedPath)\n );\n log.debug(\"Path match check\", { path, allowedPaths, matches });\n return matches;\n}\n\n/**\n * Determines if a span should be captured based on domain rules\n */\nexport function shouldCaptureSpan(\n url: string,\n domainAllowList?: DomainRule[],\n domainDenyList?: DomainRule[]\n): boolean {\n log.debug(\"Checking domain filter rules\", {\n url,\n hasAllowList: !!domainAllowList && domainAllowList.length > 0,\n hasDenyList: !!domainDenyList && domainDenyList.length > 0,\n allowListCount: domainAllowList?.length || 0,\n denyListCount: domainDenyList?.length || 0,\n });\n\n const domain = extractDomain(url);\n\n // Extract path from URL\n let path = \"/\";\n try {\n const urlObj = new URL(url);\n path = urlObj.pathname;\n } catch {\n // If URL parsing fails, try to extract path from string\n const pathMatch = url.match(/^(?:https?:\\/\\/)?[^/]+(\\/.*)?$/);\n path = pathMatch && pathMatch[1] ? pathMatch[1] : \"/\";\n }\n\n log.debug(\"Extracted domain and path\", { url, domain, path });\n\n // Deny list is evaluated first - if domain is denied, don't capture\n if (domainDenyList) {\n for (const rule of domainDenyList) {\n if (domainMatches(domain, rule.domain)) {\n log.info(\"Domain denied by deny list\", {\n domain,\n ruleDomain: rule.domain,\n url,\n });\n return false;\n }\n }\n log.debug(\"Domain passed deny list check\", { domain });\n }\n\n // If no allow list, capture all (except denied)\n if (!domainAllowList || domainAllowList.length === 0) {\n log.debug(\"No allow list configured, capturing span\", { domain, url });\n return true;\n }\n\n // Check if domain matches any allow list rule\n for (const rule of domainAllowList) {\n if (domainMatches(domain, rule.domain)) {\n // If paths are specified, check path match\n if (rule.paths && rule.paths.length > 0) {\n const pathMatch = pathMatches(path, rule.paths);\n if (pathMatch) {\n log.info(\"Domain and path allowed by allow list\", {\n domain,\n ruleDomain: rule.domain,\n path,\n allowedPaths: rule.paths,\n url,\n });\n return true;\n } else {\n log.debug(\"Domain allowed but path not matched\", {\n domain,\n ruleDomain: rule.domain,\n path,\n allowedPaths: rule.paths,\n });\n }\n } else {\n log.info(\"Domain allowed by allow list\", {\n domain,\n ruleDomain: rule.domain,\n url,\n });\n return true;\n }\n }\n }\n\n // Domain not in allow list\n log.info(\"Domain not in allow list, filtering out\", { domain, url });\n return false;\n}\n","/**\n * Sensitive header patterns and redaction configuration\n */\n\n/**\n * Default patterns for sensitive headers that should be redacted\n * These are matched case-insensitively\n */\nexport const DEFAULT_SENSITIVE_HEADER_PATTERNS = [\n // Authentication & Authorization\n \"authorization\",\n \"www-authenticate\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"x-auth-token\",\n \"x-api-key\",\n \"x-api-token\",\n \"x-access-token\",\n \"x-auth-user\",\n \"x-auth-password\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n\n // API Keys & Access Tokens\n \"api-key\",\n \"apikey\",\n \"api_key\",\n \"access-key\",\n \"accesskey\",\n \"access_key\",\n \"secret-key\",\n \"secretkey\",\n \"secret_key\",\n \"private-key\",\n \"privatekey\",\n \"private_key\",\n\n // Session & Cookie tokens\n \"cookie\",\n \"set-cookie\",\n \"session-id\",\n \"sessionid\",\n \"session_id\",\n \"session-token\",\n \"sessiontoken\",\n \"session_token\",\n\n // OAuth & OAuth2\n \"oauth-token\",\n \"oauth_token\",\n \"oauth2-token\",\n \"oauth2_token\",\n \"bearer\",\n\n // AWS & Cloud credentials\n \"x-amz-security-token\",\n \"x-amz-signature\",\n \"x-aws-access-key\",\n \"x-aws-secret-key\",\n \"x-aws-session-token\",\n\n // Other common sensitive headers\n \"x-password\",\n \"x-secret\",\n \"x-token\",\n \"x-jwt\",\n \"x-jwt-token\",\n \"x-refresh-token\",\n \"x-client-secret\",\n \"x-client-id\",\n \"x-user-token\",\n \"x-service-key\",\n] as const;\n\n/**\n * Redaction strategies for sensitive header values\n */\nexport enum HeaderRedactionStrategy {\n /**\n * Replace the entire value with a fixed redaction string\n */\n REPLACE = \"replace\",\n /**\n * Show only the first N characters, redact the rest\n */\n PARTIAL = \"partial\",\n /**\n * Show only the last N characters, redact the rest\n */\n PARTIAL_END = \"partial_end\",\n /**\n * Remove the header entirely (same as deny list)\n */\n REMOVE = \"remove\",\n}\n\n/**\n * Configuration for header redaction\n */\nexport interface HeaderRedactionConfig {\n /**\n * Patterns to match sensitive headers (case-insensitive)\n * Defaults to DEFAULT_SENSITIVE_HEADER_PATTERNS if not provided\n */\n sensitivePatterns?: readonly string[];\n\n /**\n * Redaction strategy to use\n * @default HeaderRedactionStrategy.REPLACE\n */\n strategy?: HeaderRedactionStrategy;\n\n /**\n * Redaction string used when strategy is REPLACE\n * @default \"[REDACTED]\"\n */\n redactionString?: string;\n\n /**\n * Number of characters to show when strategy is PARTIAL or PARTIAL_END\n * @default 4\n */\n visibleChars?: number;\n\n /**\n * Whether to enable redaction\n * @default true\n */\n enabled?: boolean;\n}\n\n/**\n * Default redaction configuration\n */\nexport const DEFAULT_REDACTION_CONFIG: Required<HeaderRedactionConfig> = {\n sensitivePatterns: DEFAULT_SENSITIVE_HEADER_PATTERNS,\n strategy: HeaderRedactionStrategy.REPLACE,\n redactionString: \"[REDACTED]\",\n visibleChars: 4,\n enabled: true,\n};\n\n/**\n * Checks if a header name matches any sensitive pattern\n * Uses case-insensitive matching with exact match, prefix/suffix, and substring matching\n *\n * @param headerName - The header name to check\n * @param patterns - Array of patterns to match against (defaults to DEFAULT_SENSITIVE_HEADER_PATTERNS)\n * @returns true if the header matches any sensitive pattern\n */\nexport function isSensitiveHeader(\n headerName: string,\n patterns: readonly string[] = DEFAULT_SENSITIVE_HEADER_PATTERNS\n): boolean {\n if (!headerName || typeof headerName !== \"string\") {\n return false;\n }\n\n if (!patterns || patterns.length === 0) {\n return false;\n }\n\n const normalizedName = headerName.toLowerCase().trim();\n\n // Early return for empty string\n if (normalizedName.length === 0) {\n return false;\n }\n\n return patterns.some((pattern) => {\n if (!pattern || typeof pattern !== \"string\") {\n return false;\n }\n\n const normalizedPattern = pattern.toLowerCase().trim();\n\n // Empty pattern doesn't match\n if (normalizedPattern.length === 0) {\n return false;\n }\n\n // Exact match (most common case, check first)\n if (normalizedName === normalizedPattern) {\n return true;\n }\n\n // Check if header name contains the pattern (e.g., \"x-api-key\" contains \"api-key\")\n // This handles cases where patterns are embedded in header names\n if (normalizedName.includes(normalizedPattern)) {\n return true;\n }\n\n // Check if pattern contains the header name (for shorter patterns matching longer headers)\n // This is less common but handles edge cases\n if (normalizedPattern.includes(normalizedName)) {\n return true;\n }\n\n return false;\n });\n}\n\n/**\n * Redacts a header value based on the configuration\n */\nexport function redactHeaderValue(\n value: string | string[] | undefined,\n config: Required<HeaderRedactionConfig>\n): string | string[] | undefined {\n if (value === undefined || value === null) {\n return value;\n }\n\n // Handle array of values\n if (Array.isArray(value)) {\n return value.map((v) => redactSingleValue(v, config));\n }\n\n return redactSingleValue(value, config);\n}\n\n/**\n * Redacts a single string value based on the configured strategy\n *\n * @param value - The value to redact\n * @param config - Redaction configuration\n * @returns Redacted value\n */\nfunction redactSingleValue(\n value: string,\n config: Required<HeaderRedactionConfig>\n): string {\n // Validate input\n if (!value || typeof value !== \"string\") {\n return value;\n }\n\n // Ensure visibleChars is a positive integer\n const visibleChars = Math.max(0, Math.floor(config.visibleChars || 0));\n const trimmedValue = value.trim();\n\n // Handle empty or very short values\n if (trimmedValue.length === 0) {\n return config.redactionString;\n }\n\n switch (config.strategy) {\n case HeaderRedactionStrategy.REPLACE:\n return config.redactionString;\n\n case HeaderRedactionStrategy.PARTIAL:\n // Show first N characters, then redaction string\n if (trimmedValue.length <= visibleChars) {\n // If value is shorter than visible chars, just redact it all\n return config.redactionString;\n }\n return trimmedValue.substring(0, visibleChars) + config.redactionString;\n\n case HeaderRedactionStrategy.PARTIAL_END:\n // Show last N characters, with redaction string prefix\n if (trimmedValue.length <= visibleChars) {\n // If value is shorter than visible chars, just redact it all\n return config.redactionString;\n }\n return (\n config.redactionString +\n trimmedValue.substring(trimmedValue.length - visibleChars)\n );\n\n case HeaderRedactionStrategy.REMOVE:\n // This should be handled at the filter level, not here\n // But if we reach here, return redaction string as fallback\n return config.redactionString;\n\n default:\n // Unknown strategy - default to full redaction for safety\n return config.redactionString;\n }\n}\n","/**\n * Header filtering logic - applies allow/deny list rules and redaction\n */\n\nimport { createLogger } from \"../logger\";\nimport type { HeaderRedactionConfig } from \"./sensitive-headers\";\nimport {\n DEFAULT_REDACTION_CONFIG,\n isSensitiveHeader,\n redactHeaderValue,\n HeaderRedactionStrategy,\n} from \"./sensitive-headers\";\n\nconst log = createLogger(\"[PingOps HeaderFilter]\");\n\n/**\n * Normalizes header name to lowercase for case-insensitive matching\n */\nfunction normalizeHeaderName(name: string): string {\n return name.toLowerCase();\n}\n\n/**\n * Merges redaction config with defaults\n */\nfunction mergeRedactionConfig(\n config?: HeaderRedactionConfig\n): Required<HeaderRedactionConfig> {\n if (!config || config.enabled === false) {\n return { ...DEFAULT_REDACTION_CONFIG, enabled: false };\n }\n\n return {\n sensitivePatterns:\n config.sensitivePatterns ?? DEFAULT_REDACTION_CONFIG.sensitivePatterns,\n strategy: config.strategy ?? DEFAULT_REDACTION_CONFIG.strategy,\n redactionString:\n config.redactionString ?? DEFAULT_REDACTION_CONFIG.redactionString,\n visibleChars: config.visibleChars ?? DEFAULT_REDACTION_CONFIG.visibleChars,\n enabled: config.enabled ?? DEFAULT_REDACTION_CONFIG.enabled,\n };\n}\n\n/**\n * Filters headers based on allow/deny lists and applies redaction to sensitive headers\n * - Deny list always wins (if header is in deny list, exclude it)\n * - Allow list filters included headers (if specified, only include these)\n * - Sensitive headers are redacted after filtering (if redaction is enabled)\n * - Case-insensitive matching\n *\n * @param headers - Headers to filter\n * @param headersAllowList - Optional allow list of header names to include\n * @param headersDenyList - Optional deny list of header names to exclude\n * @param redactionConfig - Optional configuration for header value redaction\n * @returns Filtered and redacted headers\n */\nexport function filterHeaders(\n headers: Record<string, string | string[] | undefined>,\n headersAllowList?: string[],\n headersDenyList?: string[],\n redactionConfig?: HeaderRedactionConfig\n): Record<string, string | string[] | undefined> {\n const originalCount = Object.keys(headers).length;\n const redaction = mergeRedactionConfig(redactionConfig);\n\n log.debug(\"Filtering headers\", {\n originalHeaderCount: originalCount,\n hasAllowList: !!headersAllowList && headersAllowList.length > 0,\n hasDenyList: !!headersDenyList && headersDenyList.length > 0,\n allowListCount: headersAllowList?.length || 0,\n denyListCount: headersDenyList?.length || 0,\n redactionEnabled: redaction.enabled,\n redactionStrategy: redaction.strategy,\n });\n\n const normalizedDenyList = headersDenyList?.map(normalizeHeaderName) ?? [];\n const normalizedAllowList = headersAllowList?.map(normalizeHeaderName) ?? [];\n\n const filtered: Record<string, string | string[] | undefined> = {};\n const deniedHeaders: string[] = [];\n const excludedHeaders: string[] = [];\n const redactedHeaders: string[] = [];\n\n for (const [name, value] of Object.entries(headers)) {\n const normalizedName = normalizeHeaderName(name);\n\n // Deny list always wins\n if (normalizedDenyList.includes(normalizedName)) {\n deniedHeaders.push(name);\n log.debug(\"Header denied by deny list\", { headerName: name });\n continue;\n }\n\n // If allow list exists, only include headers in the list\n if (normalizedAllowList.length > 0) {\n if (!normalizedAllowList.includes(normalizedName)) {\n excludedHeaders.push(name);\n log.debug(\"Header excluded (not in allow list)\", { headerName: name });\n continue;\n }\n }\n\n // Apply redaction if enabled and header is sensitive\n let finalValue = value;\n if (redaction.enabled) {\n try {\n // Check if header matches sensitive patterns\n if (isSensitiveHeader(name, redaction.sensitivePatterns)) {\n // Handle REMOVE strategy at filter level\n if (redaction.strategy === HeaderRedactionStrategy.REMOVE) {\n log.debug(\"Header removed by redaction strategy\", {\n headerName: name,\n });\n continue;\n }\n\n // Redact the value\n finalValue = redactHeaderValue(value, redaction);\n redactedHeaders.push(name);\n log.debug(\"Header value redacted\", {\n headerName: name,\n strategy: redaction.strategy,\n });\n }\n } catch (error) {\n // Log error but don't fail - use original value as fallback\n log.warn(\"Error redacting header value\", {\n headerName: name,\n error: error instanceof Error ? error.message : String(error),\n });\n finalValue = value;\n }\n }\n\n filtered[name] = finalValue;\n }\n\n const filteredCount = Object.keys(filtered).length;\n log.info(\"Header filtering complete\", {\n originalCount,\n filteredCount,\n deniedCount: deniedHeaders.length,\n excludedCount: excludedHeaders.length,\n redactedCount: redactedHeaders.length,\n deniedHeaders: deniedHeaders.length > 0 ? deniedHeaders : undefined,\n excludedHeaders: excludedHeaders.length > 0 ? excludedHeaders : undefined,\n redactedHeaders: redactedHeaders.length > 0 ? redactedHeaders : undefined,\n });\n\n return filtered;\n}\n\n/**\n * Extracts and normalizes headers from OpenTelemetry span attributes\n *\n * Handles flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1')\n * and converts them to proper key-value objects.\n *\n * Some OpenTelemetry instrumentations store headers as flat arrays:\n * - 'http.request.header.0': 'Content-Type'\n * - 'http.request.header.1': 'application/json'\n * - 'http.request.header.2': 'Authorization'\n * - 'http.request.header.3': 'Bearer token'\n *\n * This function converts them to:\n * - { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }\n */\nexport function extractHeadersFromAttributes(\n attributes: Record<string, unknown>,\n headerPrefix: \"http.request.header\" | \"http.response.header\"\n): Record<string, string | string[] | undefined> | null {\n const headerMap: Record<string, string | string[] | undefined> = {};\n const headerKeys: number[] = [];\n\n // Find all keys matching the pattern (e.g., 'http.request.header.0', 'http.request.header.1', etc.)\n for (const key in attributes) {\n if (key.startsWith(`${headerPrefix}.`) && key !== headerPrefix) {\n const match = key.match(new RegExp(`^${headerPrefix}\\\\.(\\\\d+)$`));\n if (match) {\n const index = parseInt(match[1], 10);\n headerKeys.push(index);\n }\n }\n }\n\n // If no flat array headers found, return null\n if (headerKeys.length === 0) {\n return null;\n }\n\n // Sort indices to process in order\n headerKeys.sort((a, b) => a - b);\n\n // Convert flat array to key-value pairs\n // Even indices are header names, odd indices are header values\n for (let i = 0; i < headerKeys.length; i += 2) {\n const nameIndex = headerKeys[i];\n const valueIndex = headerKeys[i + 1];\n\n if (valueIndex !== undefined) {\n const nameKey = `${headerPrefix}.${nameIndex}`;\n const valueKey = `${headerPrefix}.${valueIndex}`;\n\n const headerName = attributes[nameKey] as string | undefined;\n const headerValue = attributes[valueKey] as string | undefined;\n\n if (headerName && headerValue !== undefined) {\n // Handle multiple values for the same header name (case-insensitive)\n const normalizedName = headerName.toLowerCase();\n const existingKey = Object.keys(headerMap).find(\n (k) => k.toLowerCase() === normalizedName\n );\n\n if (existingKey) {\n const existing = headerMap[existingKey];\n headerMap[existingKey] = Array.isArray(existing)\n ? [...existing, headerValue]\n : [existing as string, headerValue];\n } else {\n // Use original case for the first occurrence\n headerMap[headerName] = headerValue;\n }\n }\n }\n }\n\n return Object.keys(headerMap).length > 0 ? headerMap : null;\n}\n\n/**\n * Type guard to check if value is a Headers-like object\n */\nfunction isHeadersLike(\n headers: unknown\n): headers is { entries: () => IterableIterator<[string, string]> } {\n return (\n typeof headers === \"object\" &&\n headers !== null &&\n \"entries\" in headers &&\n typeof (headers as { entries?: unknown }).entries === \"function\"\n );\n}\n\n/**\n * Normalizes headers from various sources into a proper key-value object\n */\nexport function normalizeHeaders(\n headers: unknown\n): Record<string, string | string[] | undefined> {\n const result: Record<string, string | string[] | undefined> = {};\n\n if (!headers) {\n return result;\n }\n\n try {\n // Handle Headers object (from fetch/undici)\n if (isHeadersLike(headers)) {\n for (const [key, value] of headers.entries()) {\n // Headers can have multiple values for the same key\n if (result[key]) {\n // Convert to array if not already\n const existing = result[key];\n result[key] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n // Handle plain object\n if (typeof headers === \"object\" && !Array.isArray(headers)) {\n for (const [key, value] of Object.entries(headers)) {\n // Skip numeric keys (array-like objects)\n if (!/^\\d+$/.test(key)) {\n result[key] = value as string | string[] | undefined;\n }\n }\n return result;\n }\n\n // Handle array (shouldn't happen, but handle gracefully)\n if (Array.isArray(headers)) {\n // Try to reconstruct from array pairs\n for (let i = 0; i < headers.length; i += 2) {\n if (i + 1 < headers.length) {\n const key = String(headers[i]);\n const value = headers[i + 1] as string | string[] | undefined;\n result[key] = value;\n }\n }\n return result;\n }\n } catch {\n // Fail silently - return empty object\n }\n\n return result;\n}\n","/**\n * Extracts structured data from spans for PingOps backend\n */\n\nimport type { ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport type { DomainRule, SpanPayload } from \"../types\";\nimport type { HeaderRedactionConfig } from \"../filtering/sensitive-headers\";\nimport {\n filterHeaders,\n extractHeadersFromAttributes,\n} from \"../filtering/header-filter\";\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 body should be captured based on priority:\n * domain rule > global config > default (false)\n */\nfunction shouldCaptureBody(\n domainRule: DomainRule | undefined,\n globalConfig: boolean | undefined,\n bodyType: \"request\" | \"response\"\n): boolean {\n // Check domain-specific rule first\n if (domainRule) {\n const domainValue =\n bodyType === \"request\"\n ? domainRule.captureRequestBody\n : domainRule.captureResponseBody;\n if (domainValue !== undefined) {\n return domainValue;\n }\n }\n // Fall back to global config\n if (globalConfig !== undefined) {\n return globalConfig;\n }\n // Default to false\n return false;\n}\n\n/**\n * Extracts structured payload from a span\n */\nexport function extractSpanPayload(\n span: ReadableSpan,\n domainAllowList?: DomainRule[],\n globalHeadersAllowList?: string[],\n globalHeadersDenyList?: string[],\n globalCaptureRequestBody?: boolean,\n globalCaptureResponseBody?: boolean,\n headerRedaction?: HeaderRedactionConfig\n): SpanPayload | null {\n const attributes = span.attributes;\n const url =\n (attributes[\"http.url\"] as string) || (attributes[\"url.full\"] as string);\n\n // Get domain-specific rule if available\n const domainRule = url ? getDomainRule(url, domainAllowList) : undefined;\n\n // Merge global and domain-specific header rules\n const headersAllowList =\n domainRule?.headersAllowList ?? globalHeadersAllowList;\n const headersDenyList = domainRule?.headersDenyList ?? globalHeadersDenyList;\n\n // Determine if bodies should be captured\n const shouldCaptureReqBody = shouldCaptureBody(\n domainRule,\n globalCaptureRequestBody,\n \"request\"\n );\n const shouldCaptureRespBody = shouldCaptureBody(\n domainRule,\n globalCaptureResponseBody,\n \"response\"\n );\n\n // Extract HTTP headers if available\n let requestHeaders: Record<string, string | string[] | undefined> = {};\n let responseHeaders: Record<string, string | string[] | undefined> = {};\n\n // First, try to extract flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1')\n const flatRequestHeaders = extractHeadersFromAttributes(\n attributes,\n \"http.request.header\"\n );\n const flatResponseHeaders = extractHeadersFromAttributes(\n attributes,\n \"http.response.header\"\n );\n\n // Try to get headers from attributes (format may vary by instrumentation)\n const httpRequestHeadersValue = attributes[\"http.request.header\"];\n const httpResponseHeadersValue = attributes[\"http.response.header\"];\n\n // Type guard: check if value is a record/object with string keys\n const isHeadersRecord = (\n value: unknown\n ): value is Record<string, string | string[] | undefined> => {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n Object.values(value).every(\n (v) =>\n typeof v === \"string\" ||\n (Array.isArray(v) && v.every((item) => typeof item === \"string\")) ||\n v === undefined\n )\n );\n };\n\n // Use flat array format if available, otherwise use direct attribute\n if (flatRequestHeaders) {\n requestHeaders = filterHeaders(\n flatRequestHeaders,\n headersAllowList,\n headersDenyList,\n headerRedaction\n );\n } else if (isHeadersRecord(httpRequestHeadersValue)) {\n requestHeaders = filterHeaders(\n httpRequestHeadersValue,\n headersAllowList,\n headersDenyList,\n headerRedaction\n );\n }\n\n if (flatResponseHeaders) {\n responseHeaders = filterHeaders(\n flatResponseHeaders,\n headersAllowList,\n headersDenyList,\n headerRedaction\n );\n } else if (isHeadersRecord(httpResponseHeadersValue)) {\n responseHeaders = filterHeaders(\n httpResponseHeadersValue,\n headersAllowList,\n headersDenyList,\n headerRedaction\n );\n }\n\n // Build attributes object\n const extractedAttributes: Record<string, unknown> = {\n ...attributes,\n };\n\n // Remove flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1', etc.)\n // We'll replace them with the proper key-value format\n for (const key in extractedAttributes) {\n if (\n (key.startsWith(\"http.request.header.\") &&\n key !== \"http.request.header\") ||\n (key.startsWith(\"http.response.header.\") &&\n key !== \"http.response.header\")\n ) {\n // Check if it's a numeric index (flat array format)\n const match = key.match(/^(http\\.(?:request|response)\\.header)\\.(\\d+)$/);\n if (match) {\n delete extractedAttributes[key];\n }\n }\n }\n\n // Add filtered headers in proper key-value format\n if (Object.keys(requestHeaders).length > 0) {\n extractedAttributes[\"http.request.header\"] = requestHeaders;\n }\n\n if (Object.keys(responseHeaders).length > 0) {\n extractedAttributes[\"http.response.header\"] = responseHeaders;\n }\n\n // Remove body attributes if capture is disabled\n if (!shouldCaptureReqBody) {\n delete extractedAttributes[\"http.request.body\"];\n }\n\n if (!shouldCaptureRespBody) {\n delete extractedAttributes[\"http.response.body\"];\n }\n\n // Build span payload\n const spanContext = span.spanContext();\n // parentSpanId may not be available in all versions of ReadableSpan\n const parentSpanId =\n \"parentSpanId\" in span\n ? (span as ReadableSpan & { parentSpanId?: string }).parentSpanId\n : undefined;\n return {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n parentSpanId,\n name: span.name,\n kind: span.kind.toString(),\n startTime: new Date(\n span.startTime[0] * 1000 + span.startTime[1] / 1000000\n ).toISOString(),\n endTime: new Date(\n span.endTime[0] * 1000 + span.endTime[1] / 1000000\n ).toISOString(),\n duration:\n (span.endTime[0] - span.startTime[0]) * 1000 +\n (span.endTime[1] - span.startTime[1]) / 1000000,\n attributes: extractedAttributes,\n status: {\n code: span.status.code.toString(),\n message: span.status.message,\n },\n };\n}\n","/**\n * OpenTelemetry context keys for PingOps\n */\n\nimport { createContextKey } from \"@opentelemetry/api\";\n\n/**\n * Context key for enabling HTTP instrumentation.\n * When set to true, HTTP requests will be automatically instrumented.\n * This allows wrapHttp to control which HTTP calls are captured.\n */\nexport const PINGOPS_HTTP_ENABLED = createContextKey(\"pingops-http-enabled\");\n\n/**\n * Context key for user ID attribute.\n * Used to propagate user identifier to all spans in the context.\n */\nexport const PINGOPS_USER_ID = createContextKey(\"pingops-user-id\");\n\n/**\n * Context key for session ID attribute.\n * Used to propagate session identifier to all spans in the context.\n */\nexport const PINGOPS_SESSION_ID = createContextKey(\"pingops-session-id\");\n\n/**\n * Context key for tags attribute.\n * Used to propagate tags array to all spans in the context.\n */\nexport const PINGOPS_TAGS = createContextKey(\"pingops-tags\");\n\n/**\n * Context key for metadata attribute.\n * Used to propagate metadata object to all spans in the context.\n */\nexport const PINGOPS_METADATA = createContextKey(\"pingops-metadata\");\n\n/**\n * Context key for capturing request body.\n * When set, controls whether request bodies should be captured for HTTP spans.\n * This allows wrapHttp to control body capture per-request.\n */\nexport const PINGOPS_CAPTURE_REQUEST_BODY = createContextKey(\n \"pingops-capture-request-body\"\n);\n\n/**\n * Context key for capturing response body.\n * When set, controls whether response bodies should be captured for HTTP spans.\n * This allows wrapHttp to control body capture per-request.\n */\nexport const PINGOPS_CAPTURE_RESPONSE_BODY = createContextKey(\n \"pingops-capture-response-body\"\n);\n","/**\n * Extracts propagated attributes from OpenTelemetry context\n */\n\nimport type { Context } from \"@opentelemetry/api\";\nimport {\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n} from \"../context-keys\";\n\n/**\n * Extracts propagated attributes from the given context and returns them\n * as span attributes that can be set on a span.\n *\n * @param parentContext - The OpenTelemetry context to extract attributes from\n * @returns Record of attribute key-value pairs to set on spans\n */\nexport function getPropagatedAttributesFromContext(\n parentContext: Context\n): Record<string, string | string[]> {\n const attributes: Record<string, string | string[]> = {};\n\n // Extract userId\n const userId = parentContext.getValue(PINGOPS_USER_ID);\n if (userId !== undefined && typeof userId === \"string\") {\n attributes[\"pingops.user_id\"] = userId;\n }\n\n // Extract sessionId\n const sessionId = parentContext.getValue(PINGOPS_SESSION_ID);\n if (sessionId !== undefined && typeof sessionId === \"string\") {\n attributes[\"pingops.session_id\"] = sessionId;\n }\n\n // Extract tags\n const tags = parentContext.getValue(PINGOPS_TAGS);\n if (tags !== undefined && Array.isArray(tags)) {\n attributes[\"pingops.tags\"] = tags;\n }\n\n // Extract metadata\n const metadata = parentContext.getValue(PINGOPS_METADATA);\n if (\n metadata !== undefined &&\n typeof metadata === \"object\" &&\n metadata !== null &&\n !Array.isArray(metadata)\n ) {\n // Flatten metadata object into span attributes with prefix\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n attributes[`pingops.metadata.${key}`] = value;\n }\n }\n }\n\n return attributes;\n}\n","/**\n * wrapHttp - Wraps a function to set attributes on HTTP spans created within the wrapped block.\n *\n * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry\n * context, which are automatically propagated to all spans created within the wrapped function.\n *\n * Instrumentation behavior:\n * - If `initializePingops` was called: All HTTP requests are instrumented by default.\n * `wrapHttp` only adds attributes to spans created within the wrapped block.\n * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks\n * are instrumented. Requests outside `wrapHttp` are not instrumented.\n */\n\nimport { context } from \"@opentelemetry/api\";\nimport { createLogger } from \"./logger\";\nimport {\n PINGOPS_HTTP_ENABLED,\n PINGOPS_USER_ID,\n PINGOPS_SESSION_ID,\n PINGOPS_TAGS,\n PINGOPS_METADATA,\n PINGOPS_CAPTURE_REQUEST_BODY,\n PINGOPS_CAPTURE_RESPONSE_BODY,\n} from \"./context-keys\";\nimport type { WrapHttpAttributes } from \"./types\";\n\nconst logger = createLogger(\"[PingOps wrapHttp]\");\n\n/**\n * Options for wrapHttp function\n */\nexport interface WrapHttpOptions {\n attributes?: WrapHttpAttributes;\n /**\n * Callback to check if SDK is initialized.\n * Required to determine if global instrumentation is enabled.\n */\n checkInitialized: () => boolean;\n /**\n * Callback to check if global instrumentation is enabled.\n * Required to determine instrumentation behavior.\n */\n isGlobalInstrumentationEnabled: () => boolean;\n /**\n * Optional callback to ensure SDK is initialized (auto-initialization).\n * If not provided, wrapHttp will try to auto-initialize from environment variables.\n */\n ensureInitialized?: () => Promise<void>;\n}\n\n/**\n * Wraps a function to set attributes on HTTP spans created within the wrapped block.\n *\n * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry\n * context, which are automatically propagated to all spans created within the wrapped function.\n *\n * Instrumentation behavior:\n * - If `initializePingops` was called: All HTTP requests are instrumented by default.\n * `wrapHttp` only adds attributes to spans created within the wrapped block.\n * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks\n * are instrumented. Requests outside `wrapHttp` are not instrumented.\n *\n * Note: This is the low-level API. For a simpler API with automatic setup,\n * use `wrapHttp` from `@pingops/sdk` instead.\n *\n * @param options - Options including attributes and required callbacks\n * @param fn - Function to execute within the attribute context\n * @returns The result of the function\n */\nexport function wrapHttp<T>(\n options: WrapHttpOptions,\n fn: () => T | Promise<T>\n): T | Promise<T> {\n logger.debug(\"wrapHttp called\", {\n hasAttributes: !!options.attributes,\n hasUserId: !!options.attributes?.userId,\n hasSessionId: !!options.attributes?.sessionId,\n hasTags: !!options.attributes?.tags,\n hasMetadata: !!options.attributes?.metadata,\n });\n\n // Normalize options - if just attributes provided, it means callbacks are required\n // This is a type error at compile time, but we handle it gracefully\n const normalizedOptions: WrapHttpOptions =\n \"checkInitialized\" in options && \"isGlobalInstrumentationEnabled\" in options\n ? options\n : (() => {\n throw new Error(\n \"wrapHttp requires checkInitialized and isGlobalInstrumentationEnabled callbacks. Use wrapHttp from @pingops/sdk for automatic setup.\"\n );\n })();\n\n const { checkInitialized, ensureInitialized } = normalizedOptions;\n\n // Ensure SDK is initialized so that span processor can extract attributes\n // If already initialized, execute synchronously\n if (checkInitialized()) {\n logger.debug(\"SDK already initialized, executing wrapHttp synchronously\");\n return executeWrapHttpWithContext(normalizedOptions, fn);\n }\n\n // If not initialized, we need to initialize first (async)\n if (ensureInitialized) {\n logger.debug(\n \"SDK not initialized, using provided ensureInitialized callback\"\n );\n return ensureInitialized()\n .then(() => {\n logger.debug(\"SDK initialized, executing wrapHttp\");\n return executeWrapHttpWithContext(normalizedOptions, fn);\n })\n .catch((error) => {\n logger.error(\"Failed to initialize SDK for wrapHttp\", {\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n });\n }\n\n // No ensureInitialized callback provided, execute without initialization\n logger.debug(\n \"SDK not initialized and no ensureInitialized callback provided, executing wrapHttp\"\n );\n return executeWrapHttpWithContext(normalizedOptions, fn);\n}\n\nfunction executeWrapHttpWithContext<T>(\n options: WrapHttpOptions,\n fn: () => T | Promise<T>\n): T | Promise<T> {\n const { attributes, isGlobalInstrumentationEnabled } = options;\n const globalInstrumentationEnabled = isGlobalInstrumentationEnabled();\n\n logger.debug(\"Executing wrapHttp context\", {\n hasAttributes: !!attributes,\n globalInstrumentationEnabled,\n });\n\n const activeContext = context.active();\n\n // If global instrumentation is not enabled, enable HTTP instrumentation\n // for this block only. If it's enabled, all requests are already instrumented.\n let contextWithAttributes = activeContext;\n if (!globalInstrumentationEnabled) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_HTTP_ENABLED,\n true\n );\n }\n\n // Set attributes in context if provided\n // These will be propagated to all spans created within the wrapped function\n if (attributes) {\n if (attributes.userId !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_USER_ID,\n attributes.userId\n );\n }\n if (attributes.sessionId !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_SESSION_ID,\n attributes.sessionId\n );\n }\n if (attributes.tags !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_TAGS,\n attributes.tags\n );\n }\n if (attributes.metadata !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_METADATA,\n attributes.metadata\n );\n }\n if (attributes.captureRequestBody !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_CAPTURE_REQUEST_BODY,\n attributes.captureRequestBody\n );\n }\n if (attributes.captureResponseBody !== undefined) {\n contextWithAttributes = contextWithAttributes.setValue(\n PINGOPS_CAPTURE_RESPONSE_BODY,\n attributes.captureResponseBody\n );\n }\n }\n\n // Run user code inside the context with attributes\n return context.with(contextWithAttributes, () => {\n try {\n const result = fn();\n\n if (result instanceof Promise) {\n return result.catch((err) => {\n logger.error(\"Error in wrapHttp async execution\", {\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n });\n }\n\n return result;\n } catch (err) {\n logger.error(\"Error in wrapHttp sync execution\", {\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,306 @@
1
+ import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
2
+ import { Context } from "@opentelemetry/api";
3
+
4
+ //#region src/types.d.ts
5
+ /**
6
+ * Shared type definitions for PingOps SDK
7
+ */
8
+ interface DomainRule {
9
+ domain: string;
10
+ paths?: string[];
11
+ headersAllowList?: string[];
12
+ headersDenyList?: string[];
13
+ captureRequestBody?: boolean;
14
+ captureResponseBody?: boolean;
15
+ }
16
+ interface SpanPayload {
17
+ traceId: string;
18
+ spanId: string;
19
+ parentSpanId?: string;
20
+ name: string;
21
+ kind: string;
22
+ startTime: string;
23
+ endTime: string;
24
+ duration: number;
25
+ attributes: Record<string, unknown>;
26
+ status: {
27
+ code: string;
28
+ message?: string;
29
+ };
30
+ }
31
+ /**
32
+ * Attributes to propagate to HTTP spans
33
+ */
34
+ interface WrapHttpAttributes {
35
+ userId?: string;
36
+ sessionId?: string;
37
+ tags?: string[];
38
+ metadata?: Record<string, string>;
39
+ /**
40
+ * Whether to capture request body for HTTP spans in this context.
41
+ * Takes precedence over domain-specific rules and global config.
42
+ */
43
+ captureRequestBody?: boolean;
44
+ /**
45
+ * Whether to capture response body for HTTP spans in this context.
46
+ * Takes precedence over domain-specific rules and global config.
47
+ */
48
+ captureResponseBody?: boolean;
49
+ }
50
+ //#endregion
51
+ //#region src/filtering/span-filter.d.ts
52
+ /**
53
+ * Checks if a span is eligible for capture based on span kind and attributes.
54
+ * A span is eligible if:
55
+ * 1. span.kind === SpanKind.CLIENT
56
+ * 2. AND has HTTP attributes (http.method, http.url, or server.address)
57
+ * OR has GenAI attributes (gen_ai.system, gen_ai.operation.name)
58
+ */
59
+ declare function isSpanEligible(span: ReadableSpan): boolean;
60
+ //#endregion
61
+ //#region src/filtering/domain-filter.d.ts
62
+ /**
63
+ * Determines if a span should be captured based on domain rules
64
+ */
65
+ declare function shouldCaptureSpan(url: string, domainAllowList?: DomainRule[], domainDenyList?: DomainRule[]): boolean;
66
+ //#endregion
67
+ //#region src/filtering/sensitive-headers.d.ts
68
+ /**
69
+ * Sensitive header patterns and redaction configuration
70
+ */
71
+ /**
72
+ * Default patterns for sensitive headers that should be redacted
73
+ * These are matched case-insensitively
74
+ */
75
+ declare const DEFAULT_SENSITIVE_HEADER_PATTERNS: readonly ["authorization", "www-authenticate", "proxy-authenticate", "proxy-authorization", "x-auth-token", "x-api-key", "x-api-token", "x-access-token", "x-auth-user", "x-auth-password", "x-csrf-token", "x-xsrf-token", "api-key", "apikey", "api_key", "access-key", "accesskey", "access_key", "secret-key", "secretkey", "secret_key", "private-key", "privatekey", "private_key", "cookie", "set-cookie", "session-id", "sessionid", "session_id", "session-token", "sessiontoken", "session_token", "oauth-token", "oauth_token", "oauth2-token", "oauth2_token", "bearer", "x-amz-security-token", "x-amz-signature", "x-aws-access-key", "x-aws-secret-key", "x-aws-session-token", "x-password", "x-secret", "x-token", "x-jwt", "x-jwt-token", "x-refresh-token", "x-client-secret", "x-client-id", "x-user-token", "x-service-key"];
76
+ /**
77
+ * Redaction strategies for sensitive header values
78
+ */
79
+ declare enum HeaderRedactionStrategy {
80
+ /**
81
+ * Replace the entire value with a fixed redaction string
82
+ */
83
+ REPLACE = "replace",
84
+ /**
85
+ * Show only the first N characters, redact the rest
86
+ */
87
+ PARTIAL = "partial",
88
+ /**
89
+ * Show only the last N characters, redact the rest
90
+ */
91
+ PARTIAL_END = "partial_end",
92
+ /**
93
+ * Remove the header entirely (same as deny list)
94
+ */
95
+ REMOVE = "remove",
96
+ }
97
+ /**
98
+ * Configuration for header redaction
99
+ */
100
+ interface HeaderRedactionConfig {
101
+ /**
102
+ * Patterns to match sensitive headers (case-insensitive)
103
+ * Defaults to DEFAULT_SENSITIVE_HEADER_PATTERNS if not provided
104
+ */
105
+ sensitivePatterns?: readonly string[];
106
+ /**
107
+ * Redaction strategy to use
108
+ * @default HeaderRedactionStrategy.REPLACE
109
+ */
110
+ strategy?: HeaderRedactionStrategy;
111
+ /**
112
+ * Redaction string used when strategy is REPLACE
113
+ * @default "[REDACTED]"
114
+ */
115
+ redactionString?: string;
116
+ /**
117
+ * Number of characters to show when strategy is PARTIAL or PARTIAL_END
118
+ * @default 4
119
+ */
120
+ visibleChars?: number;
121
+ /**
122
+ * Whether to enable redaction
123
+ * @default true
124
+ */
125
+ enabled?: boolean;
126
+ }
127
+ /**
128
+ * Default redaction configuration
129
+ */
130
+ declare const DEFAULT_REDACTION_CONFIG: Required<HeaderRedactionConfig>;
131
+ /**
132
+ * Checks if a header name matches any sensitive pattern
133
+ * Uses case-insensitive matching with exact match, prefix/suffix, and substring matching
134
+ *
135
+ * @param headerName - The header name to check
136
+ * @param patterns - Array of patterns to match against (defaults to DEFAULT_SENSITIVE_HEADER_PATTERNS)
137
+ * @returns true if the header matches any sensitive pattern
138
+ */
139
+ declare function isSensitiveHeader(headerName: string, patterns?: readonly string[]): boolean;
140
+ /**
141
+ * Redacts a header value based on the configuration
142
+ */
143
+ declare function redactHeaderValue(value: string | string[] | undefined, config: Required<HeaderRedactionConfig>): string | string[] | undefined;
144
+ //#endregion
145
+ //#region src/filtering/header-filter.d.ts
146
+ /**
147
+ * Filters headers based on allow/deny lists and applies redaction to sensitive headers
148
+ * - Deny list always wins (if header is in deny list, exclude it)
149
+ * - Allow list filters included headers (if specified, only include these)
150
+ * - Sensitive headers are redacted after filtering (if redaction is enabled)
151
+ * - Case-insensitive matching
152
+ *
153
+ * @param headers - Headers to filter
154
+ * @param headersAllowList - Optional allow list of header names to include
155
+ * @param headersDenyList - Optional deny list of header names to exclude
156
+ * @param redactionConfig - Optional configuration for header value redaction
157
+ * @returns Filtered and redacted headers
158
+ */
159
+ declare function filterHeaders(headers: Record<string, string | string[] | undefined>, headersAllowList?: string[], headersDenyList?: string[], redactionConfig?: HeaderRedactionConfig): Record<string, string | string[] | undefined>;
160
+ /**
161
+ * Extracts and normalizes headers from OpenTelemetry span attributes
162
+ *
163
+ * Handles flat array format headers (e.g., 'http.request.header.0', 'http.request.header.1')
164
+ * and converts them to proper key-value objects.
165
+ *
166
+ * Some OpenTelemetry instrumentations store headers as flat arrays:
167
+ * - 'http.request.header.0': 'Content-Type'
168
+ * - 'http.request.header.1': 'application/json'
169
+ * - 'http.request.header.2': 'Authorization'
170
+ * - 'http.request.header.3': 'Bearer token'
171
+ *
172
+ * This function converts them to:
173
+ * - { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' }
174
+ */
175
+ declare function extractHeadersFromAttributes(attributes: Record<string, unknown>, headerPrefix: "http.request.header" | "http.response.header"): Record<string, string | string[] | undefined> | null;
176
+ /**
177
+ * Normalizes headers from various sources into a proper key-value object
178
+ */
179
+ declare function normalizeHeaders(headers: unknown): Record<string, string | string[] | undefined>;
180
+ //#endregion
181
+ //#region src/utils/span-extractor.d.ts
182
+ /**
183
+ * Extracts structured payload from a span
184
+ */
185
+ declare function extractSpanPayload(span: ReadableSpan, domainAllowList?: DomainRule[], globalHeadersAllowList?: string[], globalHeadersDenyList?: string[], globalCaptureRequestBody?: boolean, globalCaptureResponseBody?: boolean, headerRedaction?: HeaderRedactionConfig): SpanPayload | null;
186
+ //#endregion
187
+ //#region src/utils/context-extractor.d.ts
188
+ /**
189
+ * Extracts propagated attributes from the given context and returns them
190
+ * as span attributes that can be set on a span.
191
+ *
192
+ * @param parentContext - The OpenTelemetry context to extract attributes from
193
+ * @returns Record of attribute key-value pairs to set on spans
194
+ */
195
+ declare function getPropagatedAttributesFromContext(parentContext: Context): Record<string, string | string[]>;
196
+ //#endregion
197
+ //#region src/logger.d.ts
198
+ /**
199
+ * Global logger utility for PingOps Core
200
+ *
201
+ * Provides consistent logging across all core components with support for
202
+ * different log levels and debug mode control via PINGOPS_DEBUG environment variable.
203
+ */
204
+ type LogLevel = "debug" | "info" | "warn" | "error";
205
+ interface Logger {
206
+ debug(message: string, ...args: unknown[]): void;
207
+ info(message: string, ...args: unknown[]): void;
208
+ warn(message: string, ...args: unknown[]): void;
209
+ error(message: string, ...args: unknown[]): void;
210
+ }
211
+ /**
212
+ * Creates a logger instance with a specific prefix
213
+ *
214
+ * @param prefix - Prefix to add to all log messages (e.g., '[PingOps Filter]')
215
+ * @returns Logger instance
216
+ */
217
+ declare function createLogger(prefix: string): Logger;
218
+ //#endregion
219
+ //#region src/context-keys.d.ts
220
+ /**
221
+ * OpenTelemetry context keys for PingOps
222
+ */
223
+ /**
224
+ * Context key for enabling HTTP instrumentation.
225
+ * When set to true, HTTP requests will be automatically instrumented.
226
+ * This allows wrapHttp to control which HTTP calls are captured.
227
+ */
228
+ declare const PINGOPS_HTTP_ENABLED: symbol;
229
+ /**
230
+ * Context key for user ID attribute.
231
+ * Used to propagate user identifier to all spans in the context.
232
+ */
233
+ declare const PINGOPS_USER_ID: symbol;
234
+ /**
235
+ * Context key for session ID attribute.
236
+ * Used to propagate session identifier to all spans in the context.
237
+ */
238
+ declare const PINGOPS_SESSION_ID: symbol;
239
+ /**
240
+ * Context key for tags attribute.
241
+ * Used to propagate tags array to all spans in the context.
242
+ */
243
+ declare const PINGOPS_TAGS: symbol;
244
+ /**
245
+ * Context key for metadata attribute.
246
+ * Used to propagate metadata object to all spans in the context.
247
+ */
248
+ declare const PINGOPS_METADATA: symbol;
249
+ /**
250
+ * Context key for capturing request body.
251
+ * When set, controls whether request bodies should be captured for HTTP spans.
252
+ * This allows wrapHttp to control body capture per-request.
253
+ */
254
+ declare const PINGOPS_CAPTURE_REQUEST_BODY: symbol;
255
+ /**
256
+ * Context key for capturing response body.
257
+ * When set, controls whether response bodies should be captured for HTTP spans.
258
+ * This allows wrapHttp to control body capture per-request.
259
+ */
260
+ declare const PINGOPS_CAPTURE_RESPONSE_BODY: symbol;
261
+ //#endregion
262
+ //#region src/wrap-http.d.ts
263
+ /**
264
+ * Options for wrapHttp function
265
+ */
266
+ interface WrapHttpOptions {
267
+ attributes?: WrapHttpAttributes;
268
+ /**
269
+ * Callback to check if SDK is initialized.
270
+ * Required to determine if global instrumentation is enabled.
271
+ */
272
+ checkInitialized: () => boolean;
273
+ /**
274
+ * Callback to check if global instrumentation is enabled.
275
+ * Required to determine instrumentation behavior.
276
+ */
277
+ isGlobalInstrumentationEnabled: () => boolean;
278
+ /**
279
+ * Optional callback to ensure SDK is initialized (auto-initialization).
280
+ * If not provided, wrapHttp will try to auto-initialize from environment variables.
281
+ */
282
+ ensureInitialized?: () => Promise<void>;
283
+ }
284
+ /**
285
+ * Wraps a function to set attributes on HTTP spans created within the wrapped block.
286
+ *
287
+ * This function sets attributes (userId, sessionId, tags, metadata) in the OpenTelemetry
288
+ * context, which are automatically propagated to all spans created within the wrapped function.
289
+ *
290
+ * Instrumentation behavior:
291
+ * - If `initializePingops` was called: All HTTP requests are instrumented by default.
292
+ * `wrapHttp` only adds attributes to spans created within the wrapped block.
293
+ * - If `initializePingops` was NOT called: Only HTTP requests within `wrapHttp` blocks
294
+ * are instrumented. Requests outside `wrapHttp` are not instrumented.
295
+ *
296
+ * Note: This is the low-level API. For a simpler API with automatic setup,
297
+ * use `wrapHttp` from `@pingops/sdk` instead.
298
+ *
299
+ * @param options - Options including attributes and required callbacks
300
+ * @param fn - Function to execute within the attribute context
301
+ * @returns The result of the function
302
+ */
303
+ declare function wrapHttp<T>(options: WrapHttpOptions, fn: () => T | Promise<T>): T | Promise<T>;
304
+ //#endregion
305
+ export { DEFAULT_REDACTION_CONFIG, DEFAULT_SENSITIVE_HEADER_PATTERNS, DomainRule, HeaderRedactionConfig, HeaderRedactionStrategy, LogLevel, Logger, PINGOPS_CAPTURE_REQUEST_BODY, PINGOPS_CAPTURE_RESPONSE_BODY, PINGOPS_HTTP_ENABLED, PINGOPS_METADATA, PINGOPS_SESSION_ID, PINGOPS_TAGS, PINGOPS_USER_ID, SpanPayload, WrapHttpAttributes, WrapHttpOptions, createLogger, extractHeadersFromAttributes, extractSpanPayload, filterHeaders, getPropagatedAttributesFromContext, isSensitiveHeader, isSpanEligible, normalizeHeaders, redactHeaderValue, shouldCaptureSpan, wrapHttp };
306
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/filtering/span-filter.ts","../src/filtering/domain-filter.ts","../src/filtering/sensitive-headers.ts","../src/filtering/header-filter.ts","../src/utils/span-extractor.ts","../src/utils/context-extractor.ts","../src/logger.ts","../src/context-keys.ts","../src/wrap-http.ts"],"sourcesContent":[],"mappings":"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"}