As33
@periodic/
arsenic

Common Patterns

Production-tested patterns for every major combination of framework, database, and exporter.

1. Express + MongoDB

typescript
import express from 'express';
import mongoose from 'mongoose';
import { createMonitor, expressContext, mongooseAdapter } from '@periodic/arsenic';

const app = express();
const monitor = createMonitor({
  slowQueryThresholdMs: 200,
  exporter: (event) => {
    if (event.severity === 'critical') sendToPagerDuty(event);
    else if (event.severity === 'warning') sendToSlack(event);
    else logger.info(event);
  },
});

app.use(expressContext(monitor));
mongooseAdapter(monitor, mongoose);
app.listen(3000);

2. Express + PostgreSQL

typescript
import express from 'express';
import { Pool } from 'pg';
import { createMonitor, expressContext, pgAdapter } from '@periodic/arsenic';

const app = express();
const pool = new Pool({ host: process.env.POSTGRES_HOST });

const monitor = createMonitor({
  slowQueryThresholdMs: 150,
  exporter: (event) => { if (event.severity === 'critical') sendToSlack(event); },
});

app.use(expressContext(monitor));
pgAdapter(monitor, pool);
app.listen(3000);

3. Fastify + Prisma

typescript
import Fastify from 'fastify';
import { PrismaClient } from '@prisma/client';
import { createMonitor, fastifyContext, prismaAdapter } from '@periodic/arsenic';

const app = Fastify();
const prisma = new PrismaClient();
const monitor = createMonitor({
  slowQueryThresholdMs: 200,
  exporter: (event) => logger.info(event),
});

await app.register(fastifyContext(monitor, { attachUser: (req) => req.user?.id }));
prismaAdapter(monitor, prisma);
await app.listen({ port: 3000 });

4. OpenTelemetry exporter

typescript
import { createMonitor, createOtelExporter } from '@periodic/arsenic';

const monitor = createMonitor({
  exporter: createOtelExporter({
    serviceName: process.env.SERVICE_NAME || 'my-service',
    exportAsSpans: true,
    exportAsMetrics: true,
  }),
});

5. Multiple databases, separate monitors

typescript
const pgMonitor = createMonitor({
  slowQueryThresholdMs: 100,  // Stricter for SQL
  exporter: pgExporter,
});

const mongoMonitor = createMonitor({
  slowQueryThresholdMs: 300,  // Looser for MongoDB
  exporter: mongoExporter,
});

const redisMonitor = createMonitor({
  slowQueryThresholdMs: 50,   // Very strict for Redis
  exporter: redisExporter,
});

pgAdapter(pgMonitor, pool);
mongooseAdapter(mongoMonitor, mongoose);
redisAdapter(redisMonitor, redis);

6. Multiple exporters

typescript
const monitor = createMonitor({
  exporter: async (event) => {
    await Promise.allSettled([
      console.log(event),
      sendToDatadog(event),
      saveToDB(event),
    ]);
  },
});

7. Production configuration

src/config/monitor.ts
typescript
import { createMonitor, SignalSeverity } from '@periodic/arsenic';

const isDev = process.env.NODE_ENV === 'development';

export const monitor = createMonitor({
  slowQueryThresholdMs: isDev ? 500 : 200,
  emitPositiveSignals: isDev,
  includeDocs: isDev,

  exporter: async (event) => {
    logger.info(event, 'db.event');
    if (!isDev && event.severity === SignalSeverity.CRITICAL) {
      await sendToPagerDuty(event);
    }
    if (event.severity === SignalSeverity.WARNING) {
      await sendToSlack(event);
    }
  },
});

export default monitor;

Rate limit your exporter

In high-traffic systems, add rate limiting to your exporter to avoid flooding PagerDuty or Slack. Use Promise.allSettled so a slow exporter never blocks your app.