Core Concepts
Understanding the three-layer architecture: monitors observe, adapters instrument, and exporters route.
The Monitor
The Monitor is the core observation engine. Call createMonitor() once, export it, and pass it to adapters and framework middleware.
- No global state — each monitor is fully independent
- Correlates database queries to active HTTP requests via AsyncLocalStorage
- Detects 60+ semantic signals across three severity levels
- Never blocks — all exports are asynchronous and isolated
- Provides callsite attribution (file + line number) for every query
const monitor = createMonitor({
slowQueryThresholdMs: 200,
emitPositiveSignals: false,
includeDocs: true,
exporter: (event) => console.log(event),
});Request correlation via AsyncLocalStorage
Framework middleware uses Node.js's AsyncLocalStorage to propagate request context through the entire call stack. Every database query executed within a request handler is automatically correlated to that request.
app.use(expressContext(monitor));
app.get('/api/orders', async (req, res) => {
const orders = await Order.find({ userId: req.user.id });
// Event includes: request.method, request.route, request.id
res.json(orders);
});Adapters
Adapters hook into database drivers cleanly — no monkey-patching, no prototype mutation. They observe queries and forward events to the monitor.
mongooseAdapter(monitor, mongoose); // MongoDB via Mongoose
prismaAdapter(monitor, prisma); // SQL via Prisma
pgAdapter(monitor, pool); // Raw PostgreSQL pg
redisAdapter(monitor, redis); // ioredis / redisExporters
An exporter is just a function: (event: ForgeEvent) => void | Promise<void>. You decide where events go.
// Single destination
const monitor = createMonitor({
exporter: (event) => sendToDatadog(event),
});
// Multiple destinations
const monitor = createMonitor({
exporter: async (event) => {
await Promise.allSettled([
sendToDatadog(event),
saveToDB(event),
event.severity === 'critical' ? sendToPagerDuty(event) : null,
]);
},
});Callsite attribution
Arsenic walks the call stack at query time to identify the exact source file and line that triggered the query.
{
"callsite": {
"file": "src/services/OrderService.ts",
"line": 47
}
}Design principles
- Core is pure TypeScript with zero dependencies
- Adapters hook into drivers cleanly, no prototype mutation
- Frameworks attach request context via AsyncLocalStorage only
- Exporters are just functions — bring your own destination
- No magic on import — nothing runs until you call
createMonitor - Exporter errors never crash your application