Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptILog writes a message at a level, optionally carrying an exception.
MicrosoftLogging.create and MicrosoftLogging.fromFactory wrap each entry in a Microsoft ILogger.BeginScope dictionary containing the current explicitly set named FiberLocal values. FiberLogging.observer does the same for defect entries. Without named values, no scope is opened. Providers must enable scope capture to display these fields.
Show page setup
// Setup for the checked examples on this page.
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actualopen System
open Axial
open Axial.PlatformService
SystemAxialPlatformServicelet recordAttempt name : Flow<#IHasLog, Never, unit> =
Log.info $"Processing {name}"
recordAttempt: 'a -> Flow<'b,Never,unit>name: 'aAxial.Flow`3Axial.PlatformService.IHasLogDeclares that an environment supplies the logging service.
Axial.NeverRepresents an error channel that cannot occur.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
Axial.PlatformService.LogHelpers for the logging service.
info: string -> Flow<'env,'error,unit>Writes an informational log message through an explicit logging service.
Each level has a helper, and two more carry an exception without flattening its stack trace into a string:
let everyLevel (error: exn) : Flow<#IHasLog, Never, unit> =
flow {
do! Log.trace "trace"
do! Log.debug "debug"
do! Log.info "info"
do! Log.warning "warning"
do! Log.error "error"
do! Log.critical "critical"
do! Log.errorExn error "error with exception"
do! Log.criticalExn error "critical with exception"
do! Log.log LogLevel.Information "at a chosen level"
do! Log.logException LogLevel.Warning error "exception at a chosen level"
}
everyLevel: exn -> Flow<'a,Never,unit>error: exnexnAn abbreviation for the CLI type . Basic Types
Axial.Flow`3Axial.PlatformService.IHasLogDeclares that an environment supplies the logging service.
Axial.NeverRepresents an error channel that cannot occur.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
Axial.PlatformService.LogHelpers for the logging service.
trace: string -> Flow<'env,'error,unit>Writes a trace log message through an explicit logging service.
debug: string -> Flow<'env,'error,unit>Writes a debug log message through an explicit logging service.
info: string -> Flow<'env,'error,unit>Writes an informational log message through an explicit logging service.
warning: string -> Flow<'env,'error,unit>Writes a warning log message through an explicit logging service.
error: string -> Flow<'env,'error,unit>Writes an error log message through an explicit logging service.
critical: string -> Flow<'env,'error,unit>Writes a critical log message through an explicit logging service.
errorExn: exn -> string -> Flow<'env,'error,unit>Writes an error log message carrying an exception through an explicit logging service.
criticalExn: exn -> string -> Flow<'env,'error,unit>Writes a critical log message carrying an exception through an explicit logging service.
log: LogLevel -> string -> Flow<'env,'error,unit>Writes a log message at the requested level through an explicit logging service.
Axial.LogLevelLog levels used by runtime logging helpers and environment-provided logging functions.
InformationlogException: LogLevel -> exn -> string -> Flow<'env,'error,unit>Writes a log message carrying an exception through an explicit logging service.
WarninglogException exists so the host logger receives the exception object itself, which is what preserves the stack
trace in a structured logging backend.
Supplying the service
Log.live is a no-op logger, so a library does not write to the console unless the host configures a sink. Wire a real one with Log.fromSink:
let consoleLog = Log.fromSink (fun level message -> Console.Error.WriteLine $"[{level}] {message}")
consoleLog: ILogAxial.PlatformService.LogHelpers for the logging service.
fromSink: (LogLevel -> string -> unit) -> ILogCreates a logger from a synchronous sink function. Exceptions are appended to the message text.
level: LogLevelmessage: stringSystem.ConsoleRepresents the standard input, output, and error streams for console applications. This class cannot be inherited.
Error: TextWriterGets the standard error output stream. A that represents the standard error output stream.
WriteLine: string -> unitWrites a string to the text stream, followed by a line terminator. The string to write. If is , only the line terminator is written. The is closed. An I/O error occurs.
Log.fromSink appends the exception text to the message for logException. To hand the exception object to a real
logging framework, implement ILog directly and forward both members.
Log.layer provides the no-op logger; supply your own layer when the application has a sink.
Logging against telemetry
A workflow uses ILog to emit messages. Spans,
metrics, and OpenTelemetry export are covered separately, in observability. The two meet at the
host: an ILog implementation can forward into the same backend the telemetry exporter writes to.
Choose ILog when the workflow itself should emit a message. Choose telemetry when you want the runtime's own
execution structure recorded.
Microsoft.Extensions.Logging adapter
MicrosoftLogging and FiberLogging live in Axial.Hosting, not Axial.PlatformService; the types they adapt
come from Microsoft.Extensions.Logging. MicrosoftLogging.fromFactory wraps a host ILoggerFactory as an ILog,
opening a scope of named FiberLocal values per entry. FiberLogging.observe reports fiber defects through the same
Microsoft logger:
open Microsoft.Extensions.Logging
open Axial
open Axial.Hosting
// `factory` is the host's ILoggerFactory, supplied at composition time.
let logger : ILog = MicrosoftLogging.fromFactory "App" factory
// Report fiber defects through the same Microsoft logger.
let observed (application: Flow<'env, 'error, 'value>) =
application |> FiberLogging.observe (factory.CreateLogger "App")The .NET hosting guide wires both adapters into a Generic Host.
Testing
Assert on log output by collecting it:
type LogEnv =
{ Log: ILog }
interface IHasLog with
member this.Log = this.Log
FsLiveDocsGeneratedPage0_F9357565CFEE.LogEnvLog: ILogAxial.PlatformService.ILogProvides synchronous access to workflow logging as an explicit service.
Axial.PlatformService.IHasLogDeclares that an environment supplies the logging service.
this: LogEnvLog: LogEnv -> unit -> ILoglet gate = obj()
let mutable messages = []
let collecting = { Log = Log.fromSink (fun level message -> lock gate (fun () -> messages <- (level, message) :: messages)) }
recordAttempt "invoice 7" |> Flow.run collecting |> shouldEqual (Exit.Success())
lock gate (fun () -> messages) |> shouldEqual [ LogLevel.Information, "Processing invoice 7" ]
lock gate (fun () -> messages <- [])
everyLevel (InvalidOperationException "boom") |> Flow.run collecting |> ignore
lock gate (fun () -> List.length messages) |> shouldEqual 10
gate: obj``.ctor``: unit -> unitInitializes a new instance of the class.
messages: (LogLevel * string) listcollecting: LogEnvLog: ILogAxial.PlatformService.LogHelpers for the logging service.
fromSink: (LogLevel -> string -> unit) -> ILogCreates a logger from a synchronous sink function. Exceptions are appended to the message text.
level: LogLevelmessage: stringlock: 'Lock -> (unit -> 'T) -> 'TExecute the function as a mutual-exclusion region using the input value as a lock. The object to be locked. The action to perform during the lock. The resulting value. open System.Linq /// A counter object, supporting unlocked and locked increment type TestCounter () = let mutable count = 0 /// Increment the counter, unlocked member this.IncrementWithoutLock() = count <- count + 1 /// Increment the counter, locked member this.IncrementWithLock() = lock this (fun () -> count <- count + 1) /// Get the count member this.Count = count let counter = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter.IncrementWithoutLock()) // Evaluates to a number between 1-100000, non-deterministically because there is no locking counter.Count let counter2 = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter2.IncrementWithLock()) // Evaluates to 100000 deterministically because the increment to the counter object is locked counter2.Count
(::)recordAttempt: 'a -> Flow<'b,Never,unit>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Axial.LogLevelLog levels used by runtime logging helpers and environment-provided logging functions.
InformationeveryLevel: exn -> Flow<'a,Never,unit>``.ctor``: string -> unitInitializes a new instance of the class with a specified error message. The message that describes the error.
ignore: 'T -> unitIgnore the passed value. This is often used to throw away results of a computation. The value to ignore. ignore 55555 // Evaluates to ()
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
length: 'T list -> intReturns the length of the list. The input list. The length of the list. The notation list.Length is preferred. This is an O(n) operation, where n is the length of the list. let inputs = [ "a"; "b"; "c" ] inputs |> List.length Evaluates to 3 This is an O(n) operation, where n is the length of the list.
Tests that do not assert on logging can supply Log.live.

