Skip to content

Commit a786529

Browse files
author
Henrik Feldt
authored
Merge pull request #578 from adamchester/logging-output-template
Allow further customising the console output
2 parents 90d16be + eff5233 commit a786529

File tree

4 files changed

+108
-14
lines changed

4 files changed

+108
-14
lines changed

examples/Example/Program.fs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,19 @@ open Suave.State.CookieStateStore
1717
let basicAuth =
1818
Authentication.authenticateBasic ((=) ("foo", "bar"))
1919

20-
let logger = Targets.create Verbose [||]
20+
// This demonstrates how to customise the console logger output.
21+
// In most cases you wont need this. Instead you can use the more succinct:
22+
// `let logger = Targets.create Verbose [||]`
23+
let loggingOptions =
24+
{ Literate.LiterateOptions.create() with
25+
getLogLevelText = function Verbose->"V" | Debug->"D" | Info->"I" | Warn->"W" | Error->"E" | Fatal->"F" }
26+
27+
let logger = LiterateConsoleTarget(
28+
name = [|"Suave";"Examples";"Example"|],
29+
minLevel = Verbose,
30+
options = loggingOptions,
31+
outputTemplate = "[{level}] {timestampUtc:o} {message} [{source}]{exceptions}"
32+
) :> Logger
2133

2234
/// With this workflow you can write WebParts like this
2335
let task : WebPart =

paket-files/logary/logary/src/Logary.Facade/Facade.fs

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// The logging namespace, which contains the logging abstraction for this
1+
/// The logging namespace, which contains the logging abstraction for this
22
/// library. See https://github.com/logary/logary for details. This module is
33
/// completely stand-alone in that it has no external references and its adapter
44
/// in Logary has been well tested.
@@ -600,6 +600,14 @@ module internal Formatting =
600600

601601
exnExceptionParts @ errorsExceptionParts
602602

603+
let getLogLevelToken = function
604+
| Verbose -> LevelVerbose
605+
| Debug -> LevelDebug
606+
| Info -> LevelInfo
607+
| Warn -> LevelWarning
608+
| Error -> LevelError
609+
| Fatal -> LevelFatal
610+
603611
/// Split a structured message up into theme-able parts (tokens), allowing the
604612
/// final output to display to a user with colours to enhance readability.
605613
let literateDefaultTokeniser (options : LiterateOptions) (message : Message) : (string * LiterateToken) list =
@@ -612,14 +620,6 @@ module internal Formatting =
612620

613621
let themedExceptionParts = literateColouriseExceptions options message
614622

615-
let getLogLevelToken = function
616-
| Verbose -> LevelVerbose
617-
| Debug -> LevelDebug
618-
| Info -> LevelInfo
619-
| Warn -> LevelWarning
620-
| Error -> LevelError
621-
| Fatal -> LevelFatal
622-
623623
[ "[", Punctuation
624624
formatLocalTime message.utcTicks
625625
" ", Subtext
@@ -684,6 +684,81 @@ module internal Formatting =
684684
formatExn message.fields +
685685
formatFields matchedFields message.fields
686686

687+
/// Assists with controlling the output of the `LiterateConsoleTarget`.
688+
module internal LiterateFormatting =
689+
open Literate
690+
type TokenisedPart = string * LiterateToken
691+
type LiterateTokeniser = LiterateOptions -> Message -> TokenisedPart list
692+
693+
type internal TemplateToken = TextToken of text:string | PropToken of name : string * format : string
694+
let internal parseTemplate template =
695+
let tokens = ResizeArray<TemplateToken>()
696+
let foundText (text: string) = tokens.Add (TextToken text)
697+
let foundProp (prop: FsMtParser.Property) = tokens.Add (PropToken (prop.name, prop.format))
698+
FsMtParser.parseParts template foundText foundProp
699+
tokens :> seq<TemplateToken>
700+
701+
[<AutoOpen>]
702+
module OutputTemplateTokenisers =
703+
704+
let tokeniseTimestamp format (options : LiterateOptions) (message : Message) =
705+
let localDateTimeOffset = DateTimeOffset(message.utcTicks, TimeSpan.Zero).ToLocalTime()
706+
let formattedTimestamp = localDateTimeOffset.ToString(format, options.formatProvider)
707+
seq { yield formattedTimestamp, Subtext }
708+
709+
let tokeniseTimestampUtc format (options : LiterateOptions) (message : Message) =
710+
let utcDateTimeOffset = DateTimeOffset(message.utcTicks, TimeSpan.Zero)
711+
let formattedTimestamp = utcDateTimeOffset.ToString(format, options.formatProvider)
712+
seq { yield formattedTimestamp, Subtext }
713+
714+
let tokeniseMissingField name format =
715+
seq {
716+
yield "{", Punctuation
717+
yield name, MissingTemplateField
718+
if not (String.IsNullOrEmpty format) then
719+
yield ":", Punctuation
720+
yield format, Subtext
721+
yield "}", Punctuation }
722+
723+
let tokeniseLogLevel (options : LiterateOptions) (message : Message) =
724+
seq { yield options.getLogLevelText message.level, Formatting.getLogLevelToken message.level }
725+
726+
let tokeniseSource (options : LiterateOptions) (message : Message) =
727+
seq { yield (String.concat "." message.name), Subtext }
728+
729+
let tokeniseNewline (options : LiterateOptions) (message : Message) =
730+
seq { yield Environment.NewLine, Text }
731+
732+
let tokeniseTab (options : LiterateOptions) (message : Message) =
733+
seq { yield "\t", Text }
734+
735+
/// Creates a `LiterateTokeniser` function which can be passed to the `LiterateConsoleTarget`
736+
/// constructor in order to customise how each log message is rendered. The default template
737+
/// would be: `[{timestampLocal:HH:mm:ss} {level}] {message}{newline}{exceptions}`.
738+
/// Available template fields are: `timestamp`, `timestampUtc`, `level`, `source`,
739+
/// `newline`, `tab`, `message`, `exceptions`. Any misspelled or otheriwese invalid property
740+
/// names will be treated as `LiterateToken.MissingTemplateField`.
741+
let tokeniserForOutputTemplate template : LiterateTokeniser =
742+
let tokens = parseTemplate template
743+
fun options message ->
744+
seq {
745+
for token in tokens do
746+
match token with
747+
| TextToken text -> yield text, LiterateToken.Punctuation
748+
| PropToken (name, format) ->
749+
match name with
750+
| "timestamp" -> yield! tokeniseTimestamp format options message
751+
| "timestampUtc" -> yield! tokeniseTimestampUtc format options message
752+
| "level" -> yield! tokeniseLogLevel options message
753+
| "source" -> yield! tokeniseSource options message
754+
| "newline" -> yield! tokeniseNewline options message
755+
| "tab" -> yield! tokeniseTab options message
756+
| "message" -> yield! Formatting.literateFormatValue options message.fields message.value |> snd
757+
| "exceptions" -> yield! Formatting.literateColouriseExceptions options message
758+
| _ -> yield! tokeniseMissingField name format
759+
}
760+
|> Seq.toList
761+
687762
/// Logs a line in a format that is great for human consumption,
688763
/// using console colours to enhance readability.
689764
/// Sample: [10:30:49 INF] User "AdamC" began the "checkout" process with 100 cart items
@@ -698,6 +773,15 @@ type LiterateConsoleTarget(name, minLevel, ?options, ?literateTokeniser, ?output
698773
|> List.map (fun (s, t) ->
699774
s, options.theme(t))
700775

776+
/// Creates the target with a custom output template. The default `outputTemplate`
777+
/// is `[{timestampLocal:HH:mm:ss} {level}] {message}{exceptions}`.
778+
/// Available template fields are: `timestamp`, `timestampUtc`, `level`, `source`,
779+
/// `newline`, `tab`, `message`, `exceptions`. Any misspelled or otheriwese invalid property
780+
/// names will be treated as `LiterateToken.MissingTemplateField`.
781+
new (name, minLevel, outputTemplate, ?options, ?outputWriter, ?consoleSemaphore) =
782+
let tokeniser = LiterateFormatting.tokeniserForOutputTemplate outputTemplate
783+
LiterateConsoleTarget(name, minLevel, ?options=options, literateTokeniser=tokeniser, ?outputWriter=outputWriter, ?consoleSemaphore=consoleSemaphore)
784+
701785
interface Logger with
702786
member x.name = name
703787
member x.logWithAck level msgFactory =

paket.dependencies

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ nuget DotLiquid
88
nuget RazorEngine
99

1010
github haf/YoLo YoLo.fs
11-
github logary/logary src/Logary.Facade/Facade.fs
11+
github logary/logary:c70468f60602e1ab954a38e574b946d4ffbda11d src/Logary.Facade/Facade.fs
1212

1313
group Build
1414
source https://nuget.org/api/v2

paket.lock

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ GITHUB
1111
remote: haf/YoLo
1212
YoLo.fs (76e16a4c9338c2f04cb067add0e3a33b42a0f396)
1313
remote: logary/logary
14-
src/Logary.Facade/Facade.fs (8fe3fce3f7d2602f2a4dc27419edfbce09c28a82)
14+
src/Logary.Facade/Facade.fs (c70468f60602e1ab954a38e574b946d4ffbda11d)
1515
GROUP Build
1616
NUGET
1717
remote: https://www.nuget.org/api/v2
@@ -196,7 +196,6 @@ NUGET
196196
System.AppContext (>= 4.1)
197197
System.Collections (>= 4.0.11)
198198
System.Collections.Concurrent (>= 4.0.12)
199-
System.Collections.Immutable (>= 1.1.37)
200199
System.Collections.Immutable (>= 1.2)
201200
System.Console (>= 4.0)
202201
System.Diagnostics.Debug (>= 4.0.11)
@@ -210,7 +209,6 @@ NUGET
210209
System.Linq (>= 4.1)
211210
System.Linq.Expressions (>= 4.1)
212211
System.Reflection (>= 4.1)
213-
System.Reflection.Metadata (>= 1.2)
214212
System.Reflection.Metadata (>= 1.3)
215213
System.Reflection.Primitives (>= 4.0.1)
216214
System.Resources.ResourceManager (>= 4.0.1)

0 commit comments

Comments
 (0)