monirail
v1.0.12
Published
Monitor Railway services and notify your team when anomalies are detected
Maintainers
Readme
Monirail
Monitor Railway services and notify your team when anomalies are detected
[!NOTE] This must be used with the Bun runtime
Quick Start
import { monitor, source, notify, watch } from "monirail";
// Create a Slack notifier
const slack = notify({
type: "slack",
webhookUrl: process.env.SLACK_WEBHOOK_URL,
});
// Monitor for errors in your logs
const errorMonitor = monitor({
name: "Error Alert",
type: "match",
source: source({ type: "environment_logs" }),
filter: "@level:error",
notify: [slack],
});
// Monitor CPU usage
const cpuMonitor = monitor({
name: "High CPU",
type: "threshold",
source: source({
type: "metrics",
aggregate: "avg",
measure: "CPU_USAGE",
}),
value: 80,
notifyOn: "above",
notify: [slack],
});
// Check monitors every minute
watch(1, [errorMonitor, cpuMonitor]);API Documentation
monitor(options)
Creates a monitor that runs background checks and triggers notifications when conditions are met. Monitors are stateful—they track whether they've been triggered to avoid sending duplicate notifications.
Returns: Monitor — An object with a check() method and thenable interface (can be awaited directly).
Match
Continuously filter log events and trigger notifications when a match is found.
monitor({
name: "Error Alert",
description: "Alerts when errors appear in logs",
type: "match",
source: source({ type: "environment_logs" }),
filter: "@level:error",
timeWindow: 5,
notify: [slack],
});| Parameter | Type | Required | Default | Description |
| ------------- | -------------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------ |
| name | string | Yes | - | A unique name for the monitor. Used as a key to store state in the database. |
| description | string | No | - | A description of the monitor. |
| type | "match" | Yes | - | The monitor type. |
| source | Promise<SourceEnvironmentLogs \| SourceHttpLogs> | Yes | - | The data source to query. Must be environment_logs or http_logs. |
| filter | string | Yes | - | The filter query to match events against. Uses Railway's log filter syntax. |
| timeWindow | number | No | 5 | The duration in minutes to sample events over, starting from the current time. |
| notify | Notifier[] | Yes | - | An array of notifiers to send alerts to. |
Threshold
Trigger notifications when a metric or count of events crosses a threshold.
// Alert when average CPU exceeds 80%
monitor({
name: "High CPU",
type: "threshold",
source: source({
type: "metrics",
aggregate: "avg",
measure: "CPU_USAGE",
}),
value: 80,
notifyOn: "above",
notify: [slack],
});
// Alert when error count exceeds 10 in 5 minutes
monitor({
name: "Error Spike",
type: "threshold",
source: source({ type: "environment_logs" }),
filter: "@level:error",
value: 10,
notifyOn: "above",
notify: [slack],
});| Parameter | Type | Required | Default | Description |
| ---------------- | ------------------------------------------------------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | string | Yes | - | A unique name for the monitor. Used as a key to store state in the database. |
| description | string | No | - | A description of the monitor. |
| type | "threshold" | Yes | - | The monitor type. |
| source | Promise<SourceMetrics \| SourceEnvironmentLogs \| SourceHttpLogs> | Yes | - | The data source to query. |
| value | number | Yes | - | The threshold value to compare against. |
| notifyOn | "above" \| "above_or_equal" \| "below" \| "below_or_equal" | Yes | - | The comparison operator. Determines when the threshold is considered crossed. |
| notifyOnNoData | boolean | No | true | Whether to trigger a notification when no data is returned from the source. |
| timeWindow | number | No | 5 | The window of time in minutes to aggregate over. |
| filter | string | Cond. | - | The filter query for log sources. Required when using environment_logs or http_logs sources. Uses Railway's log filter syntax. |
| notify | Notifier[] | Yes | - | An array of notifiers to send alerts to. |
Availability
Trigger notifications when an HTTP service is unavailable. Makes an HTTP request to the service's static URL and checks if the response is successful.
monitor({
name: "API Health",
description: "Alerts when the API is down",
type: "availability",
source: source({ type: "service", service: "my-api" }),
path: "/health",
notify: [pagerduty],
});| Parameter | Type | Required | Default | Description |
| ------------- | ------------------------ | -------- | ------- | ---------------------------------------------------------------------------- |
| name | string | Yes | - | A unique name for the monitor. Used as a key to store state in the database. |
| description | string | No | - | A description of the monitor. |
| type | "availability" | Yes | - | The monitor type. |
| source | Promise<SourceService> | Yes | - | The service data source. Must be a service source. |
| path | string | No | "/" | The path to check for availability. |
| notify | Notifier[] | Yes | - | An array of notifiers to send alerts to. |
Custom
Trigger notifications when a custom condition is met. Use this for complex logic that doesn't fit the other monitor types.
monitor({
name: "Deployment Check",
type: "custom",
source: source({ type: "service", service: "my-api" }),
check: async () => {
const deployments = await railway.listDeployments({
/* ... */
});
const crashed = deployments.edges.filter(
(d) => d.node.status === "CRASHED",
);
return {
triggered: crashed.length > 0,
crashedCount: crashed.length,
deployments: crashed.map((d) => d.node.id),
};
},
notify: [slack],
});| Parameter | Type | Required | Default | Description |
| ------------- | ------------------------------------------------------------------------------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| name | string | Yes | - | A unique name for the monitor. Used as a key to store state in the database. |
| description | string | No | - | A description of the monitor. |
| type | "custom" | Yes | - | The monitor type. |
| source | Promise<SourceMetrics \| SourceEnvironmentLogs \| SourceHttpLogs \| SourceService> | Yes | - | The data source to use. |
| check | () => Promise<{ triggered: boolean } & Record<string, unknown>> | Yes | - | A function that returns whether the monitor is triggered. Any additional properties are included in the notification payload as data. |
| notify | Notifier[] | Yes | - | An array of notifiers to send alerts to. |
source(options)
Creates a data source for monitors to query. Returns a Promise that resolves to the configured source.
Returns: Promise<Source> — A source object with environment info, services, and a fetch() method for querying data.
Environment Logs
Use Railway environment logs as a data source. Can filter to specific services or query all services in the environment.
// All services in the current environment
source({ type: "environment_logs" });
// Specific services only
source({
type: "environment_logs",
services: ["api", "worker"],
});
// Different environment
source({
type: "environment_logs",
environment: "production",
services: ["api"],
});| Parameter | Type | Required | Default | Description |
| ------------- | -------------------- | -------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
| type | "environment_logs" | Yes | - | The source type. |
| environment | string | No | Current environment | The ID or name of the Railway environment to query. Defaults to the environment monirail is deployed to. |
| services | string[] | No | All services | An array of service IDs or names to filter logs to. If omitted, logs from all services are included. |
HTTP Logs
Use Railway service HTTP request/response logs as a data source.
source({
type: "http_logs",
service: "api",
});| Parameter | Type | Required | Default | Description |
| ------------- | ------------- | -------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
| type | "http_logs" | Yes | - | The source type. |
| environment | string | No | Current environment | The ID or name of the Railway environment to query. Defaults to the environment monirail is deployed to. |
| service | string | Yes | - | The ID or name of the Railway service to query HTTP logs from. |
Metrics
Use Railway metrics (CPU, memory, network, disk) as a data source.
// Average CPU usage across the environment
source({
type: "metrics",
aggregate: "avg",
measure: "CPU_USAGE",
});
// Max memory for a specific service
source({
type: "metrics",
service: "api",
aggregate: "max",
measure: "MEMORY_USAGE_GB",
});
// Sum of network egress
source({
type: "metrics",
aggregate: "sum",
measure: "NETWORK_EGRESS_GB",
});| Parameter | Type | Required | Default | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
| type | "metrics" | Yes | - | The source type. |
| environment | string | No | Current environment | The ID or name of the Railway environment to query. Defaults to the environment monirail is deployed to. |
| service | string | No | All services | The ID or name of a specific Railway service to query metrics from. |
| aggregate | "sum" \| "avg" \| "max" \| "min" | Yes | - | How to aggregate metric values over the time window. sum intelligently handles gaps from sleeping services. |
| measure | "CPU_USAGE" \| "MEMORY_USAGE_GB" \| "NETWORK_INGRESS_GB" \| "NETWORK_EGRESS_GB" \| "EPHEMERAL_DISK_USAGE_GB" | Yes | - | The metric to measure. |
Service
Use a Railway service as a data source. Primarily used for availability monitors.
source({
type: "service",
service: "api",
});| Parameter | Type | Required | Default | Description |
| ------------- | ----------- | -------- | ------------------- | ----------------------------------------------------------------------------------------------- |
| type | "service" | Yes | - | The source type. |
| environment | string | No | Current environment | The ID or name of the Railway environment. Defaults to the environment monirail is deployed to. |
| service | string | Yes | - | The ID or name of the Railway service. |
notify(options)
Creates a notifier that sends alerts to a specific channel. Notifications include rich formatting with links to your Railway dashboard, logs, and services.
Returns: Notifier — An object with a send(payload) method.
Slack
Send a message to a Slack channel using the Incoming Webhook integration.
notify({
type: "slack",
webhookUrl: process.env.SLACK_WEBHOOK_URL,
});| Parameter | Type | Required | Description |
| ------------ | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| type | "slack" | Yes | The notifier type. |
| webhookUrl | string | Yes | The Slack Incoming Webhook URL. Get one from the Slack App Directory. |
Discord
Send a message to a Discord channel using a Webhook. Messages are formatted as rich embeds with color-coding (red for triggered, green for resolved).
notify({
type: "discord",
webhookUrl: process.env.DISCORD_WEBHOOK_URL,
});| Parameter | Type | Required | Description |
| ------------ | ----------- | -------- | ----------------------------------------------------------------------------------- |
| type | "discord" | Yes | The notifier type. |
| webhookUrl | string | Yes | The Discord Webhook URL. Create one in your Discord server's Integrations settings. |
PagerDuty
Send events to PagerDuty using the Events API v2. Automatically handles incident creation and resolution with deduplication.
notify({
type: "pagerduty",
routingKey: process.env.PAGERDUTY_ROUTING_KEY,
severity: "critical",
});| Parameter | Type | Required | Default | Description |
| ------------ | ---------------------------------------------- | -------- | ---------------------- | --------------------------------------------------------------------------------------- |
| type | "pagerduty" | Yes | - | The notifier type. |
| routingKey | string | Yes | - | The PagerDuty Events API v2 integration key (routing key). |
| severity | "critical" \| "error" \| "warning" \| "info" | Yes | - | The severity of the incident. |
| component | string | No | Service name | The component of the incident. Defaults to the service name that triggered the monitor. |
| group | string | No | RAILWAY_PROJECT_NAME | The logical grouping of the incident. |
| client | string | No | RAILWAY_SERVICE_NAME | The name of the monitoring client. |
| clientUrl | string | No | Railway service URL | A URL to the monitoring client. Defaults to the monirail service URL in Railway. |
Webhook
Send a JSON payload to any HTTP endpoint via POST request.
notify({
type: "webhook",
url: "https://example.com/webhook",
});| Parameter | Type | Required | Description |
| --------- | ----------- | -------- | --------------------------------------- |
| type | "webhook" | Yes | The notifier type. |
| url | string | Yes | The URL to send the webhook payload to. |
Payload Schema:
type NotificationPayload = {
type: "match" | "threshold" | "availability" | "custom";
monitor: {
name: string;
description?: string;
source: { type: string; environment: Environment; services: Service[] };
// Additional fields vary by monitor type
};
triggered: boolean;
timestamp: Date;
// Additional fields vary by monitor type (value, threshold, url, duration, data, etc.)
};Custom
Send notifications through your own custom logic. Use this for email, SMS, or any other notification channel.
import sendgrid from "@sendgrid/mail";
notify({
type: "custom",
send: async (payload) => {
await sendgrid.send({
to: "[email protected]",
from: "[email protected]",
subject: `[${payload.triggered ? "ALERT" : "RESOLVED"}] ${payload.monitor.name}`,
text: `Monitor ${payload.monitor.name} ${payload.triggered ? "triggered" : "resolved"} at ${payload.timestamp}`,
});
},
});| Parameter | Type | Required | Description |
| --------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------ |
| type | "custom" | Yes | The notifier type. |
| send | (payload: NotificationPayload) => Promise<void> | Yes | A function that sends the notification. Receives the full payload. |
check(monitors)
Run all monitors once in parallel and send notifications for any that are triggered. Logs errors for any monitors that fail.
import { check } from "monirail";
await check([errorMonitor, cpuMonitor, availabilityMonitor]);| Parameter | Type | Required | Description |
| ---------- | ----------- | -------- | ------------------------------ |
| monitors | Monitor[] | Yes | An array of monitors to check. |
Returns: Promise<void>
watch(interval, monitors)
Continuously run monitors at a specified interval. Returns a stop function for graceful shutdown.
import { watch } from "monirail";
const stop = watch(1, [errorMonitor, cpuMonitor]);
// Graceful shutdown
process.on("SIGINT", () => {
stop();
process.exit(0);
});| Parameter | Type | Required | Description |
| ---------- | ----------- | -------- | --------------------------------------- |
| interval | number | Yes | The interval in minutes between checks. |
| monitors | Monitor[] | Yes | An array of monitors to watch. |
Returns: () => void — A function that stops the watch loop when called.
[!WARNING] Do not use
watch()in a cron job. Usecheck()instead for one-off executions.
Environment Variables
| Variable | Required | Default | Description |
| -------------------------- | -------- | ------------------------------------------ | ------------------------------------------ |
| RAILWAY_API_TOKEN | Yes | - | Your Railway API token |
| RAILWAY_PROJECT_ID | Yes | - | Railway project ID |
| RAILWAY_PROJECT_NAME | Yes | - | Railway project name |
| RAILWAY_ENVIRONMENT_ID | Yes | - | Railway environment ID |
| RAILWAY_ENVIRONMENT_NAME | Yes | - | Railway environment name |
| RAILWAY_SERVICE_ID | Yes | - | Railway service ID |
| RAILWAY_SERVICE_NAME | Yes | - | Railway service name |
| RAILWAY_API_URL | No | https://backboard.railway.app/graphql/v2 | Railway API URL |
| SQLITE_DB_FILE | No | /data/monirail.sqlite | Path to SQLite database for monitor state |
| LOG_FORMAT | No | pretty | Log format: pretty or json |
| NODE_ENV | No | development | Environment: development or production |
