|
| 1 | +import type { Integration } from '@sentry/core'; |
| 2 | +import { |
| 3 | + consoleIntegration, |
| 4 | + conversationIdIntegration, |
| 5 | + dedupeIntegration, |
| 6 | + functionToStringIntegration, |
| 7 | + getIntegrationsToSetup, |
| 8 | + GLOBAL_OBJ, |
| 9 | + inboundFiltersIntegration, |
| 10 | + initAndBind, |
| 11 | + linkedErrorsIntegration, |
| 12 | + requestDataIntegration, |
| 13 | + stackParserFromStackParserOptions, |
| 14 | +} from '@sentry/core'; |
| 15 | +import type { CloudflareClientOptions, CloudflareOptions } from './client'; |
| 16 | +import { CloudflareClient } from './client'; |
| 17 | +import { makeFlushLock } from './flush'; |
| 18 | +import { fetchIntegration } from './integrations/fetch'; |
| 19 | +import { httpServerIntegration } from './integrations/httpServer'; |
| 20 | +import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from './integrations/spotlight'; |
| 21 | +import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; |
| 22 | +import { makeCloudflareTransport } from './transport'; |
| 23 | +import { defaultStackParser } from './vendor/stacktrace'; |
| 24 | + |
| 25 | +/** |
| 26 | + * Instantiate the channel-subscriber factories the `@sentry/cloudflare/vite` |
| 27 | + * plugin registered on the global marker. The plugin splices a small snippet |
| 28 | + * into each instrumented module that `.set`s its factory here (keyed by export |
| 29 | + * name), so the marker holds one factory per package actually bundled. |
| 30 | + * |
| 31 | + * The marker is read directly instead of importing the factories, so a worker |
| 32 | + * built without the plugin — where the channels never fire — ships none of this |
| 33 | + * code. |
| 34 | + * TODO(v11): Use `@sentry/server-utils/orchestrion` once we move to `nodejs_compat` by default. |
| 35 | + */ |
| 36 | +function getRegisteredChannelIntegrations(): Integration[] { |
| 37 | + const registered = GLOBAL_OBJ.__SENTRY_ORCHESTRION__?.integrations; |
| 38 | + |
| 39 | + return registered ? [...registered.values()].map(factory => factory()) : []; |
| 40 | +} |
| 41 | + |
| 42 | +/** |
| 43 | + * Get the default integrations that run on any Workers-compatible runtime, i.e. without the |
| 44 | + * `nodejs_compat` compatibility flag. |
| 45 | + * |
| 46 | + * `getDefaultIntegrations` in `sdk.ts` extends this set with the integrations that do depend on |
| 47 | + * Node.js APIs. Keeping the two apart is what allows `wrapRequestHandler` to stay usable on runtimes |
| 48 | + * that cannot enable `nodejs_compat`, such as Shopify Oxygen. |
| 49 | + */ |
| 50 | +export function getBaseDefaultIntegrations(options: CloudflareOptions): Integration[] { |
| 51 | + return [ |
| 52 | + // The Dedupe integration should not be used in workflows because we want to |
| 53 | + // capture all step failures, even if they are the same error. |
| 54 | + ...(options.enableDedupe === false ? [] : [dedupeIntegration()]), |
| 55 | + // TODO(v11): Replace with `eventFiltersIntegration` once we remove the deprecated `inboundFiltersIntegration` |
| 56 | + // eslint-disable-next-line typescript/no-deprecated |
| 57 | + inboundFiltersIntegration(), |
| 58 | + functionToStringIntegration(), |
| 59 | + conversationIdIntegration(), |
| 60 | + linkedErrorsIntegration(), |
| 61 | + fetchIntegration(), |
| 62 | + httpServerIntegration(), |
| 63 | + // oxlint-disable-next-line typescript/no-deprecated |
| 64 | + requestDataIntegration(), |
| 65 | + consoleIntegration(), |
| 66 | + // The orchestrion diagnostics-channel subscribers (mysql, pg, …). The |
| 67 | + // `@sentry/cloudflare/vite` plugin injects the channels at build time and, |
| 68 | + // next to each, a snippet that registers the matching subscriber factory on |
| 69 | + // the global marker. Read from there instead of importing them so bundles |
| 70 | + // built without the plugin — where the channels would never fire — don't |
| 71 | + // ship the code. |
| 72 | + ...getRegisteredChannelIntegrations(), |
| 73 | + ]; |
| 74 | +} |
| 75 | + |
| 76 | +/** |
| 77 | + * Initializes the Cloudflare SDK with the passed default integrations. |
| 78 | + * |
| 79 | + * The default integrations are injected rather than imported so that this module stays free of |
| 80 | + * Node.js-only code. `request.ts` — which backs both `wrapRequestHandler` and the |
| 81 | + * `@sentry/cloudflare/request` entry point, and therefore has to work on runtimes without the |
| 82 | + * `nodejs_compat` compatibility flag — creates its client from here instead of from `sdk.ts`. |
| 83 | + */ |
| 84 | +export function initWithDefaultIntegrations( |
| 85 | + options: CloudflareOptions, |
| 86 | + getDefaultIntegrationsImpl: (options: CloudflareOptions) => Integration[], |
| 87 | +): CloudflareClient | undefined { |
| 88 | + if (options.defaultIntegrations === undefined) { |
| 89 | + options.defaultIntegrations = getDefaultIntegrationsImpl(options); |
| 90 | + } |
| 91 | + |
| 92 | + const flushLock = options.ctx ? makeFlushLock(options.ctx) : undefined; |
| 93 | + delete options.ctx; |
| 94 | + |
| 95 | + const clientOptions: CloudflareClientOptions = { |
| 96 | + ...options, |
| 97 | + stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser), |
| 98 | + integrations: getIntegrationsToSetup(options), |
| 99 | + transport: options.transport || makeCloudflareTransport, |
| 100 | + flushLock, |
| 101 | + }; |
| 102 | + |
| 103 | + /*! rollup-include-development-only */ |
| 104 | + if (options.spotlight && !clientOptions.integrations.some(({ name }) => name === SPOTLIGHT_INTEGRATION_NAME)) { |
| 105 | + clientOptions.integrations.push( |
| 106 | + spotlightIntegration({ |
| 107 | + sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined, |
| 108 | + }), |
| 109 | + ); |
| 110 | + } |
| 111 | + /*! rollup-include-development-only-end */ |
| 112 | + |
| 113 | + /** |
| 114 | + * The Cloudflare SDK is not OpenTelemetry native, however, we set up some OpenTelemetry compatibility |
| 115 | + * via a custom trace provider. |
| 116 | + * This ensures that any spans emitted via `@opentelemetry/api` will be captured by Sentry. |
| 117 | + * HOWEVER, big caveat: This does not handle custom context handling, it will always work off the current scope. |
| 118 | + * This should be good enough for many, but not all integrations. |
| 119 | + */ |
| 120 | + if (!options.skipOpenTelemetrySetup) { |
| 121 | + setupOpenTelemetryTracer(); |
| 122 | + } |
| 123 | + |
| 124 | + return initAndBind(CloudflareClient, clientOptions) as CloudflareClient; |
| 125 | +} |
| 126 | + |
| 127 | +/** |
| 128 | + * Initializes the Cloudflare SDK with only the default integrations from |
| 129 | + * {@link getBaseDefaultIntegrations}, i.e. those that work without the `nodejs_compat` |
| 130 | + * compatibility flag. |
| 131 | + */ |
| 132 | +export function initBaseSdk(options: CloudflareOptions): CloudflareClient | undefined { |
| 133 | + return initWithDefaultIntegrations(options, getBaseDefaultIntegrations); |
| 134 | +} |
0 commit comments