getsentry / sentry-docs

Sentry's documentation (and tools to build it)
https://docs.sentry.io
Other
319 stars 1.35k forks source link

How to set a different root trace for a sentry span? #10060

Open jtomaszewski opened 2 weeks ago

jtomaszewski commented 2 weeks ago

Core or SDK?

Platform/SDK

Which part? Which one?

nodejs

Description

I have custom code handling async jobs. I'd like to have each async job as a separate transaction - just as each HTTP request/response in the server is a separate transaction.

How can I achieve this with node.js Sentry SDK?

I've been trying to tweak this code for hours and I still don't know how it should be done. See my current code:

/* eslint-disable @typescript-eslint/return-await */
import { Injectable } from '@nestjs/common';
import * as Sentry from '@sentry/node';
import { Job } from 'bullmq';
import { JobMiddleware } from './job-middleware';
import { getJobName } from './job-name';

@Injectable()
export class SentryJobMiddleware<JobResult>
  implements JobMiddleware<JobResult>
{
  async run(job: Job, next: () => Promise<JobResult>): Promise<JobResult> {
    return await Sentry.withIsolationScope(async (scope) => {
      scope.setTags(buildSentryTagsForJob(job));
      return await this.runInSpan(scope, job, next);
    });
  }

  private async runInSpan(
    scope: Sentry.Scope,
    job: Job,
    next: () => Promise<JobResult>,
  ): Promise<JobResult> {
    const tags = buildSentryTagsForJob(job);

    return await Sentry.startSpanManual(
      {
        name: getJobName(job),
        op: 'job.process',
        attributes: {
          ...tags,
          ...(typeof job.data === 'object' && job.data
            ? Object.fromEntries(
                // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
                Object.entries(job.data).map(([k, v]) => {
                  return [`job.data.${k}`, v as any];
                }),
              )
            : undefined),
        },
        scope,
      },
      async (span) => {
        try {
          const result = await next();
          span?.setStatus(Sentry.getSpanStatusFromHttpCode(200));
          return result;
        } catch (error) {
          Sentry.captureException(error, {
            tags,
          });
          span?.setStatus(Sentry.getSpanStatusFromHttpCode(500));
          throw error;
        } finally {
          span?.end();
        }
      },
    );
  }
}

function buildSentryTagsForJob(job: Job<any, any, string>) {
  return {
    'job.name': getJobName(job),
    'job.token': job.token,
  };
}

How can I improve it, so that several async jobs aren't coupled with each other by a single trace id?

Currently this is how it looks for me. It's awful to me, as it can have hundreds of completely unrelated async jobs put in a single Sentry trace.

image

Suggested Solution

  1. Make SDK easier to use so that creating a sentry span, scope and capturing exceptions is all done out-of-the-box using a single function.
  2. Provide docs example.
getsantry[bot] commented 2 weeks ago

Assigning to @getsentry/support for routing ⏲️