{"id":16715295,"url":"https://github.com/theangrybyrd/fsliblog","last_synced_at":"2025-03-21T20:33:52.118Z","repository":{"id":34179762,"uuid":"170011765","full_name":"TheAngryByrd/FsLibLog","owner":"TheAngryByrd","description":"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.","archived":false,"fork":false,"pushed_at":"2023-03-14T17:55:23.000Z","size":2641,"stargazers_count":56,"open_issues_count":4,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-18T05:08:03.792Z","etag":null,"topics":["dotnet","dotnet-core","fsharp","logging"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheAngryByrd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"TheAngryByrd"}},"created_at":"2019-02-10T18:34:08.000Z","updated_at":"2025-01-28T10:32:56.000Z","dependencies_parsed_at":"2024-01-07T00:09:30.713Z","dependency_job_id":null,"html_url":"https://github.com/TheAngryByrd/FsLibLog","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheAngryByrd%2FFsLibLog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheAngryByrd%2FFsLibLog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheAngryByrd%2FFsLibLog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheAngryByrd%2FFsLibLog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheAngryByrd","download_url":"https://codeload.github.com/TheAngryByrd/FsLibLog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244866467,"owners_count":20523520,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","dotnet-core","fsharp","logging"],"created_at":"2024-10-12T21:08:54.058Z","updated_at":"2025-03-21T20:33:51.632Z","avatar_url":"https://github.com/TheAngryByrd.png","language":"F#","funding_links":["https://github.com/sponsors/TheAngryByrd"],"categories":[],"sub_categories":[],"readme":"# FsLibLog\n\n## What is this?\n\nFsLibLog is a single file you can copy paste or add through [Paket Github dependencies](https://fsprojects.github.io/Paket/github-dependencies.html) to provide your F# library with a logging abstraction. This is a port of the C# [LibLog](https://github.com/damianh/LibLog).\n\n## Why does this exist?\n\nWhen 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](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/dependencies#diamond-dependencies) problem. Since this is just a file you compile into your library, no dependency is taken and is transparent to your consumers.\n\nAdditionally, loggers aren't particularly friendly for F#, this sets out to resolve that.\n\n## How to get started\n\n### 1. Put the file into your project\n\n#### Option 1\n\nCopy/paste [FsLibLog.fs](https://github.com/TheAngryByrd/FsLibLog/blob/master/src/FsLibLog/FsLibLog.fs) into your library.\n\n#### Option 2\n\nRead over [Paket Github dependencies](https://fsprojects.github.io/Paket/github-dependencies.html).\n\nAdd the following line to your `paket.depedencies` file.\n\n```paket\ngithub TheAngryByrd/FsLibLog src/FsLibLog/FsLibLog.fs\n```\n\nThen add the following line to projects with `paket.references` file you want FsLibLog to be available to.\n\n```paket\nFile: FsLibLog.fs\n```\n\n### 2. Replace its namespace with yours\n\nTo alleviate potential naming conflicts, it's best to replace FsLibLog namespace with your own.\n\nHere is an example with FAKE 5:\n\n```fsharp\nTarget.create \"Replace\" \u003c| fun _ -\u003e\n  Shell.replaceInFiles\n    [ \"FsLibLog\", \"MyLib.Logging\" ]\n    (!! \"paket-files/TheAngryByrd/FsLibLog/src/FsLibLog/FsLibLog.fs\")\n```\n\n### 3. [Setup a LogProvider](#log-providers)\n\n## Using in your library\n\n### Open namespaces\n\n```fsharp\nopen FsLibLog\nopen FsLibLog.Types\n```\n\n### Get a logger\n\nThere are currently six ways to get a logger.\n\n- `getCurrentLogger` - __Deprecated__ because inferring the correct StackFrame is too difficult. Creates a logger. It's name is based on the current StackFrame.\n- `getLoggerByFunc` - Creates a logger based on `Reflection.MethodBase.GetCurrentMethod` call.  This is only useful for calls within functions.\n- `getLoggerByQuotation` - Creates a logger given a Quotations.Expr type. This is only useful for module level declarations.\n- `getLoggerFor` - Creates a logger given a `'a` type.\n- `getLoggerByType` - Creates a logger given a `Type`.\n- `getLoggerByName` - Creates a logger given a `string`.\n\n**Fable** libraries can only use the following:\n- `getLoggerByType`\n- `getLoggerByName`\n- `getLoggerFor`\n\nThe other functions rely reflection and thus are only available when compiling on `dotnet`.\n\n### Set the loglevel, message, exception and parameters\n\nChoose a LogLevel. (Fatal|Error|Warn|Info|Debug|Trace).\n\nThere are helper methods on the logger instance, such as `logger.warn`.\n\nThese helper functions take a `(Log -\u003e 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.\n\n```fsharp\nlogger.warn(\n    Log.setMessage \"{name} Was said hello to\"\n    \u003e\u003e Log.addParameter name\n)\n```\n\nThe set of functions to augment the `Log` record are\n\n- `Log.setMessage` - Amends a `Log` with a message\n- `Log.setMessageIntepolated` - Ammends `Log` with a message. Using the syntax of `{variableName:loggerName}` it will automatically convert an intermpolated string into a proper message template.\n- `Log.setMessageThunk` - Amends a `Log` with a message thunk.  Useful for \"expensive\" string construction scenarios.\n- `Log.addParameter` - Amends a `Log` with a parameter.\n- `Log.addParameters` - Amends a `Log` with a list of parameters.\n- `Log.addContext` - Amends a `Log` with 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.\n- `Log.addContextDestructured` - Amends a `Log` with 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 calling `ToString()` on it.  WARNING: Destructring can be expensive\n- `Log.addException` - Amends a `Log` with an exception.\n\n### Full Example\n\n```fsharp\nnamespace SomeLib\nopen FsLibLog\nopen FsLibLog.Types\nopen FsLibLog.Operators\n\n\nmodule Say =\n    let logger = LogProvider.getCurrentLogger()\n\n    type AdditionalData = {\n        Name : string\n    }\n\n\n    // Example Log Output:\n    // 16:23 [Information] \u003cSomeLib.Say\u003e () \"Captain\" Was said hello to - {\"UserContext\": {\"Name\": \"User123\", \"$type\": \"AdditionalData\"}, \"FunctionName\": \"hello\"}\n    let hello name  =\n        // Starts the log out as an Informational log\n        logger.info(\n            Log.setMessage \"{name} Was said hello to\"\n            // MessageTemplates require the order of parameters to be consistent with the tokens to replace\n            \u003e\u003e Log.addParameter name\n            // This adds additional context to the log, it is not part of the message template\n            // This is useful for things like MachineName, ProcessId, ThreadId, or anything that doesn't easily fit within a MessageTemplate\n            // This is the same as calling `LogProvider.openMappedContext` right before logging.\n            \u003e\u003e Log.addContext \"FunctionName\" \"hello\"\n            // This is the same as calling `LogProvider.openMappedContextDestucturable`  right before logging.\n            \u003e\u003e Log.addContextDestructured \"UserContext\"  {Name = \"User123\"}\n        )\n        sprintf \"hello %s.\" name\n\n\n    // Example Log Output:\n    // 16:23 [Debug] \u003cSomeLib.Say\u003e () In nested - {\"DestructureTrue\": {\"Name\": \"Additional\", \"$type\": \"AdditionalData\"}, \"DestructureFalse\": \"{Name = \\\"Additional\\\";}\", \"Value\": \"bar\"}\n    // [Information] \u003cSomeLib.Say\u003e () \"Commander\" Was said hello to - {\"UserContext\": {\"Name\": \"User123\", \"$type\": \"AdditionalData\"}, \"FunctionName\": \"hello\", \"DestructureTrue\": {\"Name\": \"Additional\", \"$type\": \"AdditionalData\"}, \"DestructureFalse\": \"{Name = \\\"Additional\\\";}\", \"Value\": \"bar\"}\n    let nestedHello name =\n        // This sets additional context to any log within scope\n        // This is useful if you want to add this to all logs within this given scope\n        use x = LogProvider.openMappedContext \"Value\" \"bar\"\n        // This doesn't destructure the record and calls ToString on it\n        use x = LogProvider.openMappedContext \"DestructureFalse\" {Name = \"Additional\"}\n        // This does destructure the record,  Destructuring can be expensive depending on how big the object is.\n        use x = LogProvider.openMappedContextDestucturable \"DestructureTrue\" {Name = \"Additional\"} true\n\n        logger.debug(\n            Log.setMessage \"In nested\"\n        )\n        // The log in `hello` should also have these additional contexts added\n        hello name\n\n\n    // Example Log Output:\n    // 16:23 [Error] \u003cSomeLib.Say\u003e () \"DaiMon\" was rejected. - {}\n    // System.Exception: Sorry DaiMon isnt valid\n    //    at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1647.Invoke(String message)\n    //    at SomeLib.Say.fail(String name) in /Users/jimmybyrd/Documents/GitHub/FsLibLog/examples/SomeLib/Library.fs:line 57\n    let fail name =\n        try\n            failwithf \"Sorry %s isnt valid\" name\n        with e -\u003e\n            // Starts the log out as an Error log\n            logger.error(\n                Log.setMessage \"{name} was rejected.\"\n                // MessageTemplates require the order of parameters to be consistent with the tokens to replace\n                \u003e\u003e Log.addParameter name\n                // Adds an exception to the log\n                \u003e\u003e Log.addException  e\n            )\n\n    // Example Log Output:\n    // 2021-09-15T20:34:14.9060810-04:00 [Information] \u003cSomeLib.Say\u003e () The user {\"Name\": \"Ensign Kim\", \"$type\": \"AdditionalData\"} has requested a reservation date of \"2021-09-16T00:34:14.8853360+00:00\"\n    let interpolated (person : AdditionalData) (reservationDate : DateTimeOffset) =\n        // Starts the log out as an Info log\n        logger.info(\n            // Generates a message template via a specific string intepolation syntax.\n            // Add the name of the property after the expression\n            // for example: \"person\" will be logged as \"User\" and \"reservationDate\" as \"ReservationDate\"\n            Log.setMessageI $\"The user {person:User} has requested a reservation date of {reservationDate:ReservationDate} \"\n        )\n\n    // Has the same logging output as `hello`, above, but uses the Operators module.\n    let helloWithOperators name =\n        // Initiate a log with a message\n        !!! \"{name} Was said hello to\"\n        // Add a parameter\n        \u003e\u003e! name\n        // Adds a value, but does not destructure it.\n        \u003e\u003e!- (\"FunctionName\", \"operators\")\n        // Adds a value \u0026 destructures it.\n        \u003e\u003e!+ (\"UserContext\", {Name = \"User123\"})\n        // Logs at the Info level.\n        |\u003e logger.info\n        sprintf \"hello %s.\" name\n\n```\n\n## Log Providers\n\nProviders 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.\n\n### Currently supported provider\n\n- [Serilog](https://github.com/serilog/serilog)\n- [Microsoft.Extensions.Logging](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0)\n\n#### Setting up Serilog\n\n1. Install [Serilog](https://www.nuget.org/packages/Serilog)\n2. Install [Serilog.Sinks.ColoredConsole](https://www.nuget.org/packages/Serilog.Sinks.Console/) (or any other [Sink](https://github.com/serilog/serilog/wiki/Provided-Sinks))\n3. Create your `Logger`\n\n    ```fsharp\n        let log =\n            LoggerConfiguration()\n                .MinimumLevel.Verbose()\n                .WriteTo.Console(outputTemplate= \"{Timestamp:o} [{Level}] \u003c{SourceContext}\u003e ({Name:l}) {Message:j} - {Properties:j}{NewLine}{Exception}\")\n                .Enrich.FromLogContext()\n                .CreateLogger();\n        Log.Logger \u003c- log\n    ```\n\n4. FsLibLog will pick up Serilog automatically, no need to tell FsLibLog about it\n\n### Setting up Microsoft.Extensions.Logging\n\n1. Install [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging/)\n2. Install [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) (or any other [Provider](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0#logging-providers-1))\n3. Create your `ILoggerFactory`\n\n    ```fsharp\n    let microsoftLoggerFactory = LoggerFactory.Create(fun builder -\u003e\n            builder\n                .SetMinimumLevel(LogLevel.Debug)\n                .AddSimpleConsole(fun opts -\u003e opts.IncludeScopes \u003c-true)\n                // .AddJsonConsole(fun opts -\u003e opts.IncludeScopes \u003c- true)\n            |\u003e ignore\n\n        )\n    ```\n\n4. Tell FsLibLog to use this factory\n\n    ```fsharp\n    FsLibLog.Providers.MicrosoftExtensionsLoggingProvider.setMicrosoftLoggerFactory microsoftLoggerFactory\n    ```\n\n    1. One downside to this is you need to do this for every library your application consumes that uses FsLiblog.\n\n#### Custom Providers\n\nYou can implement and teach FsLibLog about your own custom provider if one is not listed. You have to do 2 things:\n\n1. You have to implement the `ILogProvider` interface. [Example Implemenation](https://github.com/TheAngryByrd/FsLibLog/blob/master/examples/ConsoleExample/Program.fs#L5-L90)\n2. You have to tell FsLibLog to use it. [Example calling FsLibLog.LogProvider.setLoggerProvider](https://github.com/TheAngryByrd/FsLibLog/blob/master/examples/ConsoleExample/Program.fs#L94)\n    1. One downside to this is you need to do this for every library your application consumes that uses FsLiblog.\n\n### Using the Example JsConsoleProvider\n\nThis can look something like [JsConsoleProvider](https://github.com/TheAngryByrd/FsLibLog/blob/master/examples/JsConsoleExample/JsConsoleProvider.fs), with simple \u0026 direct logging to console. Other log providers, such as one to ship front-end logs to a back-end service, are left as an exercise for the reader.\n\n#### Option 1\n\nCopy/paste [JsConsoleProvider.fs](https://github.com/TheAngryByrd/FsLibLog/blob/master/examples/JsConsoleExample/JsConsoleProvider.fs) into your library.\n\n#### Option 2\n\nRead over [Paket Github dependencies](https://fsprojects.github.io/Paket/github-dependencies.html).\n\nAdd the following line to your `paket.depedencies` file.\n\n```\ngithub TheAngryByrd/FsLibLog examples/JsConsoleProvider.JsConsoleProvider.fs\n```\n\nThen add the following line to projects with `paket.references` file you want the console provider to be available to.\n\n```\nFile: JsConsoleProvider.fs\n```\n\n### Register the Log Provider\n\nNote that the log provider must be registered manually in a Fable application, like so:\n\n```\nLogProvider.setLoggerProvider \u003c| JsConsoleProvider.create()\n```\n\nFrom there, the logger can be used as normal.\n\n---\n\n## String Interpolation\n\nThis allows for [string interpolation](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/interpolated-strings) with a special syntax to be convertable to [Message Templates](https://messagetemplates.org/) used in underlying providers (such as Serilog).\n\nTypically when one uses string interpolation it works as such:\n\n```fsharp\nlet favoriteCartoon = \"Captain Planet\"\nlet dayItsOn = \"Saturday\"\nprintfn $\"My favorite cartoon is {favoriteCartoon} and airs on {dayItsOn}\"\n```\n\nF# compiler will create a [FormattableString](https://docs.microsoft.com/en-us/dotnet/api/system.formattablestring?view=net-5.0) where it's `Format` property looks like `My favorite cartoon is {0} and airs on {1}` and the `GetArguments()` are `[| \"Captain Planet\";  \"Saturday\" |]`.  As you can see, `FormattableString` doesn't have the named parameters that `Message Templates` would want.  To make this work the way we want we need to introduce a specific syntax.\n\n```fsharp\nlet favoriteCartoon = \"Captain Planet\"\nlet dayItsOn = \"Saturday\"\nprintfn $\"My favorite cartoon is {favoriteCartoon:CartoonShow} and airs on {dayItsOn:DayAired}\"\n```\n\n`setMessageInterpolated` will make the template look like `My favorite cartoon is {CartoonShow} and airs on {DayAired}`.  This will replace the number arguments with the names after the colon within the interpolated string. This makes a usable message template.\n\n- Why aren't we just trying to get the name of the variable/value? Needing to specify dedicate names _is a good thing_ since refactoring your variable names can have drastic effects on your logging queries. Explicit naming separates these concerns.\n\n## Builds\n\nGitHub Actions |\n:---: |\n[![GitHub Actions](https://github.com/TheAngryByrd/FsLibLog/workflows/Build%20master/badge.svg)](https://github.com/TheAngryByrd/FsLibLog/actions?query=branch%3Amaster) |\n[![Build History](https://buildstats.info/github/chart/TheAngryByrd/FsLibLog)](https://github.com/TheAngryByrd/FsLibLog/actions?query=branch%3Amaster) |\n\n---\n\n### Building\n\nMake sure the following **requirements** are installed in your system:\n\n- [dotnet SDK](https://www.microsoft.com/net/download/core) 2.0 or higher\n- [Mono](http://www.mono-project.com/) if you're on Linux or macOS.\n\n```bash\n\u003e build.cmd // on windows\n$ ./build.sh  // on unix\n```\n\n#### Environment Variables\n\n- `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands.  If not set it will default to Release.\n  - `CONFIGURATION=Debug ./build.sh` will result in things like `dotnet build -c Debug`\n- `GITHUB_TOKEN` will be used to upload release notes and nuget packages to github.\n  - Be sure to set this before releasing\n\n### Watch Tests\n\nThe `WatchTests` target will use [dotnet-watch](https://github.com/aspnet/Docs/blob/master/aspnetcore/tutorials/dotnet-watch.md) to watch for changes in your lib or tests and re-run your tests on all `TargetFrameworks`\n\n```bash\n./build.sh WatchTests\n```\n\n### Releasing\n\n- [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/)\n\n```bash\ngit add .\ngit commit -m \"Scaffold\"\ngit remote add origin origin https://github.com/user/MyCoolNewLib.git\ngit push -u origin master\n```\n\n- [Add your nuget API key to paket](https://fsprojects.github.io/Paket/paket-config.html#Adding-a-NuGet-API-key)\n\n```bash\npaket config add-token \"https://www.nuget.org\" 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a\n```\n\n- [Create a GitHub OAuth Token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)\n  - You can then set the `GITHUB_TOKEN` to upload release notes and artifacts to github\n  - Otherwise it will fallback to username/password\n\n- Then update the `RELEASE_NOTES.md` with a new version, date, and release notes [ReleaseNotesHelper](https://fsharp.github.io/FAKE/apidocs/fake-releasenoteshelper.html)\n\n```markdown\n#### 0.2.0 - 2017-04-20\n* FEATURE: Does cool stuff!\n* BUGFIX: Fixes that silly oversight\n```\n\n- You can then use the `Release` target.  This will:\n  - make a commit bumping the version:  `Bump version to 0.2.0` and add the release notes to the commit\n  - publish the package to nuget\n  - push a git tag\n\n```bash\n./build.sh Release\n```\n\n### Code formatting\n\nTo format code run the following target\n\n```bash\n./build.sh FormatCode\n```\n\nThis uses [Fantomas](https://github.com/fsprojects/fantomas) to do code formatting.  Please report code formatting bugs to that repository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheangrybyrd%2Ffsliblog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheangrybyrd%2Ffsliblog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheangrybyrd%2Ffsliblog/lists"}