davepi-plugin-sentry
v0.1.0
Published
Sentry error tracking + performance tracing for dAvePi. Initializes @sentry/node when SENTRY_DSN is set; forwards 5xx errors to Sentry after the framework's errorHandler has produced the response (shape unchanged); auto-tags user.id, accountId, and the fr
Downloads
163
Maintainers
Readme
davepi-plugin-sentry
Sentry error tracking + performance tracing for dAvePi.
Initializes @sentry/node at boot when SENTRY_DSN is
set, forwards 5xx errors to Sentry after the framework's
errorHandler has produced the response (so the response shape is
unchanged), auto-tags every event with the failing user / tenant /
request ID, and runs the same redaction rules as the framework's pino
logger so secrets never leave the process. Dormant when SENTRY_DSN is
unset.
Install
npm install davepi-plugin-sentryAdd it to your project's package.json under davepi.plugins:
{
"davepi": {
"plugins": ["davepi-plugin-sentry"]
}
}Sentry project setup
In sentry.io, create a project of platform Node.js → Express.
Copy the DSN from Settings → Projects → (your project) → Client Keys (DSN). It looks like:
https://[email protected]/7891011Set it in your environment:
export SENTRY_DSN="https://[email protected]/7891011" export SENTRY_ENVIRONMENT="production" export SENTRY_RELEASE="$(git rev-parse HEAD)" # or leave unset; see belowBoot the app, then deliberately trigger a 5xx (e.g. a route that throws). Within seconds it lands in the Sentry Issues dashboard with the stack trace, the
req_idtag, and theuser/accountIdcontext attached. See the Sentry docs for what the dashboard shows: Issues · Tracing / Performance · Breadcrumbs.
Configure
All config is env-driven:
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| SENTRY_DSN | yes (else dormant) | — | The DSN from your Sentry project. |
| SENTRY_ENVIRONMENT | no | NODE_ENV | Environment tag (production, staging, …). |
| SENTRY_RELEASE | no | consumer package.json version, then git rev-parse HEAD | Release identifier used for regression detection + source-map association. |
| SENTRY_TRACES_SAMPLE_RATE | no | 0 (off) | 0.0–1.0. Turns on Apollo (GraphQL) + Mongoose query spans. 0.1 is a sensible prod starting point. |
| SENTRY_PROFILES_SAMPLE_RATE | no | 0 (off) | Requires the optional @sentry/profiling-node dep; ignored (with a warning) if it isn't installed. |
| SENTRY_IGNORE_ERRORS | no | — | Comma-separated error class names / message substrings to drop before send. |
| SENTRY_MIN_STATUS | no | 500 | Only forward errors whose mapped HTTP status is ≥ this. Set 400 to also forward 4xx (usually noise). |
| SENTRY_SEND_DEFAULT_PII | no | false | Sentry's sendDefaultPii. Off by default — even with field redaction the request body / user-agent / IP carry PII. Flip to true only if ops explicitly want it. |
A missing SENTRY_DSN is intentional: the plugin logs a warning and
stays dormant. captureException / setRequestContext become no-ops in
that state (they don't throw), so it's safe to ship the plugin in a
project before the DSN is provisioned without crashing boot or
peppering hooks with try/catch.
What you get out of the box
- 5xx capture with the response shape untouched. The plugin mounts
an error-forwarding middleware just before the framework's terminal
errorHandler. It captures the exception and then callsnext(err), soerrorHandlerstill writes the canonical{ error: { code, message } }body. There is a single capture path — no double-reporting. - 4xx stays out of the way. The default
SENTRY_MIN_STATUS=500keeps known-4xx noise (ValidationError,NotFoundError,UnauthorizedError, raw MongooseValidationError/CastError, duplicate-key409s) out of Sentry. The status mapping mirrorsmiddleware/errorHandler.js. - User + tenant + request context. Every event auto-tags
user.idandaccountIdfromreq.user, plusreq_id— the same value the framework's pino log line carries (req.id), so a Sentry event stitches directly to its log line. - Redaction parity with your logs.
beforeSendruns the same field rules asutils/logger.js(authorization,cookie,set-cookie,*.password,*.token,*.encryptedPassword) over the event'srequest,user,extra,contexts, and breadcrumb data. Apasswordin a request body is[REDACTED]in Sentry, not cleartext — single source of truth, resolved live from the framework's logger config. - Release tagging.
SENTRY_RELEASE, else the consumer project'spackage.jsonversion, elsegit rev-parse HEADif a checkout is present at boot.
Performance tracing (opt-in)
Set SENTRY_TRACES_SAMPLE_RATE to a non-zero rate to turn on tracing.
@sentry/node then instruments GraphQL operations (Apollo v4/v5, which
dAvePi runs) and Mongoose queries as spans on each request transaction,
correlated by the same req_id.
export SENTRY_TRACES_SAMPLE_RATE=0.1 # sample 10% of requests in prodTracing every Mongoose query is high-cardinality and not free — keep the
rate low in production (0.1 or lower) and dial it up only while
investigating. Profiling (SENTRY_PROFILES_SAMPLE_RATE) additionally
requires installing the optional peer:
npm install @sentry/profiling-nodeProgrammatic API
const sentry = require('davepi-plugin-sentry');
// Manually capture an exception with the standard framework context
// (current request's user / accountId / req_id / extras) attached.
try {
await doRiskyThing();
} catch (err) {
sentry.captureException(err, { tags: { feature: 'risky-thing' } });
}
// Add extra context for the lifetime of the current request — shows up
// on any event captured later in the same request.
sentry.setRequestContext({ feature: 'onboarding' });
// The raw @sentry/node SDK, for advanced use.
const Sentry = sentry.client;Both captureException and setRequestContext are safe to call from a
schema lifecycle hook: when the plugin is dormant they no-op.
setRequestContext relies on the per-request AsyncLocalStorage scope
the plugin establishes at the head of the Express stack, so it's a no-op
outside a request.
Capturing from a hook
// schema/versions/v1/order.js
const sentry = require('davepi-plugin-sentry');
module.exports = {
path: 'order',
collection: 'order',
fields: [/* ... */],
hooks: {
afterCreate: async ({ record }) => {
try {
await chargeCard(record);
} catch (err) {
// 4xx-ish business errors won't reach the global forwarder; send
// them explicitly with feature context for triage.
sentry.captureException(err, { tags: { feature: 'billing' }, extra: { orderId: record._id } });
throw err;
}
},
},
};Mount ordering
The plugin wires three things in setup():
- A request-context middleware at the head of the Express stack
(it
app.uses then moves its layer to index 0), establishing the per-requestAsyncLocalStoragescope. - An error-forwarding middleware near the tail.
schemaLoader.moveErrorHandlerToEnd()— the framework helper that re-asserts the terminalerrorHandlerat the very end, so the order becomes[…routes, sentry-forwarder, errorHandler]: capture, then write the unchanged response.
Failure handling
- Error forwarder: capture is wrapped in
try/catchand the handler always callsnext(err). A Sentry outage never alters the HTTP response or surfaces as anunhandledRejection. - Boot: a missing
SENTRY_DSN, or@sentry/nodefailing to load, logs once and leaves the plugin dormant. Boot does not fail. - PII:
sendDefaultPiidefaults tofalse. Even so, review what your routes put in request bodies — redaction strips known secret fields, not arbitrary PII.
Advanced
require('davepi-plugin-sentry') returns a default instance reading
config from process.env. Use the createPlugin factory to inject a
custom env source, or a mock SDK in tests:
const { createPlugin } = require('davepi-plugin-sentry');
module.exports = createPlugin({
env: { ...process.env, SENTRY_TRACES_SAMPLE_RATE: '0.25' },
});License
ISC
