'How to use Serilog.Log.ForContext with F# function or C# Method
Using Serilog and F# how to use .ForContext
with functions? it appears it only accepts type classes:
type A()=
log = Serilog.Log.ForContext<A>() // ✅
let f _ =
log = Serilog.Log.ForContext<f>() // compile error
log = Serilog.Log.ForContext(f.GetType()) // the information is not the same as A where it gives module + type path
What would be the right way to add the context of a F# function or a C# method (which I presume should be the same problem)?
So far my solution has been using this function which return SourceContext exactly as using a class (moduleFullName+className
):
let logFromFn _ =
Serilog.Log.ForContext
("SourceContext",
StackTrace().GetFrame(1).GetMethod().DeclaringType
|> fun dt -> dt.FullName + "+" + dt.Name)
- As stated in this answer, it might be expensive, I use it in places where it would be called once in the whole program.
- It works well in F# functions inside modules, not so good in for example, the entry point file, where it returns
program+program
Solution 1:[1]
Methods don't have a Type
associated with them so you can't use either of the ForContext<T>()
or ForContext(Type type)
overloads. Since functions in an F# module are compiled to static methods inside a static class (the module), the same applies to them. As a simple alternative, you could set the SourceContext
property yourself:
let propName = Serilog.Core.Constants.SourceContextPropertyName
Log.ForContext(propName, "App.Class.Method")
Log.ForContext(propName, "App.Module.function")
If you want to be robust to rename operations, you could do something like the following:
class Class
{
public void Method()
{
var source = $"{typeof(Class).FullName}.{nameof(Method)}";
var logger = Log.ForContext(Core.Constants.SourceContextPropertyName, source);
// ...
}
}
module Module =
// It's a bit fiddly to get the module type in F#, but here's one
// approach (credit https://stackoverflow.com/a/14706890/7694577).
type private Marker = interface end
let private moduleType = typeof<Marker>.DeclaringType
let loggerFor name =
let source = sprintf "%s.%s" moduleType.FullName name
let propName = Serilog.Core.Constants.SourceContextPropertyName
Log.ForContext(propName, source)
// Requires the `nameof` operator from F# 4.7 preview.
// The function needs to be recursive since it is referenced when calling `nameof`.
let rec func () =
let logger = loggerFor (nameof func)
// ...
I'd add that usually the class/module name is enough context to figure out where a message has originated from, so I would suggest leaving out the method/function name from the source context. This would allow you to use the ForContext<T>()
or ForContext(Type type)
overloads, and would therefore simplify things a great deal.
Solution 2:[2]
The key complication in F# is that you cannot use a module
identifier as a type literal, i.e. while you can say Log.ForContext<TypeName>()
, you cannot do Log.ForContext<ModuleName>()
.
The best workaround until the language supports using a module identifier as a Type
and/or a typeof(ModuleName)
facility is added is unfortunately to plonk this ugly boilerplate at the top of your module
definition:
type private Marker = interface end
let private moduleType = typeof<Marker>.DeclaringType
let log = Log.ForContext(moduleType)
The other answer obviously answers the question as posed in full. This one answers a different question as IMO:
As with C#, including the member/function name in the log context is not appropriate idiomatic use of structured logging - the message should make sense independent of you needing to know what function/member it's embedded in.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Ruben Bartelink |
Solution 2 |