1
- /// The logging namespace, which contains the logging abstraction for this
1
+ /// The logging namespace, which contains the logging abstraction for this
2
2
/// library. See https://github.com/logary/logary for details. This module is
3
3
/// completely stand-alone in that it has no external references and its adapter
4
4
/// in Logary has been well tested.
@@ -600,6 +600,14 @@ module internal Formatting =
600
600
601
601
exnExceptionParts @ errorsExceptionParts
602
602
603
+ let getLogLevelToken = function
604
+ | Verbose -> LevelVerbose
605
+ | Debug -> LevelDebug
606
+ | Info -> LevelInfo
607
+ | Warn -> LevelWarning
608
+ | Error -> LevelError
609
+ | Fatal -> LevelFatal
610
+
603
611
/// Split a structured message up into theme-able parts (tokens), allowing the
604
612
/// final output to display to a user with colours to enhance readability.
605
613
let literateDefaultTokeniser ( options : LiterateOptions ) ( message : Message ) : ( string * LiterateToken ) list =
@@ -612,14 +620,6 @@ module internal Formatting =
612
620
613
621
let themedExceptionParts = literateColouriseExceptions options message
614
622
615
- let getLogLevelToken = function
616
- | Verbose -> LevelVerbose
617
- | Debug -> LevelDebug
618
- | Info -> LevelInfo
619
- | Warn -> LevelWarning
620
- | Error -> LevelError
621
- | Fatal -> LevelFatal
622
-
623
623
[ " [" , Punctuation
624
624
formatLocalTime message.utcTicks
625
625
" " , Subtext
@@ -684,6 +684,81 @@ module internal Formatting =
684
684
formatExn message.fields +
685
685
formatFields matchedFields message.fields
686
686
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
+
687
762
/// Logs a line in a format that is great for human consumption,
688
763
/// using console colours to enhance readability.
689
764
/// 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
698
773
|> List.map ( fun ( s , t ) ->
699
774
s, options.theme( t))
700
775
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
+
701
785
interface Logger with
702
786
member x.name = name
703
787
member x.logWithAck level msgFactory =
0 commit comments