Skip to content

Commit d059df3

Browse files
nicohrubecs1gr1d
authored andcommitted
fix(core): Do not overwrite user provided conversation id in Vercel (#19903)
We set the conversation id unconditionally based on the `resonse_id`, so if a user calls `Sentry.setConversationId()` explicitly this will be overwritten, which is unexpected. Closes #19904 (added automatically)
1 parent c55fac9 commit d059df3

File tree

9 files changed

+626
-536
lines changed

9 files changed

+626
-536
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { NitroAppPlugin } from 'nitropack';
2+
import { useDatabase } from 'nitropack/runtime';
3+
// @ts-expect-error - This is a virtual module
4+
import { databaseConfig } from '#sentry/database-config.mjs';
5+
import type { DatabaseConnectionConfig } from '../utils/database-span-data';
6+
import { createDatabasePlugin } from '../utils/instrumentDatabase';
7+
8+
/**
9+
* Nitro plugin that instruments database calls for Nuxt v3/v4 (Nitro v2)
10+
*/
11+
export default (() => {
12+
createDatabasePlugin(useDatabase, databaseConfig as Record<string, DatabaseConnectionConfig>);
13+
}) satisfies NitroAppPlugin;
Lines changed: 6 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1,232 +1,13 @@
1-
import {
2-
addBreadcrumb,
3-
captureException,
4-
debug,
5-
flushIfServerless,
6-
SEMANTIC_ATTRIBUTE_SENTRY_OP,
7-
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
8-
type Span,
9-
SPAN_STATUS_ERROR,
10-
startSpan,
11-
type StartSpanOptions,
12-
} from '@sentry/core';
13-
import type { Database, PreparedStatement } from 'db0';
14-
import type { NitroAppPlugin } from 'nitropack';
15-
import { useDatabase } from 'nitropack/runtime';
16-
import type { DatabaseConnectionConfig as DatabaseConfig } from 'nitropack/types';
1+
import type { NitroAppPlugin } from 'nitro/types';
2+
import { useDatabase } from 'nitro/database';
173
// @ts-expect-error - This is a virtual module
184
import { databaseConfig } from '#sentry/database-config.mjs';
19-
import { type DatabaseSpanData, getDatabaseSpanData } from '../utils/database-span-data';
20-
21-
type MaybeInstrumentedDatabase = Database & {
22-
__sentry_instrumented__?: boolean;
23-
};
24-
25-
/**
26-
* Keeps track of prepared statements that have been patched.
27-
*/
28-
const patchedStatement = new WeakSet<PreparedStatement>();
5+
import type { DatabaseConnectionConfig } from '../utils/database-span-data';
6+
import { createDatabasePlugin } from '../utils/instrumentDatabase';
297

308
/**
31-
* The Sentry origin for the database plugin.
32-
*/
33-
const SENTRY_ORIGIN = 'auto.db.nuxt';
34-
35-
/**
36-
* Creates a Nitro plugin that instruments the database calls.
9+
* Nitro plugin that instruments database calls for Nuxt v5+ (Nitro v3+)
3710
*/
3811
export default (() => {
39-
try {
40-
const _databaseConfig = databaseConfig as Record<string, DatabaseConfig>;
41-
const databaseInstances = Object.keys(databaseConfig);
42-
debug.log('[Nitro Database Plugin]: Instrumenting databases...');
43-
44-
for (const instance of databaseInstances) {
45-
debug.log('[Nitro Database Plugin]: Instrumenting database instance:', instance);
46-
const db = useDatabase(instance);
47-
instrumentDatabase(db, _databaseConfig[instance]);
48-
}
49-
50-
debug.log('[Nitro Database Plugin]: Databases instrumented.');
51-
} catch (error) {
52-
// During build time, we can't use the useDatabase function, so we just log an error.
53-
if (error instanceof Error && /Cannot access 'instances'/.test(error.message)) {
54-
debug.log('[Nitro Database Plugin]: Database instrumentation skipped during build time.');
55-
return;
56-
}
57-
58-
debug.error('[Nitro Database Plugin]: Failed to instrument database:', error);
59-
}
12+
createDatabasePlugin(useDatabase, databaseConfig as Record<string, DatabaseConnectionConfig>);
6013
}) satisfies NitroAppPlugin;
61-
62-
/**
63-
* Instruments a database instance with Sentry.
64-
*/
65-
function instrumentDatabase(db: MaybeInstrumentedDatabase, config?: DatabaseConfig): void {
66-
if (db.__sentry_instrumented__) {
67-
debug.log('[Nitro Database Plugin]: Database already instrumented. Skipping...');
68-
return;
69-
}
70-
71-
const metadata: DatabaseSpanData = {
72-
'db.system.name': config?.connector ?? db.dialect,
73-
...getDatabaseSpanData(config),
74-
};
75-
76-
db.prepare = new Proxy(db.prepare, {
77-
apply(target, thisArg, args: Parameters<typeof db.prepare>) {
78-
const [query] = args;
79-
80-
return instrumentPreparedStatement(target.apply(thisArg, args), query, metadata);
81-
},
82-
});
83-
84-
// Sadly the `.sql` template tag doesn't call `db.prepare` internally and it calls the connector's `.prepare` directly
85-
// So we have to patch it manually, and would mean we would have less info in the spans.
86-
// https://github.com/unjs/db0/blob/main/src/database.ts#L64
87-
db.sql = new Proxy(db.sql, {
88-
apply(target, thisArg, args: Parameters<typeof db.sql>) {
89-
const query = args[0]?.[0] ?? '';
90-
const opts = createStartSpanOptions(query, metadata);
91-
92-
return startSpan(
93-
opts,
94-
handleSpanStart(() => target.apply(thisArg, args)),
95-
);
96-
},
97-
});
98-
99-
db.exec = new Proxy(db.exec, {
100-
apply(target, thisArg, args: Parameters<typeof db.exec>) {
101-
return startSpan(
102-
createStartSpanOptions(args[0], metadata),
103-
handleSpanStart(() => target.apply(thisArg, args), { query: args[0] }),
104-
);
105-
},
106-
});
107-
108-
db.__sentry_instrumented__ = true;
109-
}
110-
111-
/**
112-
* Instruments a DB prepared statement with Sentry.
113-
*
114-
* This is meant to be used as a top-level call, under the hood it calls `instrumentPreparedStatementQueries`
115-
* to patch the query methods. The reason for this abstraction is to ensure that the `bind` method is also patched.
116-
*/
117-
function instrumentPreparedStatement(
118-
statement: PreparedStatement,
119-
query: string,
120-
data: DatabaseSpanData,
121-
): PreparedStatement {
122-
// statement.bind() returns a new instance of D1PreparedStatement, so we have to patch it as well.
123-
// eslint-disable-next-line @typescript-eslint/unbound-method
124-
statement.bind = new Proxy(statement.bind, {
125-
apply(target, thisArg, args: Parameters<typeof statement.bind>) {
126-
return instrumentPreparedStatementQueries(target.apply(thisArg, args), query, data);
127-
},
128-
});
129-
130-
return instrumentPreparedStatementQueries(statement, query, data);
131-
}
132-
133-
/**
134-
* Patches the query methods of a DB prepared statement with Sentry.
135-
*/
136-
function instrumentPreparedStatementQueries(
137-
statement: PreparedStatement,
138-
query: string,
139-
data: DatabaseSpanData,
140-
): PreparedStatement {
141-
if (patchedStatement.has(statement)) {
142-
return statement;
143-
}
144-
145-
// eslint-disable-next-line @typescript-eslint/unbound-method
146-
statement.get = new Proxy(statement.get, {
147-
apply(target, thisArg, args: Parameters<typeof statement.get>) {
148-
return startSpan(
149-
createStartSpanOptions(query, data),
150-
handleSpanStart(() => target.apply(thisArg, args), { query }),
151-
);
152-
},
153-
});
154-
155-
// eslint-disable-next-line @typescript-eslint/unbound-method
156-
statement.run = new Proxy(statement.run, {
157-
apply(target, thisArg, args: Parameters<typeof statement.run>) {
158-
return startSpan(
159-
createStartSpanOptions(query, data),
160-
handleSpanStart(() => target.apply(thisArg, args), { query }),
161-
);
162-
},
163-
});
164-
165-
// eslint-disable-next-line @typescript-eslint/unbound-method
166-
statement.all = new Proxy(statement.all, {
167-
apply(target, thisArg, args: Parameters<typeof statement.all>) {
168-
return startSpan(
169-
createStartSpanOptions(query, data),
170-
handleSpanStart(() => target.apply(thisArg, args), { query }),
171-
);
172-
},
173-
});
174-
175-
patchedStatement.add(statement);
176-
177-
return statement;
178-
}
179-
180-
/**
181-
* Creates a span start callback handler
182-
*/
183-
function handleSpanStart(fn: () => unknown, breadcrumbOpts?: { query: string }) {
184-
return async (span: Span) => {
185-
try {
186-
const result = await fn();
187-
if (breadcrumbOpts) {
188-
createBreadcrumb(breadcrumbOpts.query);
189-
}
190-
191-
return result;
192-
} catch (error) {
193-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
194-
captureException(error, {
195-
mechanism: {
196-
handled: false,
197-
type: SENTRY_ORIGIN,
198-
},
199-
});
200-
201-
// Re-throw the error to be handled by the caller
202-
throw error;
203-
} finally {
204-
await flushIfServerless();
205-
}
206-
};
207-
}
208-
209-
function createBreadcrumb(query: string): void {
210-
addBreadcrumb({
211-
category: 'query',
212-
message: query,
213-
data: {
214-
'db.query.text': query,
215-
},
216-
});
217-
}
218-
219-
/**
220-
* Creates a start span options object.
221-
*/
222-
function createStartSpanOptions(query: string, data: DatabaseSpanData): StartSpanOptions {
223-
return {
224-
name: query,
225-
attributes: {
226-
'db.query.text': query,
227-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: SENTRY_ORIGIN,
228-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db.query',
229-
...data,
230-
},
231-
};
232-
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { NitroAppPlugin } from 'nitropack';
2+
import { useStorage } from 'nitropack/runtime';
3+
// @ts-expect-error - This is a virtual module
4+
import { userStorageMounts } from '#sentry/storage-config.mjs';
5+
import { createStoragePlugin } from '../utils/instrumentStorage';
6+
7+
/**
8+
* Nitro plugin that instruments storage driver calls for Nuxt v3/v4 (Nitro v2)
9+
*/
10+
export default (async _nitroApp => {
11+
await createStoragePlugin(useStorage, userStorageMounts as string[]);
12+
}) satisfies NitroAppPlugin;

0 commit comments

Comments
 (0)