As33
@periodic/
arsenic

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
typescript
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.

typescript
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.

typescript
mongooseAdapter(monitor, mongoose);  // MongoDB via Mongoose
prismaAdapter(monitor, prisma);       // SQL via Prisma
pgAdapter(monitor, pool);             // Raw PostgreSQL pg
redisAdapter(monitor, redis);         // ioredis / redis

Exporters

An exporter is just a function: (event: ForgeEvent) => void | Promise<void>. You decide where events go.

typescript
// 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.

json
{
  "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
Applications create monitors, adapters observe queries, exporters decide where data goes. Each layer is fully decoupled.