Skip to content
vllntPublic

About

Structured JSON logging for the @vllnt ecosystem. Zero deps, tree-shakeable, multi-runtime (Node, Convex, browser, edge).

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

@vllnt/logger

Structured JSON logging for the @vllnt ecosystem.

Zero runtime dependencies. Tree-shakeable. Works in Node.js, Convex, browser, and edge runtimes.

Install

pnpm add @vllnt/logger

Quick Start

import { createBackendLogger } from "@vllnt/logger";

const logger = createBackendLogger("auth");

logger.info("login", { userId: "123" });
// → {"event":"auth.login","level":"info","timestamp":"...","userId":"123"}

logger.warn("rate-limited", { ip: "1.2.3.4" });

Set LOG_LEVEL env var to control verbosity (debug, info, warn, error). Defaults to warn.

Subpath Exports

Import Use Case
@vllnt/logger Core API + Node.js backend preset
@vllnt/logger/convex Convex-safe logger (no process.env)
@vllnt/logger/posthog PostHog output adapter
@vllnt/logger/testing In-memory test output for assertions

API

createBackendLogger(scope): ExtendedLogger

Pre-configured for Node.js. Reads LOG_LEVEL from process.env lazily on each log call.

const logger = createBackendLogger("payments");
logger.info("charge", { amount: 99, currency: "usd" });

createLogger(scope, config): Logger

Core factory. Bring your own level strategy and output.

import { createLogger, consoleOutput } from "@vllnt/logger";

const logger = createLogger("api", {
  getLogLevel: () => "debug",
  output: consoleOutput,
});

createExtendedLogger(scope, config): ExtendedLogger

Same as createLogger but adds withTiming and withTimingSync helpers.

const result = await logger.withTiming("db.query", async () => {
  return db.query("SELECT ...");
});
// Logs: scope.db.query.start → scope.db.query.complete (with durationMs)
// On error: scope.db.query.error (with durationMs + error message)

composeOutputs(...outputs): LogOutput

Fan-out to multiple outputs. Each output is error-isolated.

import { composeOutputs, consoleOutput } from "@vllnt/logger";
import { createPostHogOutput } from "@vllnt/logger/posthog";

const output = composeOutputs(
  consoleOutput,
  createPostHogOutput(posthog.capture.bind(posthog)),
);

createConvexLogger(scope, level?): ExtendedLogger

Safe for Convex queries, mutations, and actions. No process.env access.

import { createConvexLogger } from "@vllnt/logger/convex";

const logger = createConvexLogger("myFunction", "debug");
logger.info("started");

createPostHogOutput(capture): LogOutput

Wraps a PostHog-compatible capture function as a LogOutput. Swallows thrown errors and rejected promises.

import { createPostHogOutput } from "@vllnt/logger/posthog";

const output = createPostHogOutput(posthog.capture.bind(posthog));

createTestOutput(): TestOutput

Captures log entries in-memory for test assertions.

import { createTestOutput } from "@vllnt/logger/testing";
import { createLogger } from "@vllnt/logger";

const { entries, output } = createTestOutput();
const logger = createLogger("test", {
  getLogLevel: () => "debug",
  output,
});

logger.info("hello", { a: 1 });
expect(entries[0].event).toBe("test.hello");

Utilities

Function Description
formatLogEntry(level, event, data?) Build a LogEntry manually
parseLogLevel(value, default?) Safe string to LogLevel parsing
consoleOutput(entry) JSON to console.debug/info/warn/error (crash-safe)

Types

type LogLevel = "debug" | "info" | "warn" | "error";
type LogData = Record<string, unknown>;
type LogEntry = { event: string; level: LogLevel; timestamp: string } & Record<
  string,
  unknown
>;
type LogOutput = (entry: LogEntry) => void;
type Logger = {
  debug;
  info;
  warn;
  error: (event: string, data?: LogData) => void;
};
type ExtendedLogger = Logger & { withTiming; withTimingSync };
type LoggerConfig = { getLogLevel: () => LogLevel; output: LogOutput };

Design Decisions

  • Scoped events: All events are prefixed with the logger scope (auth.login, payments.charge)
  • Reserved field protection: User data cannot overwrite event, level, or timestamp
  • Log calls never throw: a failing getLogLevel, output, or data getter is swallowed; an invalid level falls back to info
  • Crash-safe serialization: consoleOutput writes BigInt as a string, Error as { name, message, stack }, and circular refs as "[Circular]"; a throwing toJSON degrades to a minimal event/level line
  • No built-in redaction: pass only data you are willing to log. withTiming logs error.message verbatim, so sanitize sensitive errors before throwing them
  • Synchronous outputs: LogOutput is synchronous by design — async adapters manage their own promises
  • Lazy level evaluation: getLogLevel() is called per-emit, so env changes are picked up without restart
  • Zero side effects: Core module has zero module-level side effects (sideEffects: false)

Releases

Every publish runs in .github/workflows/publish.yml with npm trusted publishing and provenance; nothing is published from a local machine. Maintainers only.

  • Canary (@canary): every push to main that touches the package publishes one (pnpm add @vllnt/logger@canary).
  • Stable (@latest): merge a PR that sets package.json version and adds a matching ## [X.Y.Z] entry to CHANGELOG.md, then push the tag vX.Y.Z on that main commit.

License

MIT

About

Structured JSON logging for the @vllnt ecosystem. Zero deps, tree-shakeable, multi-runtime (Node, Convex, browser, edge).

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages