Skip to content

Commit c035033

Browse files
committed
fix: handle exceptions properly across all db methods
1 parent 4fbfe16 commit c035033

File tree

1 file changed

+56
-47
lines changed

1 file changed

+56
-47
lines changed

packages/nuxt/src/runtime/plugins/database.server.ts

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
type Span,
23
type StartSpanOptions,
34
addBreadcrumb,
45
captureException,
@@ -19,6 +20,11 @@ type PreparedStatementType = 'get' | 'run' | 'all' | 'raw';
1920
*/
2021
const patchedStatement = new WeakSet<PreparedStatement>();
2122

23+
/**
24+
* The Sentry origin for the database plugin.
25+
*/
26+
const SENTRY_ORIGIN = 'auto.db.nuxt';
27+
2228
/**
2329
* Creates a Nitro plugin that instruments the database calls.
2430
*/
@@ -49,38 +55,19 @@ function instrumentDatabase(db: Database): void {
4955
const query = args[0]?.[0] ?? '';
5056
const opts = createStartSpanOptions(query, db.dialect);
5157

52-
return startSpan(opts, async span => {
53-
try {
54-
const result = await target.apply(thisArg, args);
55-
56-
return result;
57-
} catch (error) {
58-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
59-
captureException(error, {
60-
mechanism: {
61-
handled: false,
62-
type: opts.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN],
63-
},
64-
});
65-
66-
// Re-throw the error to be handled by the caller
67-
throw error;
68-
} finally {
69-
await flushIfServerless();
70-
}
71-
});
58+
return startSpan(
59+
opts,
60+
handleSpanStart(() => target.apply(thisArg, args)),
61+
);
7262
},
7363
});
7464

7565
db.exec = new Proxy(db.exec, {
7666
apply(target, thisArg, args: Parameters<typeof db.exec>) {
77-
return startSpan(createStartSpanOptions(args[0], db.dialect, 'run'), async () => {
78-
const result = await target.apply(thisArg, args);
79-
80-
createBreadcrumb(args[0], 'run');
81-
82-
return result;
83-
});
67+
return startSpan(
68+
createStartSpanOptions(args[0], db.dialect, 'run'),
69+
handleSpanStart(() => target.apply(thisArg, args), { query: args[0], type: 'run' }),
70+
);
8471
},
8572
});
8673
}
@@ -118,37 +105,30 @@ function instrumentPreparedStatementQueries(
118105
// eslint-disable-next-line @typescript-eslint/unbound-method
119106
statement.get = new Proxy(statement.get, {
120107
apply(target, thisArg, args: Parameters<typeof statement.get>) {
121-
return startSpan(createStartSpanOptions(query, dialect, 'get'), async () => {
122-
const result = await target.apply(thisArg, args);
123-
createBreadcrumb(query, 'get');
124-
125-
return result;
126-
});
108+
return startSpan(
109+
createStartSpanOptions(query, dialect, 'get'),
110+
handleSpanStart(() => target.apply(thisArg, args), { query, type: 'get' }),
111+
);
127112
},
128113
});
129114

130115
// eslint-disable-next-line @typescript-eslint/unbound-method
131116
statement.run = new Proxy(statement.run, {
132117
apply(target, thisArg, args: Parameters<typeof statement.run>) {
133-
return startSpan(createStartSpanOptions(query, dialect, 'run'), async () => {
134-
const result = await target.apply(thisArg, args);
135-
createBreadcrumb(query, 'run');
136-
137-
return result;
138-
});
118+
return startSpan(
119+
createStartSpanOptions(query, dialect, 'run'),
120+
handleSpanStart(() => target.apply(thisArg, args), { query, type: 'run' }),
121+
);
139122
},
140123
});
141124

142125
// eslint-disable-next-line @typescript-eslint/unbound-method
143126
statement.all = new Proxy(statement.all, {
144127
apply(target, thisArg, args: Parameters<typeof statement.all>) {
145-
return startSpan(createStartSpanOptions(query, dialect, 'all'), async () => {
146-
const result = await target.apply(thisArg, args);
147-
// Since all has no regular shape, we can assume if it returns an array, it's a success.
148-
createBreadcrumb(query, 'all');
149-
150-
return result;
151-
});
128+
return startSpan(
129+
createStartSpanOptions(query, dialect, 'all'),
130+
handleSpanStart(() => target.apply(thisArg, args), { query, type: 'all' }),
131+
);
152132
},
153133
});
154134

@@ -157,6 +137,35 @@ function instrumentPreparedStatementQueries(
157137
return statement;
158138
}
159139

140+
/**
141+
* Creates a span start callback handler
142+
*/
143+
function handleSpanStart(fn: () => unknown, breadcrumbOpts?: { query: string; type: PreparedStatementType }) {
144+
return async (span: Span) => {
145+
try {
146+
const result = await fn();
147+
if (breadcrumbOpts) {
148+
createBreadcrumb(breadcrumbOpts.query, breadcrumbOpts.type);
149+
}
150+
151+
return result;
152+
} catch (error) {
153+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
154+
captureException(error, {
155+
mechanism: {
156+
handled: false,
157+
type: SENTRY_ORIGIN,
158+
},
159+
});
160+
161+
// Re-throw the error to be handled by the caller
162+
throw error;
163+
} finally {
164+
await flushIfServerless();
165+
}
166+
};
167+
}
168+
160169
function createBreadcrumb(query: string, type: PreparedStatementType): void {
161170
addBreadcrumb({
162171
category: 'query',
@@ -177,7 +186,7 @@ function createStartSpanOptions(query: string, dialect: string, type?: PreparedS
177186
attributes: {
178187
'db.system': dialect,
179188
'db.query_type': type,
180-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.nuxt',
189+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: SENTRY_ORIGIN,
181190
},
182191
};
183192
}

0 commit comments

Comments
 (0)