Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

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.

25 exports Since v2.0.0 Source

Constructors

Since v2.0.0

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"]]

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. Chooses between tty and browser implementation. If the runtime platform is known and fixed, prefer consolePrettyBrowser or consolePrettyTty.

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.

See

Signature

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

Example Logging with pretty console output

import { Effect, Logger } from "effect"
const prettyLogger = Logger.layer([Logger.consolePretty()])
Effect.log("hello").pipe(
Effect.withLogSpan('label'),
Effect.annotateLogs('key', 'value'),
Effect.provide(prettyLogger),
Effect.runSync
)

Example Logging with console.error, when the environment has TTY

import { Effect, Layer, Logger } from "effect"
const prettyLoggerLayer = Layer.merge(
Logger.layer([Logger.consolePretty()]),
Layer.succeed(Logger.LogToStderr, true)
)
Effect.log('hello').pipe(
Effect.provide(prettyLoggerLayer),
Effect.runSync
)

A Logger which outputs logs in a "pretty" format and writes them to the console. Intended to be used on platforms with a browser 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.

See

Signature

declare const consolePrettyBrowser: (options?: {
readonly colors?: boolean;
readonly formatDate?: (date: Date) => string;
}) => Logger<unknown, void>

Example Logging with pretty console output

import { Effect, Logger } from "effect"
const prettyLogger = Logger.layer([Logger.consolePrettyBrowser()])
Effect.log("hello").pipe(
Effect.withLogSpan('label'),
Effect.annotateLogs('key', 'value'),
Effect.provide(prettyLogger),
Effect.runSync
)

A Logger which outputs logs in a "pretty" format and writes them to the console. Intended to be used on platforms with tty 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.

See

Signature

declare const consolePrettyTty: (options?: {
readonly colors?: boolean;
readonly formatDate?: (date: Date) => string;
}) => Logger<unknown, void>

Example Logging with pretty console output

import { Effect, Logger } from "effect"
const prettyLogger = Logger.layer([Logger.consolePrettyTty()])
Effect.log("hello").pipe(
Effect.withLogSpan('label'),
Effect.annotateLogs('key', 'value'),
Effect.provide(prettyLogger),
Effect.runSync
)

Example Logging with console.error

import { Effect, Layer, Logger } from "effect"
const prettyLoggerLayer = Layer.merge(
Logger.layer([Logger.consolePrettyTty()]),
Layer.succeed(Logger.LogToStderr, true)
)
Effect.log('hello').pipe(
Effect.provide(prettyLoggerLayer),
Effect.runSync
)

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

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\"}"]

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\""]

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" }]
Since v2.0.0

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"]

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

Since v4.0.0

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

Since v4.0.0

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

unstable
Since v4.0.0

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 = {
writeAll: (buffer: Uint8Array) => Effect.sync(() => {
writes.push(new TextDecoder().decode(buffer).trim())
})
} 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 = {
writeAll: (buffer: Uint8Array) => Effect.sync(() => {
writes.push(new TextDecoder().decode(buffer).trim())
})
} 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

Since v2.0.0

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
Since v2.0.0

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
Since v2.0.0

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"]]

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>