Skip to main content
Libretto ships a structured logger that routes log entries to one or more pluggable sinks. Every public API that accepts an optional logger parameter accepts a MinimalLogger, so you can pass in either the full Logger class or any compatible object.

Logger

The main logger class. Construct it with an array of sinks.
new Logger(
  scopes?: string[],
  sinks?: LoggerSink[],
  scopeData?: Record<string, any>
)
In practice you usually construct a Logger by passing only sinks:
import { Logger, prettyConsoleSink, createFileLogSink } from "libretto";

const logger = new Logger(
  [],
  [
    prettyConsoleSink,
    createFileLogSink({
      filePath: ".libretto/sessions/my-session/logs.jsonl",
    }),
  ]
);
Logger implements LoggerApi and also satisfies MinimalLogger.

Logger.withSink(sink)

Returns a new Logger that writes to all existing sinks plus the additional sink. The original logger is not modified.
const fileLogger = logger.withSink(
  createFileLogSink({ filePath: ".libretto/sessions/my-session/logs.jsonl" })
);

defaultLogger

A pre-built MinimalLogger that writes to console.log / console.warn / console.error with [INFO], [WARN], and [ERROR] prefixes. Used as the fallback when you omit the logger option on any Libretto function.
import { defaultLogger } from "libretto";

LoggerApi interface

The full interface implemented by Logger.
log
(event: string, data?: Record<string, any>, options?: LogOptions) => void
Logs an entry at "log" level.
info
(event: string, data?, options?: LogOptions) => void
Logs an entry at "info" level. data can be an Error, { error: Error, ...rest }, or any plain object.
warn
(event: string, data?, options?: LogOptions) => void
Logs an entry at "warn" level.
error
(event: string, data?, options?: LogOptions) => Error
Logs an entry at "error" level and returns an Error object. If data is or contains an Error, that error is returned; otherwise a new Error is constructed from event.
withScope
(scope: string, context?: Record<string, any>) => LoggerApi
Returns a new LoggerApi with an additional scope segment prepended to all log entries. Scope segments are joined with . (e.g. "workflow.network").
withContext
(context: Record<string, any>) => LoggerApi
Returns a new LoggerApi with additional key-value pairs merged into every log entry’s data.
flush
() => Promise<void>
Flushes all sinks in reverse order (most recently added first).

MinimalLogger type

A minimal subset of LoggerApi. Any object with info, warn, and error methods satisfies this type, which is what every Libretto runtime function accepts for its optional logger parameter.
type MinimalLogger = {
  info: (event: string, data?: any) => void;
  warn: (event: string, data?: any) => void;
  error: (event: string, data?: any) => any;
};

LogOptions

timestamp
Date
Override the timestamp written to the log entry. Defaults to new Date() at write time.

LoggerSink type

A sink is any object with a write method. Implement this interface to route log output to any destination.
type LoggerSink = {
  write: (args: {
    id: string;
    scope: string;
    level: "log" | "error" | "warn" | "info";
    event: string;
    data: Record<string, any>;
    options?: LogOptions;
  }) => void;
  flush?: () => Promise<void>;
  close?: () => Promise<void>;
};
write
(args) => void
Called for every log entry. id is a unique random identifier per entry.
flush
() => Promise<void>
Optional. Called by Logger.flush() to ensure buffered entries are written.
close
() => Promise<void>
Optional. Called by Logger.close() to release resources (e.g. close file handles). Safe to call multiple times — subsequent calls are no-ops.

Built-in sinks

createFileLogSink({ filePath })

Writes JSONL (one JSON object per line) to a file. The directory is created automatically if it does not exist. Appends to the file if it already exists.
function createFileLogSink({ filePath }: { filePath: string }): LoggerSink
filePath
string
required
Absolute or relative path to the log file.

prettyConsoleSink

A pre-built LoggerSink constant that writes human-readable, ANSI-colored output to the console. Timestamps, log levels, scopes, and events are each color-coded. Error stacks are printed on separate lines.
import { prettyConsoleSink } from "libretto";

jsonlConsoleSink

A pre-built LoggerSink constant that writes each log entry as a single JSON line to console.log. Useful for log aggregation systems that consume structured output from stdout.
import { jsonlConsoleSink } from "libretto";

Full example

import { Logger, createFileLogSink, prettyConsoleSink } from "libretto";

const logger = new Logger(
  [],
  [
    prettyConsoleSink,
    createFileLogSink({
      filePath: ".libretto/sessions/my-session/logs.jsonl",
    }),
  ]
);

logger.info("Starting workflow", { session: "my-session" });
logger.warn("Element not found", { selector: "#submit" });

const scopedLogger = logger.withScope("network", { requestId: "abc123" });
scopedLogger.info("Request started", { url: "/api/items" });
// Logged with scope "network" and data merged with { requestId: "abc123" }

await logger.flush();

createLLMClientFromModel()

Creates a Libretto LLMClient from a Vercel AI SDK LanguageModel. While logically an LLM utility, it is included here as a companion to the logger since both are commonly configured together at the top of a workflow file.
function createLLMClientFromModel(model: LanguageModel): LLMClient
model
LanguageModel
required
A Vercel AI SDK language model instance. The result of calling a provider factory such as openai("gpt-4o") from @ai-sdk/openai, anthropic("claude-3-5-sonnet-20241022") from @ai-sdk/anthropic, or google("gemini-1.5-pro") from @ai-sdk/google.
The returned LLMClient is used by extractFromPage, attemptWithRecovery, executeRecoveryAgent, and detectSubmissionError.

LLMClient interface

interface LLMClient {
  generateObject<T extends z.ZodType>(opts: {
    prompt: string;
    schema: T;
    temperature?: number;
  }): Promise<z.output<T>>;

  generateObjectFromMessages<T extends z.ZodType>(opts: {
    messages: Message[];
    schema: T;
    temperature?: number;
  }): Promise<z.output<T>>;
}

Example

import {
  Logger,
  prettyConsoleSink,
  createFileLogSink,
  createLLMClientFromModel,
} from "libretto";
import { openai } from "@ai-sdk/openai";

const logger = new Logger(
  [],
  [
    prettyConsoleSink,
    createFileLogSink({ filePath: ".libretto/sessions/my-run/logs.jsonl" }),
  ]
);

const llmClient = createLLMClientFromModel(openai("gpt-4o"));

Build docs developers (and LLMs) love