Skip to content

Repository files navigation

Fable.Logging

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.

Packages

Package NuGet Description
Fable.Logging NuGet Core interfaces, LoggerFactory, ConsoleLogger, JS console logger
Fable.Logging.Structlog NuGet Python structlog provider
Fable.Logging.Beam NuGet Erlang/OTP logger provider

Quick Start

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

Platform-Specific Providers

JavaScript (console)

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

Python (structlog)

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

Erlang/BEAM (OTP logger)

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

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

Filtering

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

Message Templates

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 Alice

Logging Exceptions

try
    failwith "Something broke"
with ex ->
    logger.LogError("Operation failed", ex)

Multiple Providers

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.

Writing a Custom Provider

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.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Logging framework for Fable

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages