Warning: Effeck is v0. The language will change in major, breaking ways. It is not suitable for production use.

PlatformLogger

File logging uses Effect's Logger module in Effeck. Combine a string formatter with Logger.toFile, then install the resulting logger with a layer whose scope owns the file and pending batches.

Writing formatted logs to a file

const FileLogger = Logger.layer([
  () => Logger.toFile(Logger.formatLogFmt, "./application.log", {
    batchWindow: "250 millis"
  })
])

function application() {
  log("Index build started")
  log("Index build completed")
}

formatLogFmt produces string log records, and toFile creates a scoped logger that appends those records to the chosen path. The array contains a program, so the file opens when Logger.layer builds, not at module initialization. The layer replaces the installed loggers with its supplied list.

In Effect 4.0.5, toFile batches by default with a one-second window; batchWindow changes that interval. Pending entries flush when the logger's scope closes. Opening the file can fail with PlatformError; this implementation ignores later flush write failures, so it does not provide a durable-write acknowledgment for each log call.

Supplying file-system dependencies

// main.effeck
import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
const LoggerLive = Layer.provide(
  Logger.layer([
    () => Logger.toFile(Logger.formatLogFmt, "./application.log")
  ]),
  NodeFileSystem.layer
)

function main() {
  provide LoggerLive {
    log("Report generated")
  }
  // LoggerLive's scope has closed and its final batch has flushed.
  const fs = Effect.service(FileSystem.FileSystem)
  return fs.readFileString("./application.log")
}

NodeRuntime.runMain(() => Effect.provide(() => main(), NodeFileSystem.layer))

Logger.toFile needs FileSystem and Scope. Logger.layer owns the logger's scope, and Layer.provide supplies its file-system dependency. Reading after the provided program completes avoids racing the batch window; a successful read still does not establish that every earlier write succeeded.

Combining file and console output

// main.effeck
import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
const LoggerLive = Layer.provide(
  Logger.layer([
    Logger.consolePretty(),
    () => Logger.toFile(Logger.formatLogFmt, "./application.log", {
      batchWindow: "500 millis"
    })
  ]),
  NodeFileSystem.layer
)

function main() {
  log("Worker ready")
}

NodeRuntime.runMain(() => Effect.provide(() => main(), LoggerLive))

Include both loggers in the installed list: the console gets pretty output, while the file gets logfmt output. For a layer that adds to whatever loggers are already installed, use Logger.layer(loggers, { mergeWithExisting: true }). Keep secrets wrapped in Redacted and avoid logging raw request or customer payloads.

Coming from Effect

Wrap Logger.toFile in a no-argument function when handing it to Logger.layer. Calling it earlier would acquire the scoped logger immediately instead of letting the layer build it.