Skip to content

Commit 14c3570

Browse files
committed
feat(cloudflare): Add support for email, queue, and tail handler
1 parent 57893e2 commit 14c3570

2 files changed

Lines changed: 803 additions & 18 deletions

File tree

packages/cloudflare/src/handler.ts

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
captureException,
33
flush,
4+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
45
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
56
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
67
startSpan,
@@ -66,7 +67,7 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
6667
'faas.cron': event.cron,
6768
'faas.time': new Date(event.scheduledTime).toISOString(),
6869
'faas.trigger': 'timer',
69-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare',
70+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.scheduled',
7071
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
7172
},
7273
},
@@ -87,8 +88,122 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
8788

8889
markAsInstrumented(handler.scheduled);
8990
}
91+
92+
if ('email' in handler && typeof handler.email === 'function' && !isInstrumented(handler.email)) {
93+
handler.email = new Proxy(handler.email, {
94+
apply(target, thisArg, args: Parameters<EmailExportedHandler<Env>>) {
95+
const [emailMessage, env, context] = args;
96+
return withIsolationScope(isolationScope => {
97+
const options = getFinalOptions(optionsCallback(env), env);
98+
99+
const client = init(options);
100+
isolationScope.setClient(client);
101+
102+
addCloudResourceContext(isolationScope);
103+
104+
return startSpan(
105+
{
106+
op: 'faas.email',
107+
name: `Handle Email ${emailMessage.to}`,
108+
attributes: {
109+
'faas.trigger': 'email',
110+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.email',
111+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
112+
},
113+
},
114+
async () => {
115+
try {
116+
return await (target.apply(thisArg, args) as ReturnType<typeof target>);
117+
} catch (e) {
118+
captureException(e, { mechanism: { handled: false, type: 'cloudflare' } });
119+
throw e;
120+
} finally {
121+
context.waitUntil(flush(2000));
122+
}
123+
},
124+
);
125+
});
126+
},
127+
});
128+
129+
markAsInstrumented(handler.email);
130+
}
131+
132+
if ('queue' in handler && typeof handler.queue === 'function' && !isInstrumented(handler.queue)) {
133+
handler.queue = new Proxy(handler.queue, {
134+
apply(target, thisArg, args: Parameters<ExportedHandlerQueueHandler<Env, QueueHandlerMessage>>) {
135+
const [batch, env, context] = args;
136+
137+
return withIsolationScope(isolationScope => {
138+
const options = getFinalOptions(optionsCallback(env), env);
139+
140+
const client = init(options);
141+
isolationScope.setClient(client);
142+
143+
addCloudResourceContext(isolationScope);
144+
145+
return startSpan(
146+
{
147+
op: 'faas.queue',
148+
name: `process ${batch.queue}`,
149+
attributes: {
150+
'faas.trigger': 'pubsub',
151+
'messaging.destination.name': batch.queue,
152+
'messaging.system': 'cloudflare',
153+
'messaging.batch.message_count': batch.messages.length,
154+
'messaging.message.retry.count': batch.messages.reduce((acc, message) => acc + message.attempts, 0),
155+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'queue.process',
156+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.queue',
157+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
158+
},
159+
},
160+
async () => {
161+
try {
162+
return await (target.apply(thisArg, args) as ReturnType<typeof target>);
163+
} catch (e) {
164+
captureException(e, { mechanism: { handled: false, type: 'cloudflare' } });
165+
throw e;
166+
} finally {
167+
context.waitUntil(flush(2000));
168+
}
169+
},
170+
);
171+
});
172+
},
173+
});
174+
175+
markAsInstrumented(handler.queue);
176+
}
177+
178+
if ('tail' in handler && typeof handler.tail === 'function' && !isInstrumented(handler.tail)) {
179+
handler.tail = new Proxy(handler.tail, {
180+
apply(target, thisArg, args: Parameters<ExportedHandlerTailHandler<Env>>) {
181+
const [, env, context] = args;
182+
183+
return withIsolationScope(async isolationScope => {
184+
const options = getFinalOptions(optionsCallback(env), env);
185+
186+
const client = init(options);
187+
isolationScope.setClient(client);
188+
189+
addCloudResourceContext(isolationScope);
190+
191+
try {
192+
return await (target.apply(thisArg, args) as ReturnType<typeof target>);
193+
} catch (e) {
194+
captureException(e, { mechanism: { handled: false, type: 'cloudflare' } });
195+
throw e;
196+
} finally {
197+
context.waitUntil(flush(2000));
198+
}
199+
});
200+
},
201+
});
202+
203+
markAsInstrumented(handler.tail);
204+
}
205+
90206
// This is here because Miniflare sometimes cannot get instrumented
91-
//
92207
} catch (e) {
93208
// Do not console anything here, we don't want to spam the console with errors
94209
}

0 commit comments

Comments
 (0)