Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set User on transaction directly ? #7715

Closed
schankam opened this issue Apr 3, 2023 · 2 comments
Closed

Set User on transaction directly ? #7715

schankam opened this issue Apr 3, 2023 · 2 comments

Comments

@schankam
Copy link

schankam commented Apr 3, 2023

Problem Statement

Would there be a way to set a user directly on a transaction, instead of setting it using Scope.configure ?

For some reasons I am using a Sentry plugin with my graphQL implementation, but it seems that the user is not correctly set to my transactions (wrong user attached to the transaction of another user)

You'll find below the code of my plugin.

TLDR: I am creating my transaction first, then setting up the user as well as attaching the transaction as a span in Scope.configureScope. If I am doing something wrong, please let me know. I am expecting my transaction to have the correct user attached at that point but this is not the case.

const SentryPlugin: ApolloServerPlugin = {
  async requestDidStart({ request, context }: GraphQLRequestContext) {
    const sentryTransaction = Sentry.startTransaction({
      op: 'gql',
      name: request?.operationName || 'GraphQLTransaction'
    });
    const { user } = context;
    if (user) {
      Sentry.configureScope((scope) => {
        scope.setUser({
          id: user.uid,
          email: user.email || null
        });
        scope.setSpan(sentryTransaction);
      });
    }

    sentryTransaction.setData('query', request.query);
    if (request.variables) {
      sentryTransaction.setData('variables', JSON.stringify(request.variables));
    }
    if (request.http?.headers?.get('x-app-platform')) {
      sentryTransaction.setTag('app-platform', request.http?.headers?.get('x-app-platform'));
    }
    if (request.http?.headers?.get('user-agent')) {
      sentryTransaction.setTag('user-agent', request.http?.headers?.get('user-agent'));
    }
    if (request.http?.headers?.get('x-app-version')) {
      sentryTransaction.setTag('app-version', request.http?.headers?.get('x-app-version'));
    }
    if (context.user?.appInfo?.userLocale) {
      sentryTransaction.setTag('user-locale', context.user.appInfo.userLocale);
    }
    return {
      async willSendResponse() {
        sentryTransaction.finish();
      },
      async executionDidStart() {
        return {
          willResolveField({ info }) {
            // hook for each new resolver
            const span = sentryTransaction.startChild({
              op: 'resolver',
              description: `${info.parentType.name}.${info.fieldName}`
            });
            return () => {
              // this will execute once the resolver is finished
              span.finish();
            };
          }
        };
      },
      async didEncounterErrors(ctx) {
        // If we couldn't parse the operation, don't do anything here
        if (!ctx.operation) {
          return;
        }
        for (const err of ctx.errors) {
          // Add scoped report details and send to Sentry
          Sentry.withScope((scope) => {
            // Annotate whether failing operation was query/mutation/subscription
            scope.setTag('kind', ctx.operation?.operation || null);
            scope.setTag('error-code', err.extensions?.code as string);
            if (ctx.context.user?.teams && Array.isArray(ctx.context.user.teams) && context.user.teams.length) {
              scope.setTag('user-teams', ctx.context.user.teams.map((t: TeamDocument) => t.id).join(','));
            }
            if (request.http?.headers?.get('x-app-platform')) {
              scope.setTag('app-platform', request.http?.headers?.get('x-app-platform'));
            }
            if (request.http?.headers?.get('x-app-version')) {
              scope.setTag('app-version', request.http?.headers?.get('x-app-version'));
            }
            if (err.path) {
              // We can also add the path as breadcrumb
              scope.addBreadcrumb({
                category: 'query-path',
                message: err.path.join(' > '),
                level: 'debug'
              });
            }
            scope.setSpan(sentryTransaction);
            Sentry.captureException(err);
          });
        }
      }
    };
  }
};

export default SentryPlugin;

Solution Brainstorm

Setting tags directly on the transaction object works well, can it be possible to also set the user directly on the transaction object ? This way we're sure to set the correct user on the correct transaction in the server lifecycle hook, and not on a global scope. I understand for a frontend that there will only be one user, but for the backend I'd like to have each different user for each of my transactions.

@Lms24
Copy link
Member

Lms24 commented Apr 4, 2023

Hi @schankam, thanks for writing in!

At this point, it is not possible to set a user onto a transaction (or span) directly.

The could be a few reasons why the users are mixed up.
One of them might be that you're using configureScope which mutates the current scope instead of pushing a new scope onto the stack. Have you tried using Sentry.withScope instead and starting your transaction, setting the user and tags within the callback?

Another reason might be scope bleed because you're in an async (for instance, the transaction is finished in an async function). This is a known issue and unfortunately a hard problem to solve (see #4071). We'll work on replacing domains with AsyncLocalStorage in the future but for now there's little we can do about this.

@schankam
Copy link
Author

schankam commented Apr 4, 2023

Hey @Lms24 !

Thanks for replying. Yes, you are right I am in the second case here.

I've been doing some research since I posted and I found a few issues on Github #4071, #3572 and #4372.

I'm happy to see that you're working on a better scope management in the 2023 roadmap. We'll wait patiently then, hopping this is coming soon !

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

No branches or pull requests

3 participants