What logging framework is better to use in F# code
As far as I know, they're the same for F#, i.e. there's nothing F# specific about them (whether good or bad). Aside from configuration, usage is pretty much the same for all logging libraries.
What you might want to add is printf-enabled logging, so instead of logger.DebugFormat("Hello {0}", "world")
or logger.Debug(sprintf "Hello %s" "world")
you can just do logger.Debugf "Hello %s" "world"
. Use type extensions and kprintf to do this.
The Logary library
https://github.com/logary/logary
I'm the author of Logary, which supports logging, metrics and distributed tracing for .Net Core.
Targets include: TextWriter, Console, LiterateConsole, Debugger, GCP Pub/Sub, GCP BigQuery, GCP Stackdriver, Jaeger, TCP (Shipper), UDP (Shipper), ZeroMQ (Shipper) Elasticsearch, Graphite/statsd, elmah.io, Aliyun, Azure ApplicationInsights, Mixpanel (commercial), OpsGenie (commercial), Server-sent-events (web push).
Further, you can expose a HTTP server for Proemetheus to scrape with Logary.Prometheus.
It also has a Dash service that supports real-time push of logs to your web browser.
Further, Logary Rutta is a sidecar container implementation or stand-alone log router for the cloud-native era.
Logary JS is a logging and metrics library for JavaScript that can ship into Logary Rutta on the server side, from where you can then furthr the logs to any of the available targets.
Logary Facade is an Apache 2-licensed facade you can copy-n-paste into all your C# and F# libraries and get high-quality console logging with.
Logary is written in F# for F# primarily.
Install-Package Logary
docs here
All of the above is free to use for non-commercial purposes. You can see the different licenses here.
I did code something like this (using verbosal syntax):
#light "off"
open System.Runtime.CompilerServices
let inline (|?) (a: 'a option) b = if a.IsSome then a.Value else b; // coalesce operator
type T() = class
static member private printLog(par) =
match ( par) with
| msg, Some m, Some p, Some l -> (
let pl = Array.head (Array.rev( string(p).Split([|'\\';'/'|]))) in
printfn "at %s(%s: line %d) %s" m pl l msg
)
| msg, _,_,_ -> printfn "at ?? %s" msg
static member LOG(msg: string, ?a:obj,
[<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?path: string,
[<CallerLineNumber>] ?line: int) = match a with
| Some a -> (match a with
| :? int as i -> T.printLog((sprintf "%s %d" msg i), memberName, path,line)
| :? float as f -> T.printLog((sprintf "%s %f" msg f), memberName, path,line)
| _ -> T.printLog((sprintf "%s %A" msg a), memberName, path,line)
)
| None -> T.printLog(msg, memberName, path,line)
static member EXIT(?msg:string, [<CallerMemberName>] ?memberName: string,
[<CallerFilePath>] ?path: string,
[<CallerLineNumber>] ?line: int) =
printf "Exiting ... ";
T.printLog((msg |? "Giving up!"), memberName, path,line);
exit 1
end
usage:
"text pushed in" |> T.LOG;
T.LOG "just text at line ";
T.LOG ("just text at line in par");
T.LOG ("string ", "text i got");
T.LOG ("int ", 1);
T.LOG ("tuple ", (1,2));
let msg = Some "existing optional value" in
printfn """ (msg |? "this is default value\") ---> %A""" (msg |? "d
T.EXIT( "after all test done no TODO new extentions");
produces:
at testit(TautoLogics.fs: line 49) text pushed in
at testit(TautoLogics.fs: line 52) just text at line
at testit(TautoLogics.fs: line 53) just text at line in par
at testit(TautoLogics.fs: line 54) string "text i got"
at testit(TautoLogics.fs: line 55) int 1
at testit(TautoLogics.fs: line 56) tuple (1, 2)
'(msg |? "this is default value\") ---> "existing optional value"
Exiting ... at testit(TautoLogics.fs: line 63) after all test done
just simple and eazy to use for me.