sentry-javascript
sentry-javascript copied to clipboard
data set via `setExtra` and `setExtras` no longer visible in UI
Is there an existing issue for this?
- [X] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- [X] I have reviewed the documentation https://docs.sentry.io/
- [X] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
7.101.1
Framework Version
No response
Link to Sentry event
https://s1seven.sentry.io/issues/4488829135/?environment=local&project=5880685&query=is%3Aunresolved&referrer=issue-stream&statsPeriod=7d&stream_index=0&utc=true
SDK Setup
init({
tracesSampleRate: 1.0,
...params,
autoSessionTracking: true,
integrations: [
browserTracingIntegration(),
...(params.integrations || []),
],
Steps to Reproduce
Prior to v7.87.0 we were able to use setExtra and see the information in the ui, we set the data like this:
const { method, statusCode, path, stack } = errorResponse;
const request = context.getRequest<ExtendedRequest>();
const scope = new Scope();
if (request.currentUser) {
scope.setUser(request.currentUser);
} else if (request.currentCompany) {
scope.setUser(request.currentCompany);
}
scope.setExtras({
...(!isEmpty(request.currentScopes) && {
[SentryExtrasProps.access_scopes]: request.currentScopes,
}),
[SentryExtrasProps.request_id]: request.requestId,
[SentryExtrasProps.stack]: stack,
[SentryExtrasProps.http_headers]: request.headers,
[SentryExtrasProps.http_method]: request.method,
[SentryExtrasProps.http_params]: request.params,
[SentryExtrasProps.http_query]: request.query,
[SentryExtrasProps.http_body]: request.body,
[SentryExtrasProps.error_message]: errorResponse.message,
[SentryExtrasProps.error_details]: errorResponse.details,
});
const mode = extractModeFromHttpRequest(request);
scope.setTags({
requestId: request.requestId,
statusCode,
method,
path,
protocol: 'http',
...(mode ? { mode } : {}),
...tags,
});
We used to use setExtra but that stopped working in v7.87.0, so we switched to setExtras, but that seemed to stop working in v7.89.0.
There is no deprecated tag on either of those methods, and in the release notes for v7.101.1 it says:
feat(core): Write data from setUser, setTags, setExtras, setTag, setExtra, and setContext to isolation scope ([#10163](https://github.com/getsentry/sentry-javascript/pull/10163)) so it appears that they are still in use.
However in the docs (https://docs.sentry.io/platforms/javascript/enriching-events/context/#additional-data) it says the following:
Additional Data is deprecated in favor of structured contexts.
Sentry used to support adding unstructured "Additional Data" via setExtra.
As you can see in the screenshot, we could previously see all this information:
Since v7.89.0 all that information is missing.
Expected Result
I should still be able to see that extra information in the UI. There should be clear documentation and deprecated tags if setExtra and setExtras are no longer to be used.
Actual Result
All that extra information is now missing:
Looking at the code in node_modules in @sentry/core/cjs/exports.js, it seems that setExtras is not deprecated, but internally it uses a method that is deprecated.
I noticed that a PR that was merged 18 hours ago updates this to use getIsolationScope instead, but in the PR description it says The hub already writes there, but this circumvents the hub now!, so it shouldn't make a difference.
https://github.com/getsentry/sentry-javascript/pull/10678/files
Not sure if this has anything to do with my issue or not.
Hey,
setExtra and setExtras should continue to work as expected on the scope. I wonder, what do you do with the scope = new Scope()? I would avoid this and instead use withScope there - as the manually created scope will not necessarily be picked up by events being created:
Sentry.withScope(scope => {
scope.setExtra(...);
// set scope stuff here
// anything captured inside of this callback will have this applied!
});
The PR you referenced is going into v8, all the APIs that are deprecated should still works as expected!
Thank you for your quick reply, in the meantime I did some tests and when I replaced scope.setExtras with Sentry.setExtras it fixed the problem, so my bad. I am still surprised that scope.setExtras doesn't work.
This works:
import * as Sentry from '@sentry/core';
const scope = new Scope();
if (request.currentUser) {
Sentry.setUser(request.currentUser);
} else if (request.currentCompany) {
Sentry.setUser(request.currentCompany);
}
Sentry.setExtras({
...(!isEmpty(request.currentScopes) && {
[SentryExtrasProps.access_scopes]: request.currentScopes,
}),
[SentryExtrasProps.request_id]: request.requestId,
[SentryExtrasProps.stack]: stack,
[SentryExtrasProps.http_headers]: request.headers,
[SentryExtrasProps.http_method]: request.method,
[SentryExtrasProps.http_params]: request.params,
[SentryExtrasProps.http_query]: request.query,
[SentryExtrasProps.http_body]: request.body,
[SentryExtrasProps.error_message]: errorResponse.message,
[SentryExtrasProps.error_details]: errorResponse.details,
});
const mode = extractModeFromHttpRequest(request);
Sentry.setTags({
requestId: request.requestId,
statusCode,
method,
path,
protocol: 'http',
...(mode ? { mode } : {}),
...tags,
});
return scope;
We used new Scope() so that we could return the scope from that function and then pass it to another method (this.sendToSentry).
const scope = SentryExceptionService.createHttpCustomExceptionScope(
errorResponse,
context,
{
appName: this.appName,
appVersion: this.appVersion,
serverUrl: this.serverUrl,
}
);
this.sendToSentry(exception, scope);
Thanks for the tip on using Sentry.withScope instead of new Scope(), I will try that.
Good that it helped! still, I'm curious - could you paste what you do in sendToSentry, e.g. how do you pass the scope etc?
Looking at the latest event here, it seems everything is attached as extras? https://s1seven.sentry.io/issues/4488829135/events/f025842c40c346e98372f786e38e69ac/
and your SDK is on 7.101.1
Good that it helped! still, I'm curious - could you paste what you do in
sendToSentry, e.g. how do you pass the scope etc?
Of course. We use NestJS so we have built a class around it, here is a slightly simplified version of what we do. The relevant methods are handleHttpCustomException and createHttpCustomExceptionScope. This is the original version, I've slightly updated it to follow your suggestion. I guess the important part is that we create a new Scope, add tags/extra properties, return the scope and then pass it to another method.
@Injectable()
export class SentryExceptionService {
readonly appName: string;
readonly appVersion: string;
readonly serverUrl: string;
readonly brokerUrl?: string;
readonly filter: SentryFilter;
constructor(
@InjectSentry() private readonly sentryClient: SentryService,
@Optional()
@InjectSentryExceptionOptions()
private readonly sentryExceptionOptions: SentryExceptionServiceOptions = {}
) {}
static createSentryException(errorResponse: ErrorResponse): Error {}
static createHttpCustomExceptionScope(
errorResponse: ErrorResponse,
context: HttpArgumentsHost,
tags: {
appName?: string;
appVersion?: string;
serverUrl?: string;
} = {}
): Scope {
const { method, statusCode, path, stack } = errorResponse;
const request = context.getRequest<ExtendedRequest>();
const scope = new Scope();
scope.setUser(request.currentUser);
scope.setExtras({
. [SentryExtrasProps.access_scopes]: request.currentScopes,
[SentryExtrasProps.request_id]: request.requestId,
[SentryExtrasProps.stack]: stack,
[SentryExtrasProps.http_headers]: request.headers,
[SentryExtrasProps.http_method]: request.method,
[SentryExtrasProps.http_params]: request.params,
[SentryExtrasProps.http_query]: request.query,
[SentryExtrasProps.http_body]: request.body,
[SentryExtrasProps.error_message]: errorResponse.message,
[SentryExtrasProps.error_details]: errorResponse.details,
});
scope.setTags({
requestId: request.requestId,
statusCode,
method,
path,
protocol: 'http',
...tags,
});
return scope;
}
static createRpcCustomExceptionScope(
errorResponse: ErrorResponse,
context: RpcArgumentsHost,
tags: {
appName?: string;
appVersion?: string;
serverUrl: string;
}
): Scope {}
shouldSendToSentry(errorResponse: ErrorResponse): boolean {}
sendToSentry(exception: Error, scope: Scope): void {
this.sentryClient.instance().captureException(exception, () => scope);
}
handleHttpCustomException(
errorResponse: ErrorResponse,
context: HttpArgumentsHost
): void {
const shouldSend = this.shouldSendToSentry(errorResponse);
if (!shouldSend) {
return;
}
try {
const exception =
SentryExceptionService.createSentryException(errorResponse);
const scope = SentryExceptionService.createHttpCustomExceptionScope(
errorResponse,
context,
{
appName: this.appName,
appVersion: this.appVersion,
serverUrl: this.serverUrl,
}
);
this.sendToSentry(exception, scope);
} catch (e) {
this.sentryClient.error(e?.message, e?.stack);
return;
}
}
}
Not sure how sentryClient.instance() looks, but if this just returns a plain Client from the Sentry SDK, I think the syntax there needs to be:
this.sentryClient.instance().captureException(exception, {}, scope);
See: https://github.com/getsentry/sentry-javascript/blob/v7/packages/types/src/client.ts#L40
does it work then?
I did a little digging and sentryClient.instance() returns Sentry from import * as Sentry from '@sentry/node';.
I asked the dev that designed it and he said "you cannot rely on the fact that a client will always be returned by Sentry.getClient(); as can be seen by the return type. So this made the Sentry client unfit for the case of our ExceptionFilter, where we need to reliably send events/exceptions."
Sentry.captureException has a different interface - captureException(exception: any, hint?: ExclusiveEventHintOrCaptureContext): string; from @sentry/core/types/exports.d.ts.
That being said, I was able to use your tip about withScope along with the fact that scope has a captureException method to refactor our solution to this:
Sentry.withScope((scope) => {
SentryExceptionService.createHttpCustomExceptionScope(
errorResponse,
context,
scope,
{
appName: this.appName,
appVersion: this.appVersion,
serverUrl: this.serverUrl,
}
);
scope.captureException(exception);
});
The logic of adding tags and extras happens in createHttpCustomExceptionScope using scope.setTags and scope.setExtras, and I think it's a pretty clean solution and it's been working reliably. Thanks very much for your help!
Ahh, I see. Yeah, I think what you refactored this to is the best way to solve this - using withScope and scope.captureException 👍
I think the fundamental problem here is that the function-based captureContext does not work like you think it does - and honestly, it is pretty confusing and I think we should change it.
I identified a bug with the function-based scope updating mechanism that I'll also fix - that's the root of the problem there! We used to take the return value of the captureContext function, which we stopped doing because it is a bit confusing under the hood (it is passed to scope.update()` but doesn't actually update the scope...).
FYI, we have just released v7.102.0, which should also have the function-based syntax fixed again! Thank you for reporting this.
Thanks for the update and for the quick fix, very impressive!