Skip to content
Warlock.js v4.4.0

Sentry channel

SentryLog forwards log entries to Sentry. It’s the one built-in channel that needs an external SDK, so @sentry/node is an optional peer — install it only if you use this channel:

Terminal window
npm install @sentry/node

If your app already calls Sentry.init(...), pass the namespace as client. The channel forwards through it and never re-imports or re-initializes the SDK:

import * as Sentry from "@sentry/node";
import { log, SentryLog } from "@warlock.js/logger";
Sentry.init({ dsn: process.env.SENTRY_DSN, environment: "production" });
log.addChannel(new SentryLog({ client: Sentry }));

Pass options instead, and the channel lazily imports @sentry/node and calls Sentry.init once — guarded so it never clobbers an existing client:

import { log, SentryLog } from "@warlock.js/logger";
log.addChannel(
new SentryLog({
options: {
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
release: process.env.GIT_SHA,
},
}),
);

With neither client nor options, the channel reuses whatever global Sentry client the host already initialized.

This is the quota-control decision. Only the levels in eventLevels create Sentry events (which consume your error quota); every other level becomes a breadcrumb that rides along with the next event for free.

Logger levelDefaultSentry call
fataleventcaptureException for an Error message, otherwise captureMessage(…, "fatal")
erroreventcaptureException for an Error message, otherwise captureMessage(…, "error")
warneventcaptureMessage(…, "warning")
successbreadcrumbaddBreadcrumb({ level: "info" })
infobreadcrumbaddBreadcrumb({ level: "info" })
debugbreadcrumbaddBreadcrumb({ level: "debug" })
  • Errors keep their stack. An Error message goes through captureException, so Sentry parses the real stack and groups correctly — the channel never pre-stringifies it.
  • module / action become tags and the entry’s context becomes a structured Sentry context, both scoped to that single event via withScope.
  • success has no Sentry severity — it is reported as info.
// Only errors create events; warnings drop to breadcrumbs.
new SentryLog({ client: Sentry, eventLevels: ["error"] });

levels and filter from BasicLogConfigurations apply first — a channel-level levels: ["error", "warn"] drops everything else before it reaches Sentry.

Sentry sends events asynchronously, so a synchronous flush can’t wait on them. SentryLog.flush() calls Sentry.flush(timeout) — drain it on your graceful-shutdown path with await log.flush():

async function shutdown() {
await httpServer.close();
await log.flush(); // SentryLog.flush() → Sentry.flush(flushTimeout)
process.exit(0);
}
process.once("SIGTERM", shutdown);

flushTimeout (default 2000 ms) bounds the wait so an unreachable Sentry can’t hang shutdown.

The channel never crashes your app. The dynamic import failure is swallowed, the install instructions are written to stderr once, and entries are dropped silently thereafter — so registering SentryLog in shared config is safe even where Sentry isn’t installed.

OptionTypeDefaultDescription
clientSentry namespace / forwarderReuse an already-initialized Sentry instance.
optionsSentryInitOptionsSentry init options (mirrors @sentry/node’s NodeOptions).
eventLevelsLogLevel[]["fatal", "error", "warn"]Levels sent as events; the rest become breadcrumbs.
flushTimeoutnumber2000Milliseconds flush() waits for the transport to drain.

Plus everything from BasicLogConfigurations (levels, filter, dateFormat, redact).