As33
@periodic/
arsenic

Exporters

An exporter is just a function that receives every ForgeEvent emitted by the monitor. You decide where events go — PagerDuty, Slack, Datadog, OpenTelemetry, a database, or stdout. Bring your own destination.

Exporter signature

typescript
type Exporter = (event: ForgeEvent) => void | Promise<void>

const monitor = createMonitor({
  exporter: (event) => { /* your routing logic */ },
});

Severity-based routing

typescript
import { SignalSeverity } from '@periodic/arsenic';

const monitor = createMonitor({
  exporter: (event) => {
    switch (event.severity) {
      case SignalSeverity.CRITICAL:
        sendToPagerDuty(event);
        break;
      case SignalSeverity.WARNING:
        sendToSlack(event);
        break;
      case SignalSeverity.INFO:
        logger.info('db.event', event);
        break;
    }
  },
});

Multiple destinations

typescript
const monitor = createMonitor({
  exporter: async (event) => {
    // allSettled — one failing exporter never blocks others
    await Promise.allSettled([
      sendToDatadog(event),
      saveToDB(event),
      event.severity === 'critical' ? sendToPagerDuty(event) : null,
    ]);
  },
});

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,
  }),
});

Structured logging with @periodic/iridium

typescript
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { createMonitor } from '@periodic/arsenic';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const monitor = createMonitor({
  exporter: (event) => logger.info('db.event', event),
});
// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Rate limiting your exporter

typescript
// Prevent alert storms on critical signals
const lastAlerted = new Map<string, number>();
const COOLDOWN_MS = 60000; // 1 minute

const monitor = createMonitor({
  exporter: async (event) => {
    const key = `${event.model}:${event.signals.join(',')}`;
    const last = lastAlerted.get(key) || 0;

    if (Date.now() - last > COOLDOWN_MS) {
      lastAlerted.set(key, Date.now());
      await sendToPagerDuty(event);
    }

    // Always log regardless of rate limit
    logger.info(event, 'db.event');
  },
});

Sentry integration

typescript
import * as Sentry from '@sentry/node';

const monitor = createMonitor({
  exporter: (event) => {
    if (event.severity === 'critical') {
      Sentry.captureEvent({
        message: event.signals.join(', '),
        level: 'error',
        extra: event,
      });
    }
  },
});

Exporter errors never crash your app

Arsenic wraps all exporter calls. If your exporter throws, the error is caught and your application continues normally. Monitor your exporter for failures with a try/catch inside if needed.