GitHub - carlos-menezes/caravan: A flexible, transport-based TypeScript-first logging system for JavaScript applications.
/ˈkærəvæn/

A small, typed logging library for JavaScript applications. Caravan gives you a core logger with levels, context and pluggable transports, plus a set of transport packages for where the logs actually go.
Packages
[](https://github.com/carlos-menezes/caravan#packages)
- `@caravan-logger/logger`: the core logger. Handles levels, context inheritance and dispatching records to transports.
- `@caravan-logger/transport-stream`: a transport that writes log records to a Node.js or web `WritableStream`, optionally formatting records before they're written.
- `@caravan-logger/transport-datadog`: a transport that sends log records to Datadog's Logs API.
- `@caravan-logger/transport-better-stack`: a transport that sends log records to Better Stack's Logs ingesting API.
- `@caravan-logger/transport-sqlite`: a transport that inserts log records as rows into a SQLite database.
Usage
[](https://github.com/carlos-menezes/caravan#usage)
Basic logging
[](https://github.com/carlos-menezes/caravan#basic-logging) Every logger needs an `id` and at least one transport to actually send records somewhere. Log methods exist for each level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) and accept a message, a context object or both.
import { createLogger } from "@caravan-logger/logger"; import { createStreamTransport } from "@caravan-logger/transport-stream";
const logger = createLogger("app", { level: "INFO", transports: [createStreamTransport({ stream: process.stdout })], });
logger.info("server started", { port: 3000 }); logger.warn({ port: 3000, retrying: true }); logger.error("failed to bind port", { port: 3000 });
await logger.flush();
Logging to a file
[](https://github.com/carlos-menezes/caravan#logging-to-a-file) `createStreamTransport` accepts any Node.js `Writable`. Each record is written as its own line (`eol` controls the line ending and defaults to `"lf"`).
import { createWriteStream } from "node:fs"; import { createLogger } from "@caravan-logger/logger"; import { createStreamTransport } from "@caravan-logger/transport-stream";
const logger = createLogger("app", { transports: [createStreamTransport({ stream: createWriteStream("app.log", { flags: "a" }) })], });
logger.info("server started", { port: 3000 }); await logger.flush();
Context and child loggers
[](https://github.com/carlos-menezes/caravan#context-and-child-loggers) Context passed to `createLogger` is merged into every record the logger writes. Child loggers created with `inherit` pick up the parent's level, transports and context, and can extend the context further without mutating the parent.
const requestLogger = createLogger("request", { inherit: { from: logger }, context: { requestId: "abc-123" }, });
requestLogger.warn("slow response", { durationMs: 820 });
// per-call context is merged with the logger's own context requestLogger.info("handled", { statusCode: 200 });
Multiple and per-transport levels
[](https://github.com/carlos-menezes/caravan#multiple-and-per-transport-levels) A logger can fan a record out to several transports at once. Each transport can define its own `level`, so it only receives records at or above that level, regardless of the logger's own level.
import { createStreamTransport } from "@caravan-logger/transport-stream";
const logger = createLogger("app", { level: "TRACE", transports: [ // everything goes to stdout createStreamTransport({ stream: process.stdout }), // only warnings and above go to the error stream createStreamTransport({ stream: process.stderr, level: "WARN" }), ], });
Custom formatting and transports
[](https://github.com/carlos-menezes/caravan#custom-formatting-and-transports)
`createStreamTransport` writes to any Node.js or web `WritableStream` and accepts a `format` function to control how records are serialized. You can also build your own transport with `createTransport` for destinations other than a stream (e.g. an HTTP endpoint or a database).
import { createTransport } from "@caravan-logger/logger"; import { createStreamTransport } from "@caravan-logger/transport-stream";
const prettyStream = createStreamTransport({ stream: process.stdout, format: (record) => `[${record.level}] ${record.message} ${JSON.stringify(record.context)}`, });
const remoteTransport = createTransport({ write: async (record) => { await fetch("https://logs.example.com/ingest", { method: "POST", body: JSON.stringify(record), }); }, });
const logger = createLogger("app", { transports: [prettyStream, remoteTransport], });
Middleware
[](https://github.com/carlos-menezes/caravan#middleware)
Middleware runs on every record before it reaches transports, in the order provided, and can transform or drop records (by returning `undefined`). Child loggers inherit their parent's middleware. This is useful for redacting sensitive fields, sampling, or enriching records.
const logger = createLogger("app", { transports: [createStreamTransport({ stream: process.stdout })], context: { password: "4f53a5c4-bfa5-4035-aba7-11e29a90c260" }, middleware: [ (record) => { if (!record.context?.["password"]) return record; return { ...record, context: { ...record.context, password: "*".repeat(5) }, }; }, ], });
logger.info("user authenticated");
Flushing
[](https://github.com/carlos-menezes/caravan#flushing) Transports may buffer or perform async work (e.g. network writes). Call `flush()` to wait for all pending writes on a logger's transports to settle before your process exits.
await logger.flush();
Benchmarks
[](https://github.com/carlos-menezes/caravan#benchmarks)
Caravan's core is benchmarked against Pino in `packages/benchmark`, covering basic messages, messages with context and child loggers. See its README for methodology and results.
pnpm bench
Development
[](https://github.com/carlos-menezes/caravan#development) This is a pnpm monorepo managed with Turbo.
pnpm install
build
pnpm build:all # everything pnpm build:caravan # only @caravan-logger/* packages pnpm build:packages # only @packages/* packages
test, lint, format
pnpm test pnpm lint pnpm fmt