sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

Proposal (Performance): Some convenience API to wrap a Promise with a Span/Transaction

Open jennmueng opened this issue 4 years ago • 1 comments

Suggestion for a simple convenience wrapper to wrap any promise. This comes from me instrumenting Tour and finding it tedious to have to wrap every promise manually when it could easily be done by a wrapper, so I made these convenience methods to use, and I think it could be very useful to our users to have these or something like them officially.

const withTransaction = <A>(
  promise: Promise<A>,
  context: TransactionContext
): Promise<A> => {
  const transaction = Sentry.startTransaction(context);

  return promise
    .catch((e) => {
      transaction.setStatus(SpanStatus.UnknownError);

      throw e;
    })
    .finally(() => {
      transaction.finish();
    });
};


const withSpan = <A>(
  promise: Promise<A>,
  context: SpanContext,
  transaction?: Transaction // Leave transaction arg empty to use scope transaction
): Promise<A> => {
  const _transaction =
    transaction ?? Sentry.getCurrentHub().getScope().getTransaction();

  const span = _transaction.startChild(context);

  return promise
    .catch((e) => {
      span.setStatus(SpanStatus.UnknownError);

      throw e;
    })
    .finally(() => {
      span.finish();
    });
};

Use case:

import { doSomething } from "some-library"

const wrappedDoSomething = Sentry.withTransaction(doSomething);

// Already wrapped with a transaction
Sentry.withTransaction(
  doSomething({
    param: "Hello",
  }),
  { op: "some-library-process", name: "Use lib" }
).then((response) => {
  // ...
});

// Similar with withSpan...

This is similar to https://github.com/getsentry/sentry-java/issues/1154

jennmueng avatar Jan 20 '21 20:01 jennmueng

I would take this issue further & add some generic tracing methods to support various use-cases:

const SentryTracing = require('@sentry/tracing');

function runTheThing() {
  // Should support async functions & promises too
}

SentryTracing.wrapHandler({ op: 'foo' }, runTheThing);

Or even a simple create-a-span-at-the-current-level function:

const Sentry = require('@sentry/node');
const SentryTracing = require('@sentry/tracing');

const span = SentryTracing.createChildSpan({
  op: 'foo'
});

try {
  // Do something optimistically that fails
} catch (err) {
  Sentry.captureException(err);
} finally {
  span && span.finish();
}

Anything to stop me writing these kinds of helper functions:

function startSentryTransactionChild({ op, description }) {
  try {
    const parentSpan = Sentry.getCurrentHub().getScope().getSpan();
    const span = parentSpan.startChild({ op, description });
    return span ? () => span.finish() : () => null;
  } catch (err) {
    return () => null;
  }
}

const stopSpan = startSentryTransactionChild({ op: 'foo' });
// Do something optimistically
stopSpan();

jdrydn avatar Jun 03 '21 20:06 jdrydn

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

github-actions[bot] avatar Jan 21 '23 00:01 github-actions[bot]