Skip to content

Commit f4cc3d2

Browse files
authored
fix(serverless-aws): Overwrite root span name with GraphQL if set (#16010)
1 parent 6a3e7c1 commit f4cc3d2

File tree

6 files changed

+124
-6
lines changed

6 files changed

+124
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { ApolloServer, gql } = require('apollo-server');
2+
const Sentry = require('@sentry/aws-serverless');
3+
4+
module.exports = () => {
5+
return Sentry.startSpan({ name: 'Test Server Start' }, () => {
6+
return new ApolloServer({
7+
typeDefs: gql`
8+
type Query {
9+
hello: String
10+
world: String
11+
}
12+
type Mutation {
13+
login(email: String): String
14+
}
15+
`,
16+
resolvers: {
17+
Query: {
18+
hello: () => {
19+
return 'Hello!';
20+
},
21+
world: () => {
22+
return 'World!';
23+
},
24+
},
25+
Mutation: {
26+
login: async (_, { email }) => {
27+
return `${email}--token`;
28+
},
29+
},
30+
},
31+
introspection: false,
32+
debug: false,
33+
});
34+
});
35+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/aws-serverless');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
tracesSampleRate: 1.0,
7+
integrations: [Sentry.graphqlIntegration({ useOperationNameForRootSpan: true })],
8+
transport: loggingTransport,
9+
});
10+
11+
async function run() {
12+
const apolloServer = require('./apollo-server')();
13+
14+
await Sentry.startSpan({ name: 'Test Transaction' }, async span => {
15+
// Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation
16+
await apolloServer.executeOperation({
17+
query: 'query GetHello {hello}',
18+
});
19+
20+
setTimeout(() => {
21+
span.end();
22+
apolloServer.stop();
23+
}, 500);
24+
});
25+
}
26+
27+
run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { afterAll, describe, expect, test } from 'vitest';
2+
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
3+
4+
const EXPECTED_TRANSCATION = {
5+
transaction: 'Test Transaction (query GetHello)',
6+
spans: expect.arrayContaining([
7+
expect.objectContaining({
8+
description: 'query GetHello',
9+
origin: 'auto.graphql.otel.graphql',
10+
status: 'ok',
11+
}),
12+
]),
13+
};
14+
15+
describe('graphqlIntegration', () => {
16+
afterAll(() => {
17+
cleanupChildProcesses();
18+
});
19+
20+
test('should use GraphQL operation name for root span if useOperationNameForRootSpan is set', async () => {
21+
await createRunner(__dirname, 'scenario.js')
22+
.ignore('event')
23+
.expect({ transaction: { transaction: 'Test Server Start (query IntrospectionQuery)' } })
24+
.expect({ transaction: EXPECTED_TRANSCATION })
25+
.start()
26+
.completed();
27+
});
28+
});

dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { createRunner } from '../../../utils/runner';
33

44
// Graphql Instrumentation emits some spans by default on server start
55
const EXPECTED_START_SERVER_TRANSACTION = {
6-
transaction: 'Test Server Start',
6+
transaction: 'Test Server Start (query IntrospectionQuery)',
77
};
88

99
describe('GraphQL/Apollo Tests', () => {
1010
test('should instrument GraphQL queries used from Apollo Server.', async () => {
1111
const EXPECTED_TRANSACTION = {
12-
transaction: 'Test Transaction',
12+
transaction: 'Test Transaction (query)',
1313
spans: expect.arrayContaining([
1414
expect.objectContaining({
1515
data: {
@@ -33,7 +33,7 @@ describe('GraphQL/Apollo Tests', () => {
3333

3434
test('should instrument GraphQL mutations used from Apollo Server.', async () => {
3535
const EXPECTED_TRANSACTION = {
36-
transaction: 'Test Transaction',
36+
transaction: 'Test Transaction (mutation Mutation)',
3737
spans: expect.arrayContaining([
3838
expect.objectContaining({
3939
data: {

dev-packages/node-integration-tests/suites/tracing/apollo-graphql/useOperationNameForRootSpan/test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { createRunner } from '../../../../utils/runner';
2-
import { describe, test, expect } from 'vitest'
2+
import { describe, test, expect } from 'vitest';
33

44
// Graphql Instrumentation emits some spans by default on server start
55
const EXPECTED_START_SERVER_TRANSACTION = {
6-
transaction: 'Test Server Start',
6+
transaction: 'Test Server Start (query IntrospectionQuery)',
77
};
88

99
describe('GraphQL/Apollo Tests > useOperationNameForRootSpan', () => {
@@ -61,7 +61,7 @@ describe('GraphQL/Apollo Tests > useOperationNameForRootSpan', () => {
6161

6262
test('useOperationNameForRootSpan ignores an invalid root span', async () => {
6363
const EXPECTED_TRANSACTION = {
64-
transaction: 'test span name',
64+
transaction: 'test span name (query GetHello)',
6565
spans: expect.arrayContaining([
6666
expect.objectContaining({
6767
data: {

packages/node/src/integrations/tracing/graphql.ts

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION } from '@sentry/opentelemet
55
import { generateInstrumentOnce } from '../../otel/instrument';
66

77
import { addOriginToSpan } from '../../utils/addOriginToSpan';
8+
import type { AttributeValue } from '@opentelemetry/api';
89

910
interface GraphqlOptions {
1011
/**
@@ -72,6 +73,16 @@ export const instrumentGraphql = generateInstrumentOnce(
7273
} else {
7374
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, newOperation);
7475
}
76+
77+
if (!spanToJSON(rootSpan).data['original-description']) {
78+
rootSpan.setAttribute('original-description', spanToJSON(rootSpan).description);
79+
}
80+
// Important for e.g. @sentry/aws-serverless because this would otherwise overwrite the name again
81+
rootSpan.updateName(
82+
`${spanToJSON(rootSpan).data['original-description']} (${getGraphqlOperationNamesFromAttribute(
83+
existingOperations,
84+
)})`,
85+
);
7586
}
7687
},
7788
};
@@ -115,3 +126,20 @@ function getOptionsWithDefaults(options?: GraphqlOptions): GraphqlOptions {
115126
...options,
116127
};
117128
}
129+
130+
// copy from packages/opentelemetry/utils
131+
function getGraphqlOperationNamesFromAttribute(attr: AttributeValue): string {
132+
if (Array.isArray(attr)) {
133+
const sorted = attr.slice().sort();
134+
135+
// Up to 5 items, we just add all of them
136+
if (sorted.length <= 5) {
137+
return sorted.join(', ');
138+
} else {
139+
// Else, we add the first 5 and the diff of other operations
140+
return `${sorted.slice(0, 5).join(', ')}, +${sorted.length - 5}`;
141+
}
142+
}
143+
144+
return `${attr}`;
145+
}

0 commit comments

Comments
 (0)