A cross-platform logging framework for Fable. It mirrors the
.NET Microsoft.Extensions.Logging
pattern with ILogger, ILoggerFactory, and ILoggerProvider interfaces, letting you
write idiomatic logging code in F# that works across JavaScript, Python, and Erlang/BEAM.
| Package | NuGet | Description |
|---|---|---|
Fable.Logging |
Core interfaces, LoggerFactory, ConsoleLogger, JS console logger | |
Fable.Logging.Structlog |
Python structlog provider | |
Fable.Logging.Beam |
Erlang/OTP logger provider |
Create a logger factory, configure it with providers, and start logging:
open Fable.Logging
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(ConsoleLoggerProvider())
builder.SetMinimumLevel(LogLevel.Debug))
let logger = factory.CreateLogger("MyApp.Service")
logger.LogInformation("Application started")
logger.LogDebug("Processing request for {UserId}", 42)
logger.LogError("Something went wrong")The built-in JS logger maps log levels to the appropriate console.* methods
(console.debug, console.info, console.warn, console.error).
open Fable.Logging
open Fable.Logging.JS
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(LoggerProvider()))
let logger = factory.CreateLogger("MyApp")
logger.LogInformation("Hello from {Platform}!", "JavaScript")Uses structlog for structured logging with support for both console and JSON output.
open Fable.Logging
open Fable.Logging.Structlog
// Console output (human-readable)
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(ConsoleLoggerProvider()))
// JSON output (machine-readable)
let jsonFactory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(JsonLoggerProvider()))
let logger = factory.CreateLogger("MyApp")
logger.LogInformation("User {Name} logged in", "Alice")Bridges to the OTP logger module for applications targeting the BEAM runtime.
open Fable.Logging
open Fable.Logging.Beam
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(LoggerProvider()))
let logger = factory.CreateLogger("MyApp")
logger.LogWarning("Connection pool running low: {Available} remaining", 3)Loggers are process-portable: one created here can be sent to another process, stored in ETS, or closed over by a spawned process. This matters for the usual BEAM server shape, where the process that configures logging is not the process that logs — a web handler running in a per-request process, for example.
The factory and providers themselves are not portable; keep them in the process that configures logging and pass out loggers.
Log levels match the .NET LogLevel enum:
| Level | Value | Method | Description |
|---|---|---|---|
| Trace | 0 | LogTrace |
Most detailed messages, may contain sensitive data |
| Debug | 1 | LogDebug |
Debugging and development |
| Information | 2 | LogInformation |
General flow of the application |
| Warning | 3 | LogWarning |
Abnormal or unexpected events |
| Error | 4 | LogError |
Errors and exceptions |
| Critical | 5 | LogCritical |
Failures requiring immediate attention |
| None | 6 | Suppresses all logging |
Set a minimum log level to filter out less severe messages:
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(ConsoleLoggerProvider())
builder.SetMinimumLevel(LogLevel.Warning))
let logger = factory.CreateLogger("MyApp")
logger.LogDebug("This is filtered out")
logger.LogWarning("This is logged")Use named placeholders in log messages for structured logging. The placeholder names become keys in the structured log output, while values are substituted positionally:
logger.LogInformation("Order {OrderId} placed by {Customer}", 1234, "Alice")
// Output: MyApp - Order 1234 placed by Alicetry
failwith "Something broke"
with ex ->
logger.LogError("Operation failed", ex)The factory dispatches log messages to all registered providers:
let factory =
LoggerFactory.Create(fun builder ->
builder.AddProvider(consoleProvider)
builder.AddProvider(jsonProvider))
// Messages are sent to both providers
let logger = factory.CreateLogger("MyApp")
logger.LogInformation("This goes to all providers")A logger captures the factory's providers when it is created. Registering a
provider later with factory.AddProvider applies to loggers created from that
point on, and leaves already-created loggers untouched — so configure providers
before handing out loggers.
Implement ILoggerProvider and ILogger to create your own logging backend:
open Fable.Logging
type MyLogger(name: string) =
interface ILogger with
member _.Log(state: LogState) =
printfn "[%A] %s: %s" state.Level name state.Format
member _.IsEnabled(logLevel: LogLevel) = true
member _.BeginScope(_) = failwith "Not implemented"
type MyLoggerProvider() =
interface ILoggerProvider with
member _.CreateLogger(name) = MyLogger(name)
member _.Dispose() = ()On the BEAM, keep your logger type free of mutable instance state — no
mutable fields, no member val ... with get, set, and no mutable collections
such as ResizeArray. Any of these makes Fable back the instance with the
process dictionary, which confines it to the process that created it; using it
from another process fails at the first member access with
{badmap,undefined}. Pass configuration as constructor parameters instead, as
MyLogger does above. Providers may hold mutable state, since they stay in the
configuring process.
This project is licensed under the MIT License - see the LICENSE file for details.