Koa

Add @sentry/node as a dependency:

Copied
npm install --save @sentry/node

Initialize the Sentry SDK and install the on error hook:

Copied
import Koa from "koa";
import * as Sentry from "@sentry/node";

// or using CommonJS
// const Koa = require('koa');
// const Sentry = require('@sentry/node');

const app = new Koa();

Sentry.init({ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" });

app.on("error", (err, ctx) => {
  Sentry.withScope(function(scope) {
    scope.addEventProcessor(function(event) {
      return Sentry.Handlers.parseRequest(event, ctx.request);
    });
    Sentry.captureException(err);
  });
});

app.listen(3000);

Monitor Performance

Copied
npm install --save @sentry/node @sentry/tracing

Create and attach a transaction to each request:

Copied
const Sentry = require("@sentry/node");
const {
  extractTraceparentData,
  Span,
  stripUrlQueryAndFragment,
} = require("@sentry/tracing");
const Koa = require("koa");
const app = new Koa();
const domain = require("domain");

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",

  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

// not mandatory, but adding domains does help a lot with breadcrumbs
const requestHandler = (ctx, next) => {
  return new Promise((resolve, _) => {
    const local = domain.create();
    local.add(ctx);
    local.on("error", err => {
      ctx.status = err.status || 500;
      ctx.body = err.message;
      ctx.app.emit("error", err, ctx);
    });
    local.run(async () => {
      Sentry.getCurrentHub().configureScope(scope =>
        scope.addEventProcessor(event =>
          Sentry.Handlers.parseRequest(event, ctx.request, { user: false })
        )
      );
      await next();
      resolve();
    });
  });
};

// this tracing middleware creates a transaction per request
const tracingMiddleWare = async (ctx, next) => {
  const reqMethod = (ctx.method || "").toUpperCase();
  const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);

  // connect to trace of upstream app
  let traceparentData;
  if (ctx.request.get("sentry-trace")) {
    traceparentData = extractTraceparentData(ctx.request.get("sentry-trace"));
  }

  const transaction = Sentry.startTransaction({
    name: `${reqMethod} ${reqUrl}`,
    op: "http.server",
    ...traceparentData,
  });

  ctx.__sentry_transaction = transaction;
  
  // We put the transaction on the scope so users can attach children to it
  Sentry.getCurrentHub().configureScope(scope => {
    scope.setSpan(transaction);
  });

  ctx.res.on("finish", () => {
    // Push `transaction.finish` to the next event loop so open spans have a chance to finish before the transaction closes
    setImmediate(() => {
      // if using koa router, a nicer way to capture transaction using the matched route
      if (ctx._matchedRoute) {
        const mountPath = ctx.mountPath || "";
        transaction.setName(`${reqMethod} ${mountPath}${ctx._matchedRoute}`);
      }
      transaction.setHttpStatus(ctx.status);
      transaction.finish();
    });
  });

  await next();
};

app.use(requestHandler);
app.use(tracingMiddleWare);

// usual error handler
app.on("error", (err, ctx) => {
  Sentry.withScope(scope => {
    scope.addEventProcessor(event => {
      return Sentry.Handlers.parseRequest(event, ctx.request);
    });
    Sentry.captureException(err);
  });
});
// the rest of your app

Subsequent Custom Child Spans

The following example creates a span for a part of the code that contains an expensive operation and sends the result to Sentry. You will need to use the transaction stored in the context.

Copied
const myMiddleware = async (ctx, next) => {
  let span;
  const transaction = ctx.__sentry_transaction;
  if (transaction) {
    span = transaction.startChild({
      description: route,
      op: "myMiddleware",
    });
  }
  await myExpensiveOperation();
  if (span) {
    span.finish();
  }
  return next();
};