diff --git a/dev-packages/browser-integration-tests/suites/integrations/supabase/auth/test.ts b/dev-packages/browser-integration-tests/suites/integrations/supabase/auth/test.ts index 6366fbac4f7e..c145e64bd1da 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/supabase/auth/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/supabase/auth/test.ts @@ -81,11 +81,12 @@ sentryTest('should capture Supabase authentication spans', async ({ getLocalTest const url = await getLocalTestUrl({ testDir: __dirname }); const eventData = await getFirstSentryEnvelopeRequest(page, url); - const supabaseSpans = eventData.spans?.filter(({ op }) => op?.startsWith('db.auth')); + const supabaseSpans = eventData.spans?.filter(({ op }) => op?.startsWith('db')); expect(supabaseSpans).toHaveLength(2); expect(supabaseSpans![0]).toMatchObject({ - description: 'signInWithPassword', + description: 'auth signInWithPassword', + op: 'db', parent_span_id: eventData.contexts?.trace?.span_id, span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -93,13 +94,16 @@ sentryTest('should capture Supabase authentication spans', async ({ getLocalTest trace_id: eventData.contexts?.trace?.trace_id, status: 'ok', data: expect.objectContaining({ - 'sentry.op': 'db.auth.signInWithPassword', + 'sentry.op': 'db', 'sentry.origin': 'auto.db.supabase', + 'db.operation': 'auth.signInWithPassword', + 'db.system': 'postgresql', }), }); expect(supabaseSpans![1]).toMatchObject({ - description: 'signOut', + description: 'auth signOut', + op: 'db', parent_span_id: eventData.contexts?.trace?.span_id, span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -107,8 +111,10 @@ sentryTest('should capture Supabase authentication spans', async ({ getLocalTest trace_id: eventData.contexts?.trace?.trace_id, status: 'ok', data: expect.objectContaining({ - 'sentry.op': 'db.auth.signOut', + 'sentry.op': 'db', 'sentry.origin': 'auto.db.supabase', + 'db.operation': 'auth.signOut', + 'db.system': 'postgresql', }), }); }); @@ -124,13 +130,14 @@ sentryTest('should capture Supabase authentication errors', async ({ getLocalTes const [errorEvent, transactionEvent] = await getMultipleSentryEnvelopeRequests(page, 2, { url }); - const supabaseSpans = transactionEvent.spans?.filter(({ op }) => op?.startsWith('db.auth')); + const supabaseSpans = transactionEvent.spans?.filter(({ op }) => op?.startsWith('db')); expect(errorEvent.exception?.values?.[0].value).toBe('Invalid email or password'); expect(supabaseSpans).toHaveLength(2); expect(supabaseSpans![0]).toMatchObject({ - description: 'signInWithPassword', + description: 'auth signInWithPassword', + op: 'db', parent_span_id: transactionEvent.contexts?.trace?.span_id, span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -138,8 +145,10 @@ sentryTest('should capture Supabase authentication errors', async ({ getLocalTes trace_id: transactionEvent.contexts?.trace?.trace_id, status: 'unknown_error', data: expect.objectContaining({ - 'sentry.op': 'db.auth.signInWithPassword', + 'sentry.op': 'db', 'sentry.origin': 'auto.db.supabase', + 'db.operation': 'auth.signInWithPassword', + 'db.system': 'postgresql', }), }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/supabase/db-operations/test.ts b/dev-packages/browser-integration-tests/suites/integrations/supabase/db-operations/test.ts index 2a10caa92c54..132a473e35f1 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/supabase/db-operations/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/supabase/db-operations/test.ts @@ -44,8 +44,10 @@ sentryTest('should capture Supabase database operation breadcrumbs', async ({ ge timestamp: expect.any(Number), type: 'supabase', category: 'db.insert', - message: 'from(todos)', - data: expect.any(Object), + message: 'insert(...) filter(columns, ) from(todos)', + data: expect.objectContaining({ + query: expect.arrayContaining(['filter(columns, )']), + }), }); }); diff --git a/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts index 80eb1a166e9b..cfb66b372420 100644 --- a/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts @@ -17,9 +17,14 @@ test('Sends server-side Supabase auth admin `createUser` span', async ({ page, b const transactionEvent = await httpTransactionPromise; expect(transactionEvent.spans).toContainEqual({ - data: expect.any(Object), - description: 'createUser', - op: 'db.auth.admin.createUser', + data: expect.objectContaining({ + 'db.operation': 'auth.admin.createUser', + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), + description: 'auth (admin) createUser', + op: 'db', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -54,8 +59,15 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry', expect(transactionEvent.spans).toContainEqual( expect.objectContaining({ - description: 'from(todos)', - op: 'db.select', + description: 'select(*) filter(order, asc) from(todos)', + op: 'db', + data: expect.objectContaining({ + 'db.operation': 'select', + 'db.query': ['select(*)', 'filter(order, asc)'], + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -67,9 +79,15 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry', ); expect(transactionEvent.spans).toContainEqual({ - data: expect.any(Object), - description: 'from(todos)', - op: 'db.insert', + data: expect.objectContaining({ + 'db.operation': 'select', + 'db.query': ['select(*)', 'filter(order, asc)'], + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), + description: 'select(*) filter(order, asc) from(todos)', + op: 'db', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -83,7 +101,7 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry', timestamp: expect.any(Number), type: 'supabase', category: 'db.select', - message: 'from(todos)', + message: 'select(*) filter(order, asc) from(todos)', data: expect.any(Object), }); @@ -91,7 +109,7 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry', timestamp: expect.any(Number), type: 'supabase', category: 'db.insert', - message: 'from(todos)', + message: 'insert(...) select(*) from(todos)', data: expect.any(Object), }); }); @@ -109,8 +127,15 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry', expect(transactionEvent.spans).toContainEqual( expect.objectContaining({ - description: 'from(todos)', - op: 'db.select', + data: expect.objectContaining({ + 'db.operation': 'insert', + 'db.query': ['select(*)'], + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), + description: 'insert(...) select(*) from(todos)', + op: 'db', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -122,9 +147,15 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry', ); expect(transactionEvent.spans).toContainEqual({ - data: expect.any(Object), - description: 'from(todos)', - op: 'db.insert', + data: expect.objectContaining({ + 'db.operation': 'select', + 'db.query': ['select(*)'], + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), + description: 'select(*) from(todos)', + op: 'db', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), @@ -138,7 +169,7 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry', timestamp: expect.any(Number), type: 'supabase', category: 'db.select', - message: 'from(todos)', + message: 'select(*) from(todos)', data: expect.any(Object), }); @@ -146,7 +177,7 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry', timestamp: expect.any(Number), type: 'supabase', category: 'db.insert', - message: 'from(todos)', + message: 'insert(...) select(*) from(todos)', data: expect.any(Object), }); }); @@ -154,8 +185,7 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry', test('Sends server-side Supabase auth admin `listUsers` span', async ({ page, baseURL }) => { const httpTransactionPromise = waitForTransaction('supabase-nextjs', transactionEvent => { return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent?.transaction === 'GET /api/list-users' + transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /api/list-users' ); }); @@ -163,9 +193,14 @@ test('Sends server-side Supabase auth admin `listUsers` span', async ({ page, ba const transactionEvent = await httpTransactionPromise; expect(transactionEvent.spans).toContainEqual({ - data: expect.any(Object), - description: 'listUsers', - op: 'db.auth.admin.listUsers', + data: expect.objectContaining({ + 'db.operation': 'auth.admin.listUsers', + 'db.system': 'postgresql', + 'sentry.op': 'db', + 'sentry.origin': 'auto.db.supabase', + }), + description: 'auth (admin) listUsers', + op: 'db', parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index 63229ccbdcf4..084d50356a83 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -219,10 +219,12 @@ function instrumentAuthOperation(operation: AuthOperationFn, isAdmin = false): A apply(target, thisArg, argumentsList) { return startSpan( { - name: operation.name, + name: `auth ${isAdmin ? '(admin) ' : ''}${operation.name}`, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.supabase', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `db.auth.${isAdmin ? 'admin.' : ''}${operation.name}`, + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db', + 'db.system': 'postgresql', + 'db.operation': `auth.${isAdmin ? 'admin.' : ''}${operation.name}`, }, }, span => { @@ -341,7 +343,6 @@ function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilte const pathParts = typedThis.url.pathname.split('/'); const table = pathParts.length > 0 ? pathParts[pathParts.length - 1] : ''; - const description = `from(${table})`; const queryItems: string[] = []; for (const [key, value] of typedThis.url.searchParams.entries()) { @@ -349,7 +350,6 @@ function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilte // so we need to use array instead of object to collect them. queryItems.push(translateFiltersIntoMethods(key, value)); } - const body: Record = Object.create(null); if (isPlainObject(typedThis.body)) { for (const [key, value] of Object.entries(typedThis.body)) { @@ -357,14 +357,22 @@ function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilte } } + // Adding operation to the beginning of the description if it's not a `select` operation + // For example, it can be an `insert` or `update` operation but the query can be `select(...)` + // For `select` operations, we don't need repeat it in the description + const description = `${operation === 'select' ? '' : `${operation}${body ? '(...) ' : ''}`}${queryItems.join( + ' ', + )} from(${table})`; + const attributes: Record = { 'db.table': table, 'db.schema': typedThis.schema, 'db.url': typedThis.url.origin, 'db.sdk': typedThis.headers['X-Client-Info'], 'db.system': 'postgresql', + 'db.operation': operation, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.supabase', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `db.${operation}`, + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db', }; if (queryItems.length) {