Skip to content
Effect Days 2026 Get your ticket

Logger

Defines loggers and log-event data for Effect programs.

A Logger<Message, Output> receives each log event as Options and turns it into output such as a formatted string, structured object, console write, file write, JSON line, or trace span event. This module also includes active logger references, console routing helpers, built-in formatters, batching, file logging, and layers for installing loggers.

23 exports Added in v2.0.0 Source

Constructors

batched

Added in v2.0.0 Source

Creates a scoped logger that batches the output of another logger.

Details

The returned effect starts a scoped background process that periodically passes buffered outputs to flush. When the scope closes, the background process is interrupted and any remaining buffered entries are flushed.

Signature

declare const batched: <Output>(options: {
readonly flush: (messages: Array<NoInfer<Output>>) => Effect<void>;
readonly window: Input;
}) => <Message>(self: Logger<Message, Output>) => Effect<Logger<Message, void>, never, Scope> & <Message, Output>(self: Logger<Message, Output>, options: {
readonly flush: (messages: Array<NoInfer<Output>>) => Effect<void>;
readonly window: Input;
}) => Effect<Logger<Message, void>, never, Scope>

Example

(Batching logger output)

import { Effect, Logger } from "effect"
const flushed: Array<ReadonlyArray<string>> = []
const messageLogger = Logger.make((options) => String(options.message))
const batchedLogger = Logger.batched(messageLogger, {
window: "1 hour",
flush: (messages) =>
Effect.sync(() => {
flushed.push(messages)
})
})
const program = Effect.scoped(Effect.gen(function*() {
const logger = yield* batchedLogger
yield* Effect.log("Event 1").pipe(Effect.provide(Logger.layer([logger])))
yield* Effect.log("Event 2").pipe(Effect.provide(Logger.layer([logger])))
}))
await Effect.runPromise(program)
flushed // => [["Event 1", "Event 2"]]

consoleJson

Added in v4.0.0 Source

A Logger which outputs logs using a structured format serialized as JSON on a single line and writes them to the console.

Details

For example, console JSON output can render as {"message":["hello"],"level":"INFO","timestamp":"2025-01-03T14:28:57.508Z", "annotations":{"key":"value"},"spans":{"label":0},"fiberId":"#1"}.

Signature

declare const consoleJson: Logger<unknown, void>

Example

(Logging JSON output to the console)

import { Logger } from "effect"
Logger.isLogger(Logger.consoleJson) // => true

A Logger which outputs logs using the logfmt style and writes them to the console.

Details

For example, a console logfmt entry is rendered as timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=info.

Signature

declare const consoleLogFmt: Logger<unknown, void>

Example

(Logging logfmt output to the console)

import { Logger } from "effect"
Logger.isLogger(Logger.consoleLogFmt) // => true

A Logger which outputs logs in a "pretty" format and writes them to the console.

Details

For example, pretty output can render as [09:37:17.579] INFO (#1) label=0ms: hello followed by an annotation line such as key: value.

Signature

declare const consolePretty: (options?: {
readonly colors?: "auto" | boolean;
readonly formatDate?: (date: Date) => string;
readonly mode?: "browser" | "tty" | "auto";
readonly stderr?: boolean;
}) => Logger<unknown, void>

Example

(Logging with pretty console output)

import { Logger } from "effect"
const prettyLogger = Logger.consolePretty({ colors: false })
Logger.isLogger(prettyLogger) // => true

A Logger which outputs logs using a structured format and writes them to the console.

Details

For example, console structured output can contain message: [ "info", "message" ], level: "INFO", timestamp: "2025-01-03T14:25:39.666Z", annotations: { key: "value" }, spans: { label: 0 }, and fiberId: "#1".

Signature

declare const consoleStructured: Logger<unknown, void>

Example

(Logging structured output to the console)

import { Logger } from "effect"
Logger.isLogger(Logger.consoleStructured) // => true

The default logging implementation used by the Effect runtime.

Signature

declare const defaultLogger: Logger<unknown, void>

Example

(Referencing the default logger)

import { Logger } from "effect"
Logger.isLogger(Logger.defaultLogger) // => true

formatJson

Added in v4.0.0 Source

A Logger which outputs logs using a structured format serialized as JSON on a single line.

Details

For example, a JSON entry can render as {"message":["hello"],"level":"INFO", "timestamp":"2025-01-03T14:28:57.508Z","annotations":{"key":"value"}, "spans":{"label":0},"fiberId":"#1"}.

Signature

declare const formatJson: Logger<unknown, string>

Example

(Formatting logs as JSON)

import { Effect, Formatter, Logger } from "effect"
import { TestConsole } from "effect/testing"
const stableJson = Logger.map(Logger.formatJson, (json) => {
const output = JSON.parse(json)
return Formatter.formatJson({ message: output.message, level: output.level })
})
const program = Effect.gen(function*() {
yield* Effect.log("Server started").pipe(
Effect.provide(Logger.layer([Logger.withConsoleLog(stableJson)]))
)
return yield* TestConsole.logLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => ["{\"message\":\"Server started\",\"level\":\"INFO\"}"]

formatLogFmt

Added in v4.0.0 Source

A Logger which outputs logs using the logfmt style.

Details

For example, a logfmt entry is rendered as timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=hello.

Signature

declare const formatLogFmt: Logger<unknown, string>

Example

(Formatting logs as logfmt)

import { Effect, Logger } from "effect"
import { TestConsole } from "effect/testing"
const stableLogFmt = Logger.map(Logger.formatLogFmt, (output) =>
output
.replace(/timestamp=\S+ /, "")
.replace(/fiber=#\d+ /, "")
)
const program = Effect.gen(function*() {
yield* Effect.log("User login").pipe(
Effect.provide(Logger.layer([Logger.withConsoleLog(stableLogFmt)]))
)
return yield* TestConsole.logLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => ["level=INFO message=\"User login\""]

formatSimple

Added in v4.0.0 Source

A Logger which outputs logs as a string.

Details

For example, a simple log entry is rendered as timestamp=2025-01-03T14:22:47.570Z level=INFO fiber=#1 message=hello.

Signature

declare const formatSimple: Logger<unknown, string>

Example

(Formatting logs as simple strings)

import { Effect, Logger } from "effect"
import { TestConsole } from "effect/testing"
// Use the simple format logger
const stableSimple = Logger.map(Logger.formatSimple, (output) =>
output
.replace(/timestamp=\S+ /, "")
.replace(/fiber=#\d+ /, "")
)
const program = Effect.gen(function*() {
yield* Effect.log("Application started").pipe(
Effect.provide(Logger.layer([Logger.withConsoleLog(stableSimple)]))
)
return yield* TestConsole.logLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => ["level=INFO message=\"Application started\""]

A Logger which outputs logs using a structured format.

Details

For example, a structured entry can contain message: [ "hello" ], level: "INFO", timestamp: "2025-01-03T14:25:39.666Z", annotations: { key: "value" }, spans: { label: 0 }, and fiberId: "#1".

Signature

declare const formatStructured: Logger<unknown, {
readonly annotations: Record<string, unknown>;
readonly cause: string | undefined;
readonly fiberId: string;
readonly level: string;
readonly message: unknown;
readonly spans: Record<string, number>;
readonly timestamp: string;
}>

Example

(Formatting logs as structured objects)

import { Effect, Logger } from "effect"
import { TestConsole } from "effect/testing"
const stableStructured = Logger.map(Logger.formatStructured, (output) => ({
message: output.message,
level: output.level
}))
const program = Effect.gen(function*() {
yield* Effect.log("User action").pipe(
Effect.provide(Logger.layer([Logger.withConsoleLog(stableStructured)]))
)
return yield* TestConsole.logLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => [{ message: "User action", level: "INFO" }]

make

Added in v2.0.0 Source

Creates a new Logger from a log function.

Details

The log function receives an options object containing the message, log level, cause, fiber information, and timestamp, and should return the desired output.

Signature

declare const make: <Message, Output>(log: (options: Options<Message>) => Output) => Logger<Message, Output>

Example

(Creating loggers from functions)

import { Effect, Logger } from "effect"
const outputs: Array<string> = []
const textLogger = Logger.make((options) =>
`${options.logLevel}: ${options.message}`
)
const collector = Logger.make((options) => outputs.push(textLogger.log(options)))
const program = Effect.log("Hello World").pipe(
Effect.provide(Logger.layer([collector]))
)
Effect.runSync(program)
outputs // => ["Info: Hello World"]

tracerLogger

Added in v2.0.0 Source

A Logger which includes log messages as tracer span events.

Details

This logger integrates logging with distributed tracing by recording all log messages as events on the current trace span, making them visible in tracing tools like OpenTelemetry, Jaeger, or Zipkin.

This logger is included in the default set of loggers for all Effect programs, so log messages automatically appear as span events unless you override the default loggers.

Signature

declare const tracerLogger: Logger<unknown, void>

Example

(Recording logs as trace span events)

import { Effect, Logger } from "effect"
const program = Effect.log("span event").pipe(
Effect.withSpan("operation"),
Effect.provide(Logger.layer([Logger.tracerLogger]))
)
Effect.runSync(program)

Guards

isLogger

Added in v4.0.0 Source

Returns true if the specified value is a Logger, otherwise returns false.

Signature

declare function isLogger(u: unknown): u is Logger<unknown, unknown>

Example

(Checking logger values)

import { Logger } from "effect"
const myLogger = Logger.make(() => undefined)
Logger.isLogger(myLogger) // => true
Logger.isLogger("not a logger") // => false
Logger.isLogger({ log: () => {} }) // => false

Layers

layer

Added in v4.0.0 Source

Creates a Layer which will overwrite the current set of loggers with the specified array of loggers.

Details

If the specified array of loggers should be merged with the current set of loggers (instead of overwriting them), set mergeWithExisting to true.

Signature

declare function layer<Loggers extends readonly Array<Logger<unknown, unknown> | Effect<Logger<unknown, unknown>, any, any>>>(loggers: Loggers, options?: {
readonly mergeWithExisting?: boolean;
}): Layer<never, Loggers extends readonly [] ? never : Error<Loggers[number]>, Exclude<Loggers extends readonly [] ? never : Services<Loggers[number]>, Scope>>

Example

(Providing logger layers)

import { Effect, Logger } from "effect"
const messages: Array<unknown> = []
const customLogger = Logger.make((options) => {
messages.push(options.message)
})
const CustomLoggerLayer = Logger.layer([customLogger])
const program = Effect.log("Application started").pipe(
Effect.provide(CustomLoggerLayer)
)
Effect.runSync(program)
messages // => [["Application started"]]

Logging

toFile

Added in v4.0.0 Source

Creates a scoped logger that writes string logger output to a file.

Details

The returned effect requires FileSystem and Scope. The file logger batches string output, writes each batch to the specified path, and flushes remaining entries when the scope closes.

Signature

declare const toFile: (path: string, options?: {
readonly batchWindow?: Input;
readonly flag?: OpenFlag;
readonly mode?: number;
}) => <Message>(self: Logger<Message, string>) => Effect<Logger<Message, void>, PlatformError, Scope | FileSystem> & <Message>(self: Logger<Message, string>, path: string, options?: {
readonly batchWindow?: Input;
readonly flag?: OpenFlag;
readonly mode?: number;
}) => Effect<Logger<Message, void>, PlatformError, Scope | FileSystem>

Example

(Writing JSON logs to a file)

import { Effect, FileSystem, Logger } from "effect"
const writes: Array<string> = []
const file = {
write: (buffer: Uint8Array) => Effect.sync(() => {
writes.push(new TextDecoder().decode(buffer).trim())
return FileSystem.Size(buffer.length)
})
} as unknown as FileSystem.File
const fileSystem = FileSystem.makeNoop({ open: () => Effect.succeed(file) })
const messageLogger = Logger.make((options) => String(options.message))
const program = Effect.scoped(Effect.gen(function*() {
const fileLogger = yield* Logger.toFile(messageLogger, "/tmp/log.txt")
yield* Effect.log("a").pipe(Effect.provide(Logger.layer([fileLogger])))
yield* Effect.log("b").pipe(Effect.provide(Logger.layer([fileLogger])))
yield* Effect.log("c").pipe(Effect.provide(Logger.layer([fileLogger])))
})).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem))
await Effect.runPromise(program)
writes // => ["a\nb\nc"]

Example

(Writing logs to files)

import { Effect, FileSystem, Logger } from "effect"
const writes: Array<string> = []
const file = {
write: (buffer: Uint8Array) => Effect.sync(() => {
writes.push(new TextDecoder().decode(buffer).trim())
return FileSystem.Size(buffer.length)
})
} as unknown as FileSystem.File
const fileSystem = FileSystem.makeNoop({ open: () => Effect.succeed(file) })
const messageLogger = Logger.make((options) => String(options.message))
const program = Effect.scoped(Effect.gen(function*() {
const fileLogger = yield* Logger.toFile(messageLogger, "/tmp/app.log", {
batchWindow: "1 hour"
})
yield* Effect.log("Application started").pipe(
Effect.provide(Logger.layer([fileLogger]))
)
})).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem))
await Effect.runPromise(program)
writes // => ["Application started"]

Returns a new Logger that writes all output of the specified Logger to the console using console.error.

When to use

Use when logger output should be routed to console.error, such as error logs that should appear on stderr instead of stdout.

Signature

declare function withConsoleError<Message, Output>(self: Logger<Message, Output>): Logger<Message, void>

Example

(Writing logger output with console.error)

import { Effect, Logger } from "effect"
import { TestConsole } from "effect/testing"
// Create an error-specific formatter
const errorFormatter = Logger.make((options) =>
`ERROR: ${options.message}`
)
const errorLogger = Logger.withConsoleError(errorFormatter)
const program = Effect.gen(function*() {
yield* Effect.logError("Database connection failed").pipe(Effect.provide(Logger.layer([errorLogger])))
return yield* TestConsole.errorLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => ["ERROR: Database connection failed"]

Returns a new Logger that writes all output of the specified Logger to the console using console.log.

When to use

Use when a logger's string or object output should be routed to console.log for development or debugging.

Signature

declare function withConsoleLog<Message, Output>(self: Logger<Message, Output>): Logger<Message, void>

Example

(Writing logger output with console.log)

import { Effect, Logger } from "effect"
import { TestConsole } from "effect/testing"
// Create a custom formatter
const customFormatter = Logger.make((options) =>
`${options.logLevel}: ${options.message}`
)
const consoleLogger = Logger.withConsoleLog(customFormatter)
const program = Effect.gen(function*() {
yield* Effect.log("Hello World").pipe(Effect.provide(Logger.layer([consoleLogger])))
return yield* TestConsole.logLines
}).pipe(Effect.provide(TestConsole.layer))
await Effect.runPromise(program) // => ["Info: Hello World"]

Returns a new Logger that writes all output of the specified Logger to the console.

Details

Will use the appropriate console method (i.e. console.log, console.error, etc.) based upon the current LogLevel.

Debug uses console.debug, Info uses console.info, Trace uses console.trace, Warn uses console.warn, Error and Fatal use console.error, and all other levels use console.log.

Signature

declare function withLeveledConsole<Message, Output>(self: Logger<Message, Output>): Logger<Message, void>

Example

(Writing logs with level-based console methods)

import { Console, Effect, Logger } from "effect"
const messages: Array<ReadonlyArray<unknown>> = []
const testConsole: Console.Console = Object.assign(Object.create(console), {
info: (message: unknown) => messages.push(["info", message]),
warn: (message: unknown) => messages.push(["warn", message]),
error: (message: unknown) => messages.push(["error", message])
})
const formatter = Logger.make((options) =>
`[${options.logLevel}] ${options.message}`
)
const leveledLogger = Logger.withLeveledConsole(formatter)
const program = Effect.gen(function*() {
yield* Effect.logInfo("Info message") // -> console.info
yield* Effect.logWarning("Warning") // -> console.warn
yield* Effect.logError("Error occurred") // -> console.error
}).pipe(Effect.provide(Logger.layer([leveledLogger])))
Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
const expected = [
["info", "[Info] Info message"],
["warn", "[Warn] Warning"],
["error", "[Error] Error occurred"]
]
messages // => expected

Mapping

map

Added in v2.0.0 Source

Transforms the output of a Logger using the provided function.

When to use

Use when an existing logger's output should be transformed without recreating the logging logic.

Signature

declare const map: <Output, Output2>(f: (output: Output) => Output2) => <Message>(self: Logger<Message, Output>) => Logger<Message, Output2> & <Message, Output, Output2>(self: Logger<Message, Output>, f: (output: Output) => Output2) => Logger<Message, Output2>

Example

(Transforming logger output)

import { Effect, Logger } from "effect"
const outputs: Array<unknown> = []
const structuredLogger = Logger.make((options) => ({
message: options.message
}))
// Transform to uppercase messages
const uppercaseLogger = Logger.map(
structuredLogger,
(output) => ({ ...output, message: String(output.message).toUpperCase() })
)
const collector = Logger.make((options) => outputs.push(uppercaseLogger.log(options)))
const program = Effect.log("hello").pipe(Effect.provide(Logger.layer([collector])))
Effect.runSync(program)
outputs // => [{ message: "HELLO" }]

Models

Logger interface

Added in v2.0.0 Source

A logger that transforms a runtime log event into an output value.

Details

The runtime calls log with the message, level, cause, fiber, and timestamp for each log event. Use Logger.layer to install one or more loggers for an effect.

Signature

interface Logger<in Message, out Output> extends Pipeable {
readonly "~effect/Logger": "~effect/Logger";
log(options: Options<Message>): Output;
}

Example

(Creating custom loggers)

import { Effect, Logger } from "effect"
const messages: Array<string> = []
const stringLogger = Logger.make<unknown, void>((options) => {
messages.push(`[${options.logLevel}] ${options.message}`)
})
const program = Effect.log("Hello World").pipe(
Effect.provide(Logger.layer([stringLogger]))
)
Effect.runSync(program)
messages // => ["[Info] Hello World"]

Options

Options interface

Added in v2.0.0 Source

Information supplied to a Logger for a single log event.

Details

Includes the logged message, log level, cause, current fiber, and timestamp.

Signature

interface Options<out Message> {
readonly cause: Cause<unknown>;
readonly date: Date;
readonly fiber: Fiber<unknown, unknown>;
readonly logLevel: LogLevel;
readonly message: Message;
}

Example

(Accessing logger options)

import { Effect, Logger } from "effect"
const outputs: Array<unknown> = []
const detailedLogger = Logger.make((options) => {
outputs.push({
message: options.message,
level: options.logLevel,
hasCause: options.cause.reasons.length > 0
})
})
const program = Effect.log("Processing request").pipe(
Effect.provide(Logger.layer([detailedLogger]))
)
Effect.runSync(program)
outputs // => [{ message: ["Processing request"], level: "Info", hasCause: false }]

Services

Context reference containing the active loggers for the current fiber.

Details

By default this set includes the default logger and the tracer logger. Providing Logger.layer replaces or merges with this set depending on its options.

Signature

declare const CurrentLoggers: Context.Reference<ReadonlySet<Logger<unknown, any>>>

Example

(Accessing current loggers)

import { Effect, Logger } from "effect"
const messages: Array<unknown> = []
const customLogger = Logger.make((options) => {
messages.push(options.message)
})
const program = Effect.gen(function*() {
const currentLoggers = yield* Effect.service(Logger.CurrentLoggers)
yield* Effect.log("Hello from custom logger").pipe(
Effect.provide(Logger.layer([customLogger]))
)
return currentLoggers.has(Logger.defaultLogger)
})
Effect.runSync(program) // => true
messages // => [["Hello from custom logger"]]

LogToStderr

Added in v4.0.0 Source

Context reference that routes the built-in default logger and TTY pretty console logger to stderr.

When to use

Use to route built-in logger output to stderr while keeping stdout reserved for protocol messages or data output.

Details

The reference defaults to false. Providing true makes the affected loggers call console.error instead of console.log.

See

  • defaultLogger for the runtime logger affected by this reference
  • consolePretty for the TTY-mode pretty console logger affected by this reference
  • withConsoleError for routing a specific formatter logger to console.error

Signature

declare const LogToStderr: Context.Reference<boolean>