Skip to content

Commit f25b4ce

Browse files
authored
Adds interpolated string (#23)
* Adds interpolated string POC * Add example and README docs * Added anonymous record test * Added doccomment to setMessageInterpolated and formatted code * Adds more info about interpolated strings to README * Update README
1 parent 89e614d commit f25b4ce

File tree

8 files changed

+1036
-933
lines changed

8 files changed

+1036
-933
lines changed

README.md

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
# FsLibLog
22

3-
FsLibLog 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).
3+
## What is this?
44

5+
FsLibLog 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).
56

6-
## Getting started
7+
## Why does this exist?
8+
9+
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](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.
10+
11+
Additionally, loggers aren't particularly friendly for F#, this sets out to resolve that.
12+
13+
## How to get started
714

815
### 1. Put the file into your project
916

@@ -17,17 +24,16 @@ Read over [Paket Github dependencies](https://fsprojects.github.io/Paket/github-
1724

1825
Add the following line to your `paket.depedencies` file.
1926

20-
```
27+
```paket
2128
github TheAngryByrd/FsLibLog src/FsLibLog/FsLibLog.fs
2229
```
2330

2431
Then add the following line to projects with `paket.references` file you want FsLibLog to be available to.
2532

26-
```
33+
```paket
2734
File: FsLibLog.fs
2835
```
2936

30-
3137
### 2. Replace its namespace with yours
3238

3339
To alleviate potential naming conflicts, it's best to replace FsLibLog namespace with your own.
@@ -41,10 +47,9 @@ Target.create "Replace" <| fun _ ->
4147
(!! "paket-files/TheAngryByrd/FsLibLog/src/FsLibLog/FsLibLog.fs")
4248
```
4349

44-
4550
## Using in your library
4651

47-
### Open namespaces
52+
### Open namespaces
4853

4954
```fsharp
5055
open FsLibLog
@@ -55,14 +60,13 @@ open FsLibLog.Types
5560

5661
There are currently three ways to get a logger.
5762

58-
- `getCurrentLogger` - __Deprecated__ because inferring the correct StackFrame is too difficult. Creates a logger. It's name is based on the current StackFrame.
63+
- `getCurrentLogger` - __Deprecated__ because inferring the correct StackFrame is too difficult. Creates a logger. It's name is based on the current StackFrame.
5964
- `getLoggerByFunc` - Creates a logger based on `Reflection.MethodBase.GetCurrentMethod` call. This is only useful for calls within functions.
6065
- `getLoggerByQuotation` - Creates a logger given a Quotations.Expr type. This is only useful for module level declarations.
6166
- `getLoggerFor` - Creates a logger given a `'a` type.
6267
- `getLoggerByType` - Creates a logger given a `Type`.
6368
- `getLoggerByName` - Creates a logger given a `string`.
6469

65-
6670
### Set the loglevel, message, exception and parameters
6771

6872
Choose a LogLevel. (Fatal|Error|Warn|Info|Debug|Trace).
@@ -81,16 +85,15 @@ logger.warn(
8185
The set of functions to augment the `Log` record are
8286

8387
- `Log.setMessage` - Amends a `Log` with a message
88+
- `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.
8489
- `Log.setMessageThunk` - Amends a `Log` with a message thunk. Useful for "expensive" string construction scenarios.
8590
- `Log.addParameter` - Amends a `Log` with a parameter.
8691
- `Log.addParameters` - Amends a `Log` with a list of parameters.
8792
- `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.
8893
- `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
8994
- `Log.addException` - Amends a `Log` with an exception.
9095

91-
92-
93-
### Full Example:
96+
### Full Example
9497

9598
```fsharp
9699
namespace SomeLib
@@ -162,6 +165,17 @@ module Say =
162165
>> Log.addException e
163166
)
164167
168+
// Example Log Output:
169+
// 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"
170+
let interpolated (person : AdditionalData) (reservationDate : DateTimeOffset) =
171+
// Starts the log out as an Info log
172+
logger.info(
173+
// Generates a message template via a specific string intepolation syntax.
174+
// Add the name of the property after the expression
175+
// for example: "person" will be logged as "user" and "reservationDate" as "reservationDate"
176+
Log.setMessageI $"The user {person:User} has requested a reservation date of {reservationDate:ReservationDate} "
177+
)
178+
165179
// Has the same logging output as `hello`, above, but uses the Operators module.
166180
let helloWithOperators name =
167181
// Initiate a log with a message
@@ -178,95 +192,127 @@ module Say =
178192
179193
```
180194

195+
## Log Providers
181196

182-
## Currently supported providers
197+
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.
198+
199+
### Currently supported provider
183200

184201
- [Serilog](https://github.com/serilog/serilog)
185202

203+
### Custom Providers
204+
205+
You can implement and teach FsLibLog about your own custom provider if one is not listed. You have to do 2 things:
206+
207+
1. You have to implement the `ILogProvider` interface. [Example Implemenation](https://github.com/TheAngryByrd/FsLibLog/blob/master/examples/ConsoleExample/Program.fs#L5-L90)
208+
2. 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)
209+
a. One downside to this is you need to do this for every library your application consumes that uses FsLiblog.
210+
186211
---
187212

213+
## String Interpolation
214+
215+
This 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).
216+
217+
Typically when one uses string interpolation it works as such:
218+
219+
```fsharp
220+
let favoriteCartoon = "Captain Planet"
221+
let dayItsOn = "Saturday"
222+
printfn $"My favorite cartoon is {favoriteCartoon} and airs on {dayItsOn}"
223+
```
224+
225+
F# 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.
226+
227+
```fsharp
228+
let favoriteCartoon = "Captain Planet"
229+
let dayItsOn = "Saturday"
230+
printfn $"My favorite cartoon is {favoriteCartoon:CartoonShow} and airs on {dayItsOn:DayAired}"
231+
```
232+
233+
`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.
234+
235+
- 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.
236+
188237
## Builds
189238

190239
GitHub Actions |
191240
:---: |
192241
[![GitHub Actions](https://github.com/TheAngryByrd/FsLibLog/workflows/Build%20master/badge.svg)](https://github.com/TheAngryByrd/FsLibLog/actions?query=branch%3Amaster) |
193242
[![Build History](https://buildstats.info/github/chart/TheAngryByrd/FsLibLog)](https://github.com/TheAngryByrd/FsLibLog/actions?query=branch%3Amaster) |
194243

195-
196244
---
197245

198246
### Building
199247

200-
201248
Make sure the following **requirements** are installed in your system:
202249

203-
* [dotnet SDK](https://www.microsoft.com/net/download/core) 2.0 or higher
204-
* [Mono](http://www.mono-project.com/) if you're on Linux or macOS.
250+
- [dotnet SDK](https://www.microsoft.com/net/download/core) 2.0 or higher
251+
- [Mono](http://www.mono-project.com/) if you're on Linux or macOS.
205252

206-
```
253+
```bash
207254
> build.cmd // on windows
208255
$ ./build.sh // on unix
209256
```
210257

211258
#### Environment Variables
212259

213-
* `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.
214-
* `CONFIGURATION=Debug ./build.sh` will result in things like `dotnet build -c Debug`
215-
* `GITHUB_TOKEN` will be used to upload release notes and nuget packages to github.
216-
* Be sure to set this before releasing
260+
- `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.
261+
- `CONFIGURATION=Debug ./build.sh` will result in things like `dotnet build -c Debug`
262+
- `GITHUB_TOKEN` will be used to upload release notes and nuget packages to github.
263+
- Be sure to set this before releasing
217264

218265
### Watch Tests
219266

220267
The `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`
221268

222-
```
269+
```bash
223270
./build.sh WatchTests
224271
```
225272

226273
### Releasing
227-
* [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/)
228274

229-
```
275+
- [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/)
276+
277+
```bash
230278
git add .
231279
git commit -m "Scaffold"
232280
git remote add origin origin https://github.com/user/MyCoolNewLib.git
233281
git push -u origin master
234282
```
235283

236-
* [Add your nuget API key to paket](https://fsprojects.github.io/Paket/paket-config.html#Adding-a-NuGet-API-key)
284+
- [Add your nuget API key to paket](https://fsprojects.github.io/Paket/paket-config.html#Adding-a-NuGet-API-key)
237285

238-
```
286+
```bash
239287
paket config add-token "https://www.nuget.org" 4003d786-cc37-4004-bfdf-c4f3e8ef9b3a
240288
```
241289

242-
* [Create a GitHub OAuth Token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)
243-
* You can then set the `GITHUB_TOKEN` to upload release notes and artifacts to github
244-
* Otherwise it will fallback to username/password
290+
- [Create a GitHub OAuth Token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)
291+
- You can then set the `GITHUB_TOKEN` to upload release notes and artifacts to github
292+
- Otherwise it will fallback to username/password
245293

294+
- Then update the `RELEASE_NOTES.md` with a new version, date, and release notes [ReleaseNotesHelper](https://fsharp.github.io/FAKE/apidocs/fake-releasenoteshelper.html)
246295

247-
* Then update the `RELEASE_NOTES.md` with a new version, date, and release notes [ReleaseNotesHelper](https://fsharp.github.io/FAKE/apidocs/fake-releasenoteshelper.html)
248-
249-
```
296+
```markdown
250297
#### 0.2.0 - 2017-04-20
251298
* FEATURE: Does cool stuff!
252299
* BUGFIX: Fixes that silly oversight
253300
```
254301

255-
* You can then use the `Release` target. This will:
256-
* make a commit bumping the version: `Bump version to 0.2.0` and add the release notes to the commit
257-
* publish the package to nuget
258-
* push a git tag
302+
- You can then use the `Release` target. This will:
303+
- make a commit bumping the version: `Bump version to 0.2.0` and add the release notes to the commit
304+
- publish the package to nuget
305+
- push a git tag
259306

260-
```
307+
```bash
261308
./build.sh Release
262309
```
263310

264-
265311
### Code formatting
266312

267313
To format code run the following target
268314

269-
```
315+
```bash
270316
./build.sh FormatCode
271317
```
272318

examples/SerilogExample/Program.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ let main argv =
1919

2020
Say.fail "DaiMon"
2121

22+
Say.interpolated {Name = "Ensign Kim"} DateTimeOffset.UtcNow
23+
2224
Console.ReadLine() |> ignore
2325
0

examples/SomeLib/Library.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace SomeLib
22
open FsLibLog
33
open FsLibLog.Types
4+
open System
45

56
module Say =
67
// let logger = LogProvider.getLoggerByName "SomeLibrary.Say"
@@ -65,3 +66,14 @@ module Say =
6566
// Adds an exception to the log
6667
>> Log.addException e
6768
)
69+
70+
// Example Log Output:
71+
// 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"
72+
let interpolated (person : AdditionalData) (reservationDate : DateTimeOffset) =
73+
// Starts the log out as an Info log
74+
logger.info(
75+
// Generates a message template via a specific string intepolation syntax.
76+
// Add the name of the property after the expression
77+
// for example: "person" will be logged as "user" and "reservationDate" as "reservationDate"
78+
Log.setMessageI $"The user {person:User} has requested a reservation date of {reservationDate:ReservationDate} "
79+
)

paket.dependencies

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
source https://www.nuget.org/api/v2
22
source https://api.nuget.org/v3/index.json
33
storage: none
4-
clitool dotnet-mono 0.5.2
5-
clitool dotnet-sourcelink 2.8.1
6-
clitool dotnet-fantomas 2.8.0
7-
clitool dotnet-reportgenerator-cli 4.0.0-rc4
8-
nuget FSharp.Core 4.3.4
4+
nuget FSharp.Core 5.0
95
nuget Expecto 8.7
106
nuget SourceLink.Create.CommandLine 2.8.1 copy_local: true
117
nuget YoloDev.Expecto.TestSdk 0.7.0

0 commit comments

Comments
 (0)