FsLibLog
FsLibLog is a single file you can copy paste or add through Paket Github dependencies to provide your F# library with a logging abstraction. This is a port of the C# LibLog.
Install / Use
/learn @TheAngryByrd/FsLibLogREADME
FsLibLog
What is this?
FsLibLog is a single file you can copy paste or add through Paket Github dependencies to provide your F# library with a logging abstraction. This is a port of the C# LibLog.
Why does this exist?
When creating a library for .NET, you typically do not want to depend on a logging framework or abstraction. Depending on a logging framework forces your consumers to use that framework, which is not ideal. Depending on an abstraction can work but you can run into the diamond dependency problem. Since this is just a file you compile into your library, no dependency is taken and is transparent to your consumers.
Additionally, loggers aren't particularly friendly for F#, this sets out to resolve that.
How to get started
1. Put the file into your project
Option 1
Copy/paste FsLibLog.fs into your library.
Option 2
Read over Paket Github dependencies.
Add the following line to your paket.depedencies file.
github TheAngryByrd/FsLibLog src/FsLibLog/FsLibLog.fs
Then add the following line to projects with paket.references file you want FsLibLog to be available to.
File: FsLibLog.fs
2. Replace its namespace with yours
To alleviate potential naming conflicts, it's best to replace FsLibLog namespace with your own.
Here is an example with FAKE 5:
Target.create "Replace" <| fun _ ->
Shell.replaceInFiles
[ "FsLibLog", "MyLib.Logging" ]
(!! "paket-files/TheAngryByrd/FsLibLog/src/FsLibLog/FsLibLog.fs")
3. Setup a LogProvider
Using in your library
Open namespaces
open FsLibLog
open FsLibLog.Types
Get a logger
There are currently six ways to get a logger.
getCurrentLogger- Deprecated because inferring the correct StackFrame is too difficult. Creates a logger. It's name is based on the current StackFrame.getLoggerByFunc- Creates a logger based onReflection.MethodBase.GetCurrentMethodcall. This is only useful for calls within functions.getLoggerByQuotation- Creates a logger given a Quotations.Expr type. This is only useful for module level declarations.getLoggerFor- Creates a logger given a'atype.getLoggerByType- Creates a logger given aType.getLoggerByName- Creates a logger given astring.
Fable libraries can only use the following:
getLoggerByTypegetLoggerByNamegetLoggerFor
The other functions rely reflection and thus are only available when compiling on dotnet.
Set the loglevel, message, exception and parameters
Choose a LogLevel. (Fatal|Error|Warn|Info|Debug|Trace).
There are helper methods on the logger instance, such as logger.warn.
These helper functions take a (Log -> Log) which allows you to amend the log record easily with functions in the Log module. You can use function composition to set the fields much easier.
logger.warn(
Log.setMessage "{name} Was said hello to"
>> Log.addParameter name
)
The set of functions to augment the Log record are
Log.setMessage- Amends aLogwith a messageLog.setMessageIntepolated- AmmendsLogwith a message. Using the syntax of{variableName:loggerName}it will automatically convert an intermpolated string into a proper message template.Log.setMessageThunk- Amends aLogwith a message thunk. Useful for "expensive" string construction scenarios.Log.addParameter- Amends aLogwith a parameter.Log.addParameters- Amends aLogwith a list of parameters.Log.addContext- Amends aLogwith additional named parameters for context. This helper adds more context to a log. This DOES NOT affect the parameters set for a message template. This is the same calling OpenMappedContext right before logging.Log.addContextDestructured- Amends aLogwith additional named parameters for context. This helper adds more context to a log. This DOES NOT affect the parameters set for a message template. This is the same calling OpenMappedContext right before logging. This destructures an object rather than callingToString()on it. WARNING: Destructring can be expensiveLog.addException- Amends aLogwith an exception.
Full Example
namespace SomeLib
open FsLibLog
open FsLibLog.Types
open FsLibLog.Operators
module Say =
let logger = LogProvider.getCurrentLogger()
type AdditionalData = {
Name : string
}
// Example Log Output:
// 16:23 [Information] <SomeLib.Say> () "Captain" Was said hello to - {"UserContext": {"Name": "User123", "$type": "AdditionalData"}, "FunctionName": "hello"}
let hello name =
// Starts the log out as an Informational log
logger.info(
Log.setMessage "{name} Was said hello to"
// MessageTemplates require the order of parameters to be consistent with the tokens to replace
>> Log.addParameter name
// This adds additional context to the log, it is not part of the message template
// This is useful for things like MachineName, ProcessId, ThreadId, or anything that doesn't easily fit within a MessageTemplate
// This is the same as calling `LogProvider.openMappedContext` right before logging.
>> Log.addContext "FunctionName" "hello"
// This is the same as calling `LogProvider.openMappedContextDestucturable` right before logging.
>> Log.addContextDestructured "UserContext" {Name = "User123"}
)
sprintf "hello %s." name
// Example Log Output:
// 16:23 [Debug] <SomeLib.Say> () In nested - {"DestructureTrue": {"Name": "Additional", "$type": "AdditionalData"}, "DestructureFalse": "{Name = \"Additional\";}", "Value": "bar"}
// [Information] <SomeLib.Say> () "Commander" Was said hello to - {"UserContext": {"Name": "User123", "$type": "AdditionalData"}, "FunctionName": "hello", "DestructureTrue": {"Name": "Additional", "$type": "AdditionalData"}, "DestructureFalse": "{Name = \"Additional\";}", "Value": "bar"}
let nestedHello name =
// This sets additional context to any log within scope
// This is useful if you want to add this to all logs within this given scope
use x = LogProvider.openMappedContext "Value" "bar"
// This doesn't destructure the record and calls ToString on it
use x = LogProvider.openMappedContext "DestructureFalse" {Name = "Additional"}
// This does destructure the record, Destructuring can be expensive depending on how big the object is.
use x = LogProvider.openMappedContextDestucturable "DestructureTrue" {Name = "Additional"} true
logger.debug(
Log.setMessage "In nested"
)
// The log in `hello` should also have these additional contexts added
hello name
// Example Log Output:
// 16:23 [Error] <SomeLib.Say> () "DaiMon" was rejected. - {}
// System.Exception: Sorry DaiMon isnt valid
// at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1647.Invoke(String message)
// at SomeLib.Say.fail(String name) in /Users/jimmybyrd/Documents/GitHub/FsLibLog/examples/SomeLib/Library.fs:line 57
let fail name =
try
failwithf "Sorry %s isnt valid" name
with e ->
// Starts the log out as an Error log
logger.error(
Log.setMessage "{name} was rejected."
// MessageTemplates require the order of parameters to be consistent with the tokens to replace
>> Log.addParameter name
// Adds an exception to the log
>> Log.addException e
)
// Example Log Output:
// 2021-09-15T20:34:14.9060810-04:00 [Information] <SomeLib.Say> () The user {"Name": "Ensign Kim", "$type": "AdditionalData"} has requested a reservation date of "2021-09-16T00:34:14.8853360+00:00"
let interpolated (person : AdditionalData) (reservationDate : DateTimeOffset) =
// Starts the log out as an Info log
logger.info(
// Generates a message template via a specific string intepolation syntax.
// Add the name of the property after the expression
// for example: "person" will be logged as "User" and "reservationDate" as "ReservationDate"
Log.setMessageI $"The user {person:User} has requested a reservation date of {reservationDate:ReservationDate} "
)
// Has the same logging output as `hello`, above, but uses the Operators module.
let helloWithOperators name =
// Initiate a log with a message
!!! "{name} Was said hello to"
// Add a parameter
>>! name
// Adds a value, but does not destructure it.
>>!- ("FunctionName", "operators")
// Adds a value & destructures it.
>>!+ ("UserContext", {Name = "User123"})
// Logs at the Info level.
|> logger.info
sprintf "hello %s." name
Log Providers
Providers are the actual logging framework that sends the logs to some destination (console, file, logging service). FsLibLog uses reflection to inspect the running application and wire these up telling FsLibLog to do it.
Currently supported provider
Setting up Serilog
- Install Serilog
- Install Serilog.Sinks.ColoredConsole (or an
Related Skills
node-connect
350.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
350.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
350.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
