|
| 1 | +### Koa |
| 2 | + |
| 3 | +Creates and attach a transaction to each context |
| 4 | + |
| 5 | +```javascript |
| 6 | +const Sentry = require('@sentry/node') |
| 7 | +const { Span } = require('@sentry/apm') |
| 8 | +const Koa = require('koa') |
| 9 | +const app = new Koa() |
| 10 | +const domain = require('domain') |
| 11 | + |
| 12 | +Sentry.init({ |
| 13 | + dsn: "___PUBLIC_DSN___", |
| 14 | + tracesSampleRate: 0.25 |
| 15 | +}) |
| 16 | + |
| 17 | +// not mandatory, but adding domains do help a lot with breadcrumbs |
| 18 | +const requestHandler = (ctx, next) => { |
| 19 | + return new Promise((resolve, reject) => { |
| 20 | + const local = domain.create() |
| 21 | + local.add(ctx) |
| 22 | + local.on('error', (err) => { |
| 23 | + ctx.status = err.status || 500 |
| 24 | + ctx.body = err.message |
| 25 | + ctx.app.emit('error', err, ctx) |
| 26 | + }) |
| 27 | + local.run(async() => { |
| 28 | + Sentry.getCurrentHub().configureScope(scope => |
| 29 | + scope.addEventProcessor((event) => Sentry.Handlers.parseRequest(event, ctx.request, {user: false})) |
| 30 | + ) |
| 31 | + await next() |
| 32 | + resolve() |
| 33 | + }) |
| 34 | + }) |
| 35 | +} |
| 36 | + |
| 37 | +// this tracing middleware creates a transaction per request |
| 38 | +const tracingMiddleWare = async (ctx, next) => { |
| 39 | + // captures span of upstream app |
| 40 | + const sentryTraceId = ctx.request.get('sentry-trace') |
| 41 | + let traceId |
| 42 | + let parentSpanId |
| 43 | + if (sentryTraceId) { |
| 44 | + const span = Span.fromTraceparent(sentryTraceId) |
| 45 | + if (span) { |
| 46 | + traceId = span.traceId |
| 47 | + parentSpanId = span.parentSpanId |
| 48 | + } |
| 49 | + } |
| 50 | + const transaction = Sentry.startTransaction({ |
| 51 | + name: `${ctx.method} ${ctx.url}`, |
| 52 | + op: 'http.server', |
| 53 | + parentSpanId, |
| 54 | + traceId, |
| 55 | + }) |
| 56 | + ctx.__sentry_transaction = transaction |
| 57 | + await next() |
| 58 | + |
| 59 | + // if using koa router, a nicer way to capture transaction using the matched route |
| 60 | + if (ctx._matchedRoute) { |
| 61 | + const mountPath = ctx.mountPath || '' |
| 62 | + transaction.setName(`${ctx.method} ${mountPath}${ctx._matchedRoute}`) |
| 63 | + } |
| 64 | + transaction.setHttpStatus(ctx.status) |
| 65 | + transaction.finish() |
| 66 | +} |
| 67 | + |
| 68 | +app.use(requestHandler) |
| 69 | +app.use(tracingMiddleWare) |
| 70 | + |
| 71 | +// usual error handler |
| 72 | +app.on('error', (err, ctx) => { |
| 73 | + Sentry.withScope(function (scope) { |
| 74 | + scope.addEventProcessor(function (event) { |
| 75 | + return Sentry.Handlers.parseRequest(event, ctx.request) |
| 76 | + }) |
| 77 | + Sentry.captureException(err) |
| 78 | + }) |
| 79 | +}) |
| 80 | +// the rest of your app |
| 81 | +``` |
| 82 | + |
| 83 | +#### Subsequent manual child transactions |
| 84 | + |
| 85 | +The following example creates a transaction 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 |
| 86 | + |
| 87 | +```javascript |
| 88 | +const myMiddleware = async (ctx, next) => { |
| 89 | + let span |
| 90 | + const transaction = ctx.__sentry_transaction |
| 91 | + if (transaction) { |
| 92 | + span = transaction.startChild({ |
| 93 | + description: route, |
| 94 | + op: 'myMiddleware', |
| 95 | + }) |
| 96 | + } |
| 97 | + await myExpensiveOperation() |
| 98 | + if (span) { |
| 99 | + span.finish() |
| 100 | + } |
| 101 | + return next() |
| 102 | +} |
| 103 | +``` |
0 commit comments