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.
Constructors
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
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) // => trueconsoleLogFmt
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) // => trueconsolePretty
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) // => trueconsoleStructured
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) // => truedefaultLogger
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) // => trueformatJson
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
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
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 loggerconst 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\""]formatStructured
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" }]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
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
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) // => trueLogger.isLogger("not a logger") // => falseLogger.isLogger({ log: () => {} }) // => falseLayers
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
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.Fileconst 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.Fileconst 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"]withConsoleError
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 formatterconst 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"]withConsoleLog
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 formatterconst 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"]withLeveledConsole
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 // => expectedMapping
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 messagesconst 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
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
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
CurrentLoggers
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) // => truemessages // => [["Hello from custom logger"]]LogToStderr
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>