( wrapperOptions: RequestHandlerWrapperOptions, handler: (...args: unknown[]) => Response | Promise<Response>, )
| 45 | * Wraps a cloudflare request handler in Sentry instrumentation |
| 46 | */ |
| 47 | export function wrapRequestHandler( |
| 48 | wrapperOptions: RequestHandlerWrapperOptions, |
| 49 | handler: (...args: unknown[]) => Response | Promise<Response>, |
| 50 | ): Promise<Response> { |
| 51 | return withIsolationScope(async isolationScope => { |
| 52 | const { options, request, captureErrors = true } = wrapperOptions; |
| 53 | const context = wrapperOptions.context; |
| 54 | |
| 55 | // Use getOriginalWaitUntil to get the un-instrumented waitUntil function. |
| 56 | // This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil |
| 57 | // to track pending tasks. If we use the instrumented version for flushAndDispose, |
| 58 | // it acquires the lock, then flushAndDispose tries to wait for the same lock, |
| 59 | // creating a deadlock. |
| 60 | const waitUntil = context ? getOriginalWaitUntil(context)?.bind(context) : undefined; |
| 61 | const errorMechanismType = getRequestErrorMechanismType(context); |
| 62 | |
| 63 | const client = init({ ...options, ctx: context }); |
| 64 | isolationScope.setClient(client); |
| 65 | |
| 66 | const urlObject = parseStringToURLObject(request.url); |
| 67 | const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request); |
| 68 | |
| 69 | const contentLength = request.headers.get('content-length'); |
| 70 | if (contentLength) { |
| 71 | attributes['http.request.body.size'] = parseInt(contentLength, 10); |
| 72 | } |
| 73 | |
| 74 | const userAgentHeader = request.headers.get('user-agent'); |
| 75 | if (userAgentHeader) { |
| 76 | attributes['user_agent.original'] = userAgentHeader; |
| 77 | } |
| 78 | |
| 79 | Object.assign( |
| 80 | attributes, |
| 81 | httpHeadersToSpanAttributes( |
| 82 | winterCGHeadersToDict(request.headers), |
| 83 | getClient()?.getDataCollectionOptions() ?? false, |
| 84 | ), |
| 85 | ); |
| 86 | |
| 87 | attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; |
| 88 | |
| 89 | addCloudResourceContext(isolationScope); |
| 90 | addRequest(isolationScope, request); |
| 91 | if (request.cf) { |
| 92 | addCultureContext(isolationScope, request.cf); |
| 93 | |
| 94 | if (typeof request.cf.httpProtocol === 'string') { |
| 95 | attributes['network.protocol.name'] = request.cf.httpProtocol; |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // Do not capture spans for OPTIONS and HEAD requests |
| 100 | if (request.method === 'OPTIONS' || request.method === 'HEAD') { |
| 101 | try { |
| 102 | return await handler(); |
| 103 | } catch (e) { |
| 104 | if (captureErrors) { |
no test coverage detected