@vibemonitor/node
v0.1.0
Published
Vibemonitor Node.js SDK — capture and send logs from server-side JS/TS apps (Express, Fastify, NestJS, Next.js server).
Downloads
78
Maintainers
Readme
@vibemonitor/node
Vibemonitor SDK for Node.js backends — Express, Fastify, NestJS, Koa, plain Node, or any server-side JS/TS runtime.
For Next.js (which has a Node runtime but also browser + edge), use
@vibemonitor/nextjs— it handles all three.
Installation
npm install @vibemonitor/nodePulls in @vibemonitor/core transitively.
Node requirement: 18+ (for native fetch).
Environment variable
# .env
VIBEMONITOR_API_KEY=vmsdk_server_token_here
# Optional
VIBEMONITOR_ENABLED=true # set "false" or "0" to disable without code changes
VIBEMONITOR_DEBUG=false # set "1" for SDK diagnostic stderr output
VIBEMONITOR_SERVICE=my-api # or pass via init({ service })
VIBEMONITOR_ENV=production
VIBEMONITOR_VERSION=1.2.3Get your token from the Vibemonitor dashboard → Settings → API Keys.
Setup — single init at server startup
Express
// server.ts
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({
apiKey: process.env.VIBEMONITOR_API_KEY,
service: "my-api",
environment: process.env.NODE_ENV,
});
// ─── Your app — unchanged ─────────────────────────
import express from "express";
const app = express();
app.get("/users", async (req, res) => {
console.log("Fetching users"); // ← captured
const users = await db.query();
res.json(users);
});
app.listen(3000);Fastify
// server.ts
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({ apiKey: process.env.VIBEMONITOR_API_KEY, service: "my-api" });
import Fastify from "fastify";
const fastify = Fastify({ logger: true });
fastify.get("/users", async () => {
console.log("Fetching users"); // ← captured
return await db.query();
});
fastify.listen({ port: 3000 });NestJS
// main.ts — first line, before any other imports
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({ apiKey: process.env.VIBEMONITOR_API_KEY, service: "my-api" });
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Koa
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({ apiKey: process.env.VIBEMONITOR_API_KEY, service: "my-api" });
import Koa from "koa";
const app = new Koa();
// ...
app.listen(3000);Plain Node HTTP server
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({ apiKey: process.env.VIBEMONITOR_API_KEY, service: "my-api" });
import http from "node:http";
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`); // ← captured
res.end("ok");
});
server.listen(3000);Next.js server runtime (SSR + API routes)
For Next.js, prefer @vibemonitor/nextjs which handles all three runtimes. If you want only the server piece, here's how:
// vibemonitor.server.config.ts
import vibemonitor from "@vibemonitor/node";
vibemonitor.init({
apiKey: process.env.VIBEMONITOR_API_KEY,
service: "my-app-server",
});// src/instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("../vibemonitor.server.config");
}
}What gets captured
Three layers run automatically after init():
- Explicit API —
vibemonitor.log("INFO", "message", { attrs })from anywhere - Console wrap —
console.log/info/warn/error/debugcalls (your code, most libraries) - Process handlers —
process.on('uncaughtException' / 'unhandledRejection')
Plus graceful shutdown flush on beforeExit, SIGTERM, SIGINT (Docker/K8s-friendly).
Manual structured logging
import { log } from "@vibemonitor/node";
log("INFO", "Payment charged", {
userId: user.id,
amount: 49.99,
gateway: "stripe",
});Configuration reference
vibemonitor.init({
// Required
apiKey: process.env.VIBEMONITOR_API_KEY,
// Identification
service: "my-api", // defaults to package.json name or script name
environment: process.env.NODE_ENV,
version: process.env.npm_package_version,
// Endpoint — default is production; override for local vm-api
endpoint: "http://localhost:8000/api/v1/ingest/logs",
// Master switches
enabled: true, // env: VIBEMONITOR_ENABLED
debug: false, // env: VIBEMONITOR_DEBUG
// Transport tuning
flushIntervalMs: 2000,
maxQueueSize: 1000,
batchSize: 50,
// Capture layers
captureConsole: true, // wrap console.*
captureGlobalErrors: true, // listen to process.on('uncaughtException' / 'unhandledRejection')
// PII scrubbing (default-on — redact before send)
scrubPatterns: ["email", "jwt", "aws_key", ...],
customScrubPatterns: { customerId: /CUST-\d+/g },
// Hook to mutate or drop entries
beforeSend: (entry) => entry,
});Known gap — pino / winston / bunyan
Structured loggers like pino, winston, and bunyan write directly to process.stdout or their own transports — they bypass console.* entirely. Our console wrapping doesn't catch them.
Workarounds (today)
Option A — mirror important logs via the explicit API:
import vibemonitor from "@vibemonitor/node";
import pino from "pino";
const logger = pino();
function logError(msg, data) {
logger.error(data, msg);
vibemonitor.log("ERROR", msg, data); // ← mirror to Vibemonitor
}Option B — attach a pino transport that does both:
const logger = pino({
transport: {
targets: [
{ target: "pino-pretty" }, // console output
{ target: "./vibemonitor-transport" }, // forward to Vibemonitor
],
},
});Coming soon
@vibemonitor/node/pino and @vibemonitor/node/winston adapters are on the roadmap. They'll let you do:
import pino from "pino";
import { pinoTransport } from "@vibemonitor/node/pino";
const logger = pino({ transport: pinoTransport });Best practices
Disable in tests / CI
vibemonitor.init({
apiKey: process.env.VIBEMONITOR_API_KEY,
enabled: process.env.NODE_ENV !== "test",
});Or via env:
VIBEMONITOR_ENABLED=false npm testWrap in your own logger
// src/lib/logger.ts
import vibemonitor from "@vibemonitor/node";
export function createLogger(module: string) {
return {
info: (msg: string, data?: Record<string, unknown>) =>
vibemonitor.log("INFO", msg, { module, ...data }),
error: (msg: string, data?: Record<string, unknown>) =>
vibemonitor.log("ERROR", msg, { module, ...data }),
};
}
// Usage
import { createLogger } from "./lib/logger";
const log = createLogger("payments");
log.info("Charge succeeded", { userId, amount });AWS Lambda / Cloud Functions
The SDK works in serverless as long as process.on('beforeExit') fires (it does by default). For very-short-lived invocations, consider calling await vibemonitor.shutdown() at the end of your handler to force a flush:
export async function handler(event) {
try {
const result = await doWork(event);
return result;
} finally {
await vibemonitor.shutdown(); // flush before Lambda freezes
}
}Kubernetes / Docker graceful shutdown
SIGTERM handling is automatic — when your pod receives SIGTERM, the SDK flushes pending logs before the process exits. No extra setup needed.
Compatibility
- Node.js 18+ — required (native
fetch) - Bun — mostly compatible (uses web-standard
fetch,zlibpolyfilled) - Deno — works with a compat shim for
process.on
Shutdown
await vibemonitor.shutdown();Called automatically on beforeExit, SIGTERM, SIGINT. Call explicitly for:
- Short-lived serverless functions (flush before freeze)
- Graceful teardown in tests
- Custom shutdown signals
FAQ
Q: Can I use this with TypeScript that isn't Node runtime (e.g., Deno)?
Mostly yes. Deno supports process via a compat layer. Test in your environment.
Q: Does this work in Bun?
Yes — Bun implements fetch, zlib, and process.on. Behavior is identical.
Q: My SIGTERM handler doesn't flush in time.
Increase Docker/K8s terminationGracePeriodSeconds to at least 5s (default 30s is plenty). The SDK has a 5-second flush timeout by default.
Q: How do I see what's actually being sent?
Set debug: true or VIBEMONITOR_DEBUG=1. The SDK will print [vibemonitor] lines to stderr showing init config, flush attempts, 429 backoff, and circuit-breaker events.
License
Proprietary. See LICENSE.
