Skip to content

Automatic capture ​

As soon as init() runs, UncaughtExceptionIntegration listens for the process's uncaughtException and unhandledRejection events. A crash is reported with its full stack, everything still buffered is flushed, and the process exits with code 1 — the same outcome as without the SDK, only now you know about it.

A rejection whose reason isn't an Error (Promise.reject('nope')) is wrapped in one, so it still gets a name, message and stack.

Errors your framework catches ​

Most errors in a server never become uncaught: Express, Fastify, Koa, NestJS and friends catch a throw inside a route handler and turn it into a 500 response. Report them from the framework's error handler.

Express — register the error middleware after all routes:

ts
import { captureException } from '@buglapse/node'

app.use((error, request, response, next) => {
    captureException(error)
    next(error) // keep Express's own handling (or send your own response)
})

Fastify:

ts
fastify.setErrorHandler((error, request, reply) => {
    if (!error.statusCode || error.statusCode >= 500) captureException(error)
    reply.send(error)
})

The check above skips validation errors and other 4xx — they're expected client mistakes, not bugs. Apply the same idea in any framework.

With HttpServerIntegration enabled, an error captured here carries the request's URL and method, the breadcrumbs of that one request, and a link to its trace.

Capturing manually ​

Report a caught error yourself with captureException():

ts
import { addBreadcrumb, captureException } from '@buglapse/node'

addBreadcrumb({ type: 'payment', level: 'info', message: 'Charging card', data: { orderId } })

try {
    await chargeCard(order)
} catch (error) {
    captureException(error)
    throw error
}

captureException() takes an Error. If a library throws something else (a string, a plain object), wrap it first: captureException(new Error(String(reason))).

Capturing and re-throwing is fine: if the re-thrown error ends up uncaught, it's reported a second time — enable DedupeIntegration to drop that duplicate.

Background jobs and scripts ​

Queue workers, cron tasks and scripts don't go through an HTTP request, so give each unit of work its own scope with asyncSpan() (or withScope()) and report failures yourself:

ts
import { asyncSpan, captureException } from '@buglapse/node'

worker.process(async (job) => {
    await asyncSpan(`job ${job.name}`, async () => {
        try {
            await handle(job)
        } catch (error) {
            captureException(error)
            throw error // let the queue retry it
        }
    }, { op: 'queue.process' })
})

Every event captured inside carries the job's own breadcrumbs and trace, even while other jobs run at the same time. See Tags & Context.

A script that exits right after an error must flush first, or the report may never leave the process:

ts
import { captureException, flush } from '@buglapse/node'

try {
    await migrate()
} catch (error) {
    captureException(error)
    await flush()
    process.exit(1)
}

Adding detail to a single error ​

withScope() overlays tags and context for everything captured inside its callback, across every await, without touching what's set globally:

ts
import { captureException, withScope } from '@buglapse/node'

await withScope(async (scope) => {
    scope.setTag('tenant', tenant.slug)
    scope.setContext('import', { file: file.name, rows: file.rows })

    try {
        await importFile(file)
    } catch (error) {
        captureException(error)
    }
})

Cutting down noise ​

Two integrations drop exceptions before they're sent — and before they count toward the quota:

For anything more specific, add a preprocessor. It sees the original Error object, so you can filter by class:

ts
import { addPreprocessor } from '@buglapse/node'

addPreprocessor((event, error) => {
    if (event.type === 'exception' && error instanceof NotFoundError) return null
    return event
})

Server-side, Settings → Filters can also drop exceptions by error name or message.

Failed outgoing requests ​

A 500 from an upstream API is a normal response as far as fetch() is concerned — nothing throws. HttpErrorsIntegration reports such responses and network failures as error logs (on the Logs page, not Exceptions).

How captured exceptions show up on the dashboard ​

Every exception appears on the project's Exceptions page and is grouped into an Issue by fingerprint, so repeats of the same error roll into one entry with a count and first/last seen times instead of flooding the list. See Issues.

Stack frames point at your server's files as they are on disk. If you bundle or minify server code, set release and upload the source maps for that build — see Source maps.