APM for Node, Bun and Deno
@falak/apm-node is a thin preset over the official OpenTelemetry JS SDK. It exports traces, logs and metrics as OTLP/HTTP JSON to the local agent (http://127.0.0.1:4318) and normalizes spans to Falak’s event types (request, outgoing_request, query, cache) so they show up in Insights.
Install
Section titled “Install”npm i @falak/apm-node # or: bun add @falak/apm-node / pnpm add @falak/apm-nodeSet up per framework
Section titled “Set up per framework”Load it before your app so auto-instrumentation can patch modules:
{ "scripts": { "start": "node --import @falak/apm-node/register dist/server.js" } }Or put import '@falak/apm-node/register'; on the first line of your entry file (covers CommonJS and built-ins; use --import when you load pg/express through ESM).
Auto-instrumented: http/https (incoming and outgoing), undici/global fetch, pg, mysql2, ioredis, express, fastify.
export async function register() { const { registerFalak } = await import('@falak/apm-node/next'); await registerFalak();}Only the Node.js runtime is instrumented. Next’s own spans are mapped, with the route from next.route.
import falak from '@falak/apm-node/nitro';export default defineNitroPlugin(falak);Records errors from Nitro’s error hook as unhandled and sets http.route from the matched route.
Bun.serve and Deno.serve are not auto-instrumented. Wrap your handler:
import { start, withFalakRequest } from '@falak/apm-node';
start();
Bun.serve({ fetch: withFalakRequest(handler, { route: (req) => '/users/:id', // low-cardinality route template user: (req) => session(req)?.userId, // enduser.id captureHeaders: ['x-request-id'], }),});The same wrapper works for Hono (withFalakRequest(app.fetch)), Deno and Next route handlers. It continues an incoming traceparent, records thrown errors as unhandled, and marks 5xx responses as errors.
Exceptions
Section titled “Exceptions”import { recordException } from '@falak/apm-node';
try { await charge(); } catch (e) { recordException(e); } // handled=truerecordException(err, { handled: false }); // span → ERRORUncaught exceptions and unhandled rejections are captured as handled=false; the process still crashes as it would by default. Opt out with captureProcessErrors: false.
Configuration
Section titled “Configuration”Pass options to start({ … }) or use environment variables:
| Option | Env | Default |
|---|---|---|
enabled |
FALAK_APM_ENABLED |
true |
endpoint |
FALAK_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINT |
http://127.0.0.1:4318 |
serviceName |
FALAK_SERVICE_NAME, OTEL_SERVICE_NAME |
npm_package_name (the agent overrides it with the site slug) |
sampleRate |
FALAK_SAMPLE_RATE |
1 (root, parent-based) |
autoInstrument |
FALAK_APM_AUTO_INSTRUMENT |
true (Node only) |
logs |
FALAK_APM_LOGS |
true |
metrics |
FALAK_APM_METRICS |
true (Node only, 60 s interval) |
redactKeys |
password, token, secret, authorization, cookie, api_key |
|
redactQueryLiterals |
true |
|
redact(attributes, eventType) |
none | |
captureRequestHeaders |
['user-agent'] |
|
captureProcessErrors |
true |
Resource attributes come from FALAK_SITE_ID, FALAK_SERVER_ID, FALAK_DEPLOYMENT_ID, FALAK_RELEASE_ID, FALAK_ORG_ID and FALAK_ENVIRONMENT (falling back to NODE_ENV).
Redaction
Section titled “Redaction”Before export:
- attribute keys containing a denylisted word (including captured headers) become
[redacted]; - denylisted query parameters in URLs and passwords in URL userinfo are redacted;
- quoted SQL literals become
?; - cache keys containing a denylisted word are redacted;
- your
redactcallback runs last (a throwing callback is ignored).
Graceful shutdown
Section titled “Graceful shutdown”import { start, shutdown } from '@falak/apm-node';
start({ serviceName: 'shop', sampleRate: 0.5 });process.on('SIGTERM', () => shutdown().finally(() => process.exit(0)));