Skip to content

Conversation

@rgaignault
Copy link
Contributor

@rgaignault rgaignault commented Oct 21, 2025

Motivation

This PR extends the existing GraphQL tracking to capture GraphQL response errors, allowing developers to monitor server-side errors in their GraphQL queries
See spec for more informations : Graphql Spec

Changes

  • New trackResponseErrors option in GraphQL configuration
  • Extraction of GraphQL errors from HTTP responses (fetch and XHR)
  • Addition of telemetry for usage
  • E2E tests

Test instructions

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

@rgaignault rgaignault requested a review from a team as a code owner October 21, 2025 09:25
@rgaignault rgaignault marked this pull request as draft October 21, 2025 09:25
@cit-pr-commenter
Copy link

cit-pr-commenter bot commented Oct 21, 2025

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 162.96 KiB 165.36 KiB +2.40 KiB +1.47%
Rum Recorder 19.78 KiB 19.79 KiB +1 B +0.00%
Rum Profiler 4.89 KiB 4.89 KiB +1 B +0.02%
Logs 56.62 KiB 57.38 KiB +777 B +1.34%
Flagging 944 B 944 B 0 B 0.00%
Rum Slim 119.90 KiB 122.29 KiB +2.39 KiB +2.00%
Worker 23.60 KiB 23.60 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base CPU Time (ms) Local CPU Time (ms) 𝚫%
RUM - add global context 0.0044 0.0058 +31.82%
RUM - add action 0.0129 0.0157 +21.71%
RUM - add error 0.0128 0.0149 +16.41%
RUM - add timing 0.0031 0.0027 -12.90%
RUM - start view 0.0038 0.0041 +7.89%
RUM - start/stop session replay recording 0.0008 0.0007 -12.50%
Logs - log message 0.0154 0.0136 -11.69%
🧠 Memory Performance
Action Name Base Memory Consumption Local Memory Consumption 𝚫
RUM - add global context 25.42 KiB 25.85 KiB +441 B
RUM - add action 45.71 KiB 46.30 KiB +605 B
RUM - add timing 24.31 KiB 24.55 KiB +248 B
RUM - add error 50.42 KiB 51.35 KiB +954 B
RUM - start/stop session replay recording 24.16 KiB 24.05 KiB -111 B
RUM - start view 422.72 KiB 431.08 KiB +8.36 KiB
Logs - log message 41.60 KiB 42.46 KiB +879 B

🔗 RealWorld

@datadog-datadog-prod-us1
Copy link

datadog-datadog-prod-us1 bot commented Oct 21, 2025

✅ Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage
Patch Coverage: 86.32%
Total Coverage: 92.57% (-0.06%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 84bfaf0 | Docs | Was this helpful? Give us feedback!

@rgaignault rgaignault marked this pull request as ready for review October 22, 2025 13:08
res.header('Content-Type', 'application/json')
res.json({ data: { result: 'success' } })

const scenario = req.query.scenario as string | undefined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 praise: ‏Nice!

Comment on lines 183 to 198
function waitForResponseToComplete(
context: RumFetchResolveContext,
configuration: RumConfiguration,
callback: (duration: Duration, graphqlErrorsCount?: number, graphqlErrors?: GraphQlError[]) => void
) {
const clonedResponse = context.response && tryToClone(context.response)
if (!clonedResponse || !clonedResponse.body) {
// do not try to wait for the response if the clone failed, fetch error or null body
callback(elapsed(context.startClocks.timeStamp, timeStampNow()))
} else {
readBytesFromStream(
clonedResponse.body,
() => {
callback(elapsed(context.startClocks.timeStamp, timeStampNow()))
},
{
bytesLimit: Number.POSITIVE_INFINITY,
collectStreamBody: false,
}
)
return
}

const graphQlConfig = findGraphQlConfiguration(context.url, configuration)
const shouldExtractGraphQlErrors = graphQlConfig?.trackResponseErrors

readBytesFromStream(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏that indeed seems to be the right spot to add the graphql response error logic, however now this function responsibility is not solely to wait for the response to complete, to compute a more accurate duration.
So it could be interesting to rename this function accordingly and see if the different responsibilities of this function could better be reflected in the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree I renamed but I am not sure of the name, let me know what you think. Related to the other comment, graphql properties are now part of the context

Comment on lines +30 to +33
const metadata = extractGraphQlRequestMetadata(request.body, graphQlConfig.trackPayload)
if (!metadata) {
return
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: ‏Does it mean that we can't track error response if we don't track the payload?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we can, payload is just undefined in this case but the metadata object is still here with operationType, operationName and variables 👍

Comment on lines 59 to 61
const graphqlError: GraphQlError = {
message: typeof errorObj.message === 'string' ? errorObj.message : 'Unknown GraphQL error',
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏If there is no message, why not just leave this empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it’s more about a malformed error type, since we’re expecting a string in the response format. So I instead of not reporting I thought it would be clearer to inform with an unknown error message

return
}

const errors = (response.errors as unknown[]).map((error: unknown) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 suggestion: ‏Do we need to go through all those validation? Can't we just have const errors = response.errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, don’t we need to have a validation for the expected type ? Furthermore the extensions code need to be set at top level

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants