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 is a deliberately small logging contract: write a message at a level, or write one carrying an exception.
Shared 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 actual
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
open 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`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.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`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.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. That is the deliberate default: a library that logs should not start writing to
somebody's console because they forgot to configure 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
ILog is the service a workflow depends on to say something. 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.
Testing
Assert on log output by collecting it:
type LogEnv =
{ Log: ILog }
interface IHasLog with
member this.Log = this.Log
FsLiveDocsGeneratedPage23_4556FC416C2B.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 messages = ResizeArray<LogLevel * string>()
let collecting = { Log = Log.fromSink (fun level message -> messages.Add(level, message)) }
recordAttempt "invoice 7" |> Flow.run collecting |> shouldEqual (Exit.Success())
List.ofSeq messages |> shouldEqual [ LogLevel.Information, "Processing invoice 7" ]
messages.Clear()
everyLevel (InvalidOperationException "boom") |> Flow.run collecting |> ignore
messages.Count |> shouldEqual 10
messages: ResizeArray<LogLevel * string>``.ctor``: unit -> unitInitializes a new instance of the class that is empty and has the default initial capacity.
Axial.LogLevelLog levels used by runtime logging helpers and environment-provided logging functions.
stringAn abbreviation for the CLI type . Basic Types
collecting: 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: stringAdd: LogLevel * string -> unitAdds an object to the end of the . The object to be added to the end of the . The value can be for reference types.
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.
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.
ofSeq: 'T seq -> 'T listBuilds a new list from the given enumerable object. The input sequence. The list of elements from the sequence. let inputs = seq { 1; 2; 5 } inputs |> List.ofSeq Evaluates to [ 1; 2; 5 ]. This is an O(n) operation, where n is the length of the sequence.
InformationClear: unit -> unitRemoves all elements from the .
everyLevel: 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 ()
Count: intGets the number of elements contained in the . The number of elements contained in the .
Because Log.live is a no-op, a test that does not care about logging can supply it and see nothing.

