|
| 1 | +import{trace}from'@opentelemetry/api'; |
| 2 | +import{OTLPTraceExporter}from'@opentelemetry/exporter-trace-otlp-http'; |
| 3 | +importtype{SpanExporter}from'@opentelemetry/sdk-trace-base'; |
| 4 | +import{BasicTracerProvider,BatchSpanProcessor}from'@opentelemetry/sdk-trace-base'; |
| 5 | +importtype{Client,IntegrationFn}from'@sentry/core'; |
| 6 | +import{debug,defineIntegration,registerExternalPropagationContext,SENTRY_API_VERSION}from'@sentry/core'; |
| 7 | + |
| 8 | +interfaceOtlpIntegrationOptions{ |
| 9 | +/** |
| 10 | + * Whether to set up the OTLP traces exporter that sends spans to Sentry. |
| 11 | + * Default: true |
| 12 | + */ |
| 13 | +setupOtlpTracesExporter?: boolean; |
| 14 | + |
| 15 | +/** |
| 16 | + * URL of your own OpenTelemetry collector. |
| 17 | + * When set, the exporter will send traces to this URL instead of the Sentry OTLP endpoint derived from the DSN. |
| 18 | + * Default: undefined (uses DSN-derived endpoint) |
| 19 | + */ |
| 20 | +collectorUrl?: string; |
| 21 | +} |
| 22 | + |
| 23 | +constINTEGRATION_NAME='OtlpIntegration'; |
| 24 | + |
| 25 | +const_otlpIntegration=((userOptions: OtlpIntegrationOptions={})=>{ |
| 26 | +constoptions={ |
| 27 | +setupOtlpTracesExporter: userOptions.setupOtlpTracesExporter??true, |
| 28 | +collectorUrl: userOptions.collectorUrl, |
| 29 | +}; |
| 30 | + |
| 31 | +let_spanProcessor: BatchSpanProcessor|undefined; |
| 32 | +let_tracerProvider: BasicTracerProvider|undefined; |
| 33 | + |
| 34 | +return{ |
| 35 | +name: INTEGRATION_NAME, |
| 36 | + |
| 37 | +setup(_client: Client): void{ |
| 38 | +// Always register external propagation context so that Sentry error/log events |
| 39 | +// are linked to the active OTel trace context. |
| 40 | +registerExternalPropagationContext(()=>{ |
| 41 | +constactiveSpan=trace.getActiveSpan(); |
| 42 | +if(!activeSpan){ |
| 43 | +returnundefined; |
| 44 | +} |
| 45 | +constspanContext=activeSpan.spanContext(); |
| 46 | +return{traceId: spanContext.traceId,spanId: spanContext.spanId}; |
| 47 | +}); |
| 48 | + |
| 49 | +debug.log(`[${INTEGRATION_NAME}] External propagation context registered.`); |
| 50 | +}, |
| 51 | + |
| 52 | +afterAllSetup(client: Client): void{ |
| 53 | +if(options.setupOtlpTracesExporter){ |
| 54 | +setupTracesExporter(client); |
| 55 | +} |
| 56 | +}, |
| 57 | +}; |
| 58 | + |
| 59 | +functionsetupTracesExporter(client: Client): void{ |
| 60 | +letendpoint: string; |
| 61 | +letheaders: Record<string,string>|undefined; |
| 62 | + |
| 63 | +if(options.collectorUrl){ |
| 64 | +endpoint=options.collectorUrl; |
| 65 | +debug.log(`[${INTEGRATION_NAME}] Sending traces to collector at ${endpoint}`); |
| 66 | +}else{ |
| 67 | +constdsn=client.getDsn(); |
| 68 | +if(!dsn){ |
| 69 | +debug.warn(`[${INTEGRATION_NAME}] No DSN found. OTLP exporter not set up.`); |
| 70 | +return; |
| 71 | +} |
| 72 | + |
| 73 | +const{ protocol, host, port, path, projectId, publicKey }=dsn; |
| 74 | + |
| 75 | +constbasePath=path ? `/${path}` : ''; |
| 76 | +constportStr=port ? `:${port}` : ''; |
| 77 | +endpoint=`${protocol}://${host}${portStr}${basePath}/api/${projectId}/integration/otlp/v1/traces/`; |
| 78 | + |
| 79 | +constsdkInfo=client.getSdkMetadata()?.sdk; |
| 80 | +constsentryClient=sdkInfo ? `, sentry_client=${sdkInfo.name}/${sdkInfo.version}` : ''; |
| 81 | +headers={ |
| 82 | +'X-Sentry-Auth': `Sentry sentry_version=${SENTRY_API_VERSION}, sentry_key=${publicKey}${sentryClient}`, |
| 83 | +}; |
| 84 | +} |
| 85 | + |
| 86 | +letexporter: SpanExporter; |
| 87 | +try{ |
| 88 | +exporter=newOTLPTraceExporter({ |
| 89 | +url: endpoint, |
| 90 | + headers, |
| 91 | +}); |
| 92 | +}catch(e){ |
| 93 | +debug.warn(`[${INTEGRATION_NAME}] Failed to create OTLPTraceExporter:`,e); |
| 94 | +return; |
| 95 | +} |
| 96 | + |
| 97 | +_spanProcessor=newBatchSpanProcessor(exporter); |
| 98 | + |
| 99 | +// Add span processor to existing global tracer provider. |
| 100 | +// trace.getTracerProvider() returns a ProxyTracerProvider; unwrap it to get the real provider. |
| 101 | +constglobalProvider=trace.getTracerProvider(); |
| 102 | +constdelegate= |
| 103 | +'getDelegate'inglobalProvider |
| 104 | + ? (globalProviderasunknownas{getDelegate(): unknown}).getDelegate() |
| 105 | + : globalProvider; |
| 106 | + |
| 107 | +// In OTel v2, addSpanProcessor was removed. We push into the internal _spanProcessors |
| 108 | +// array on the MultiSpanProcessor, which is how OTel's own forceFlush() accesses it. |
| 109 | +constactiveProcessor=(delegateasRecord<string,unknown>)?._activeSpanProcessoras |
| 110 | +|{_spanProcessors?: unknown[]} |
| 111 | +|undefined; |
| 112 | +if(activeProcessor?._spanProcessors){ |
| 113 | +activeProcessor._spanProcessors.push(_spanProcessor); |
| 114 | +debug.log(`[${INTEGRATION_NAME}] Added span processor to existing TracerProvider.`); |
| 115 | +}else{ |
| 116 | +// No user-configured provider; create a minimal one and set it as global |
| 117 | +_tracerProvider=newBasicTracerProvider({ |
| 118 | +spanProcessors: [_spanProcessor], |
| 119 | +}); |
| 120 | +trace.setGlobalTracerProvider(_tracerProvider); |
| 121 | +debug.log(`[${INTEGRATION_NAME}] Created new TracerProvider with OTLP span processor.`); |
| 122 | +} |
| 123 | + |
| 124 | +client.on('flush',()=>{ |
| 125 | +void_spanProcessor?.forceFlush(); |
| 126 | +}); |
| 127 | + |
| 128 | +client.on('close',()=>{ |
| 129 | +void_spanProcessor?.shutdown(); |
| 130 | +void_tracerProvider?.shutdown(); |
| 131 | +}); |
| 132 | +} |
| 133 | +})satisfiesIntegrationFn; |
| 134 | + |
| 135 | +/** |
| 136 | + * OTLP integration for the Sentry light SDK. |
| 137 | + * |
| 138 | + * Bridges an existing OpenTelemetry setup with Sentry by: |
| 139 | + * 1. Linking Sentry error/log events to the active OTel trace context |
| 140 | + * 2. Exporting OTel spans to Sentry via OTLP (or to a custom collector) |
| 141 | + */ |
| 142 | +exportconstotlpIntegration=defineIntegration(_otlpIntegration); |
0 commit comments