Skip to content

Console

The Console module is the effectful mirror of the global console. Every method you know from consolelog, error, table, group, time, and friends — has an Effect-returning counterpart that you yield* inside Effect.gen (or pipe through Effect.tap). The output goes wherever the active Console reference points, which by default is globalThis.console.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
yield* Console.log("Hello, world!")
yield* Console.info("server starting", { port: 3000 })
yield* Console.warn("inventory is low")
yield* Console.error("checkout failed", { code: 500 })
})

Because the console is held as a Context.Reference, you can substitute your own implementation with Effect.provideService. This makes console output capturable and assertable in tests, with no global patching.

import { Console, Effect } from "effect"
// A minimal capturing console. Only implement the methods you exercise;
// cast through the Console interface for the rest.
const makeCapture = () => {
const lines: Array<string> = []
const capture = {
...globalThis.console,
log: (...args: Array<any>) => lines.push(args.join(" ")),
error: (...args: Array<any>) => lines.push(`ERROR ${args.join(" ")}`)
} as Console.Console
return { capture, lines }
}
const program = Effect.gen(function*() {
yield* Console.log("hello")
yield* Console.error("boom")
})
const { capture, lines } = makeCapture()
Effect.runSync(program.pipe(Effect.provideService(Console.Console, capture)))
console.log(lines)
// => ["hello", "ERROR boom"]

The shape of a console implementation: the methods assert, clear, count, countReset, debug, dir, dirxml, error, group, groupCollapsed, groupEnd, info, log, table, time, timeEnd, timeLog, trace, and warn. The browser/Node console already satisfies it, which is why it is the default.

import { Console } from "effect"
// Provide your own implementation by satisfying this interface.
const silent: Console.Console = {
...globalThis.console,
log: () => {},
error: () => {}
}

Console.Console is the Context.Reference holding the active console. It resolves to globalThis.console unless overridden. Override it with Effect.provideService(Console.Console, impl) to redirect output.

import { Console, Effect } from "effect"
const program = Console.log("written to the overridden console")
const myConsole = { ...globalThis.console } as Console.Console
Effect.runSync(program.pipe(Effect.provideService(Console.Console, myConsole)))

Accesses the current console and lets you build an Effect from it directly — useful when you want to call several methods on the raw console in one synchronous block.

import { Console, Effect } from "effect"
const program = Console.consoleWith((console) =>
Effect.sync(() => {
console.log("Hello, world!")
console.error("This is an error message")
})
)

Logs a general-purpose message. Accepts any number of arguments.

import { Console, Effect } from "effect"
const program = Console.log("User data:", { name: "John", age: 30 })
// => User data: { name: 'John', age: 30 }

Writes an error-level message, typically styled as an error by the host console.

import { Console } from "effect"
const program = Console.error("Error details:", { code: 500 })
// => Error details: { code: 500 }

Writes a warning-level message.

import { Console } from "effect"
const program = Console.warn("This feature is deprecated")
// => This feature is deprecated

Writes an informational message.

import { Console } from "effect"
const program = Console.info("Server configuration:", { port: 3000 })
// => Server configuration: { port: 3000 }

Writes a debug-level message. Visibility depends on the host console’s level filtering.

import { Console } from "effect"
const program = Console.debug("Debug info:", { userId: 123, action: "login" })
// => Debug info: { userId: 123, action: 'login' }

Writes the current stack trace alongside the supplied arguments, showing how the current point in the code was reached.

import { Console } from "effect"
const program = Console.trace("Debug trace point")
// => Trace: Debug trace point
// => at ...

Displays an interactive list of an object’s properties. The optional second argument forwards inspection options (e.g. { depth, colors }) to the host console.

import { Console } from "effect"
const obj = { name: "John", nested: { city: "New York" } }
const program = Console.dir(obj, { depth: 2 })
// => { name: 'John', nested: { city: 'New York' } }

Displays an interactive tree of descendant XML/HTML elements, primarily useful for inspecting DOM nodes in the browser. Falls back to dir-like output elsewhere.

import { Console } from "effect"
const program = Console.dirxml("<user id=\"1\">Ada</user>")
// => <user id="1">Ada</user>

Renders an array or object as a formatted table. The optional second argument restricts the table to the listed property names.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
const users = [
{ name: "John", age: 30, city: "New York" },
{ name: "Jane", age: 25, city: "London" }
]
yield* Console.table(users)
yield* Console.table(users, ["name", "age"]) // only these columns
})
// => ┌─────────┬────────┬─────┬────────────┐
// => │ (index) │ name │ age │ city │
// => ├─────────┼────────┼─────┼────────────┤
// => │ 0 │ 'John' │ 30 │ 'New York' │
// => │ 1 │ 'Jane' │ 25 │ 'London' │
// => └─────────┴────────┴─────┴────────────┘

Writes an assertion error to the console when condition is false; produces no output when it is true.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
yield* Console.assert(2 + 2 === 4, "Math is working") // no output
yield* Console.assert(2 + 2 === 5, "This is logged as an error")
})
// => Assertion failed: This is logged as an error

Logs and increments a named counter. With no label it uses the console’s default counter.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
yield* Console.count("hits")
yield* Console.count("hits")
})
// => hits: 1
// => hits: 2

Resets the named counter back to zero.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
yield* Console.count("hits") // => hits: 1
yield* Console.count("hits") // => hits: 2
yield* Console.countReset("hits")
yield* Console.count("hits") // => hits: 1
})

Console.time and Console.group are scoped: they require a Scope and end the timer / close the group automatically when the scope is finalized. Run them under Effect.scoped. For the common “time/group exactly this effect” case, prefer the withTime / withGroup combinators below, which manage the scope for you.

Starts a console timer for label, ending it automatically when the surrounding scope closes. Returns Effect<void, never, Scope>.

import { Console, Effect } from "effect"
const program = Effect.scoped(
Effect.gen(function*() {
yield* Console.time("operation")
yield* Effect.sleep("1 second")
yield* Console.log("done")
// timer ends here, when the scope closes
})
)
// => done
// => operation: 1004.218ms

Logs the elapsed time of an existing timer without stopping it — handy for progress reports inside a time scope.

import { Console, Effect } from "effect"
const program = Effect.scoped(
Effect.gen(function*() {
yield* Console.time("job")
yield* Effect.sleep("500 millis")
yield* Console.timeLog("job", "halfway")
yield* Effect.sleep("500 millis")
})
)
// => job: 503.91ms halfway
// => job: 1006.77ms (emitted when the scope closes)

Runs an Effect with a console timer wrapped around it: starts the timer before the effect and ends it after. Dual — data-first (self, label?) or data-last (label?) for piping. No explicit Scope needed.

import { Console, Effect } from "effect"
const work = Effect.sleep("1 second")
// data-first
const a = Console.withTime(work, "my-operation")
// data-last (pipeable)
const b = work.pipe(Console.withTime("my-operation"))
// => my-operation: 1003.12ms

Opens a console group (indenting subsequent output), closing it automatically when the scope is finalized. Pass { collapsed: true } for a collapsed group in the browser. Returns Effect<void, never, Scope>.

import { Console, Effect } from "effect"
const program = Effect.scoped(
Effect.gen(function*() {
yield* Console.group({ label: "User Processing" })
yield* Console.log("Loading user data...")
yield* Console.log("Validating user...")
// group closes here, when the scope closes
})
)
// => User Processing
// => Loading user data...
// => Validating user...

Runs an Effect inside a console group, opening it before and closing it after the effect completes. Dual — data-first (self, options?) or data-last (options?). No explicit Scope needed.

import { Console, Effect } from "effect"
const steps = Effect.gen(function*() {
yield* Console.log("Step 1: Initialize")
yield* Console.log("Step 2: Process")
})
const program = Console.withGroup(steps, {
label: "Processing Steps",
collapsed: false
})
// => Processing Steps
// => Step 1: Initialize
// => Step 2: Process

Requests that the active console clear its visible output. The exact behavior depends on the host environment.

import { Console, Effect } from "effect"
const program = Effect.gen(function*() {
yield* Console.log("This will be cleared")
yield* Console.clear
yield* Console.log("This appears after clearing")
})