|
| 1 | +const bugsnagInFlight = require('@bugsnag/in-flight') |
| 2 | +const BugsnagPluginBrowserSession = require('@bugsnag/plugin-browser-session') |
| 3 | + |
| 4 | +const SERVER_PLUGIN_NAMES = ['express', 'koa', 'restify', 'hono'] |
| 5 | +const isServerPluginLoaded = client => SERVER_PLUGIN_NAMES.some(name => client.getPlugin(name)) |
| 6 | + |
| 7 | +const extractRequestInfo = (request) => { |
| 8 | + if (!request) return {} |
| 9 | + |
| 10 | + const url = new URL(request.url) |
| 11 | + |
| 12 | + const info = { |
| 13 | + url: request.url, |
| 14 | + path: url.pathname, |
| 15 | + httpMethod: request.method, |
| 16 | + headers: Object.fromEntries(request.headers), |
| 17 | + query: url.searchParams.size > 0 ? Object.fromEntries(url.searchParams) : undefined, |
| 18 | + clientIp: request.headers.get('Cf-Connecting-IP') || request.headers.get('X-Forwarded-For') || undefined |
| 19 | + } |
| 20 | + |
| 21 | + return info |
| 22 | +} |
| 23 | + |
| 24 | +const getRequestAndMetadataFromReq = (request) => { |
| 25 | + const requestInfo = extractRequestInfo(request) |
| 26 | + |
| 27 | + return { |
| 28 | + metadata: requestInfo, |
| 29 | + request: { |
| 30 | + clientIp: requestInfo.clientIp, |
| 31 | + headers: requestInfo.headers, |
| 32 | + httpMethod: requestInfo.httpMethod, |
| 33 | + url: requestInfo.url |
| 34 | + } |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +const BugsnagPluginCloudflareWorkers = { |
| 39 | + name: 'cloudflareWorkers', |
| 40 | + |
| 41 | + load (client) { |
| 42 | + bugsnagInFlight.trackInFlight(client) |
| 43 | + client._loadPlugin(BugsnagPluginBrowserSession) |
| 44 | + |
| 45 | + // Reset the app duration between invocations, if the plugin is loaded |
| 46 | + const appDurationPlugin = client.getPlugin('appDuration') |
| 47 | + |
| 48 | + if (appDurationPlugin) { |
| 49 | + appDurationPlugin.reset() |
| 50 | + } |
| 51 | + |
| 52 | + return { |
| 53 | + createHandler ({ flushTimeoutMs = 2000 } = {}) { |
| 54 | + return wrapHandler.bind(null, client, flushTimeoutMs) |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +function wrapHandler (client, flushTimeoutMs, handler) { |
| 61 | + return async function (request, env, ctx) { |
| 62 | + // Add request metadata via onError callback so server plugins can override |
| 63 | + // Only add metadata if no server plugin is loaded |
| 64 | + if (!isServerPluginLoaded(client)) { |
| 65 | + client.addOnError((event) => { |
| 66 | + const { metadata, request: requestData } = getRequestAndMetadataFromReq(request) |
| 67 | + event.request = { ...event.request, ...requestData } |
| 68 | + event.addMetadata('request', metadata) |
| 69 | + }, true) |
| 70 | + } |
| 71 | + |
| 72 | + // Track sessions if autoTrackSessions is enabled and no server plugin is loaded |
| 73 | + if (client._config.autoTrackSessions && !isServerPluginLoaded(client)) { |
| 74 | + client.startSession() |
| 75 | + } |
| 76 | + |
| 77 | + try { |
| 78 | + return await handler(request, env, ctx) |
| 79 | + } catch (err) { |
| 80 | + if (client._config.autoDetectErrors && client._config.enabledErrorTypes.unhandledExceptions) { |
| 81 | + const handledState = { |
| 82 | + severity: 'error', |
| 83 | + unhandled: true, |
| 84 | + severityReason: { type: 'unhandledException' } |
| 85 | + } |
| 86 | + |
| 87 | + const event = client.Event.create(err, true, handledState, 'cloudflare workers plugin', 1) |
| 88 | + |
| 89 | + client._notify(event) |
| 90 | + } |
| 91 | + |
| 92 | + throw err |
| 93 | + } finally { |
| 94 | + // Use ctx.waitUntil to ensure flush completes even after response is returned |
| 95 | + // This is critical for Cloudflare Workers as they can terminate immediately |
| 96 | + if (ctx && typeof ctx.waitUntil === 'function') { |
| 97 | + ctx.waitUntil( |
| 98 | + bugsnagInFlight.flush(flushTimeoutMs).catch(err => { |
| 99 | + client._logger.error(`Delivery may be unsuccessful: ${err.message}`) |
| 100 | + }) |
| 101 | + ) |
| 102 | + } else { |
| 103 | + try { |
| 104 | + await bugsnagInFlight.flush(flushTimeoutMs) |
| 105 | + } catch (err) { |
| 106 | + client._logger.error(`Delivery may be unsuccessful: ${err.message}`) |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +module.exports = BugsnagPluginCloudflareWorkers |
| 114 | + |
| 115 | +// add a default export for ESM modules without interop |
| 116 | +module.exports.default = module.exports |
0 commit comments