Last active
August 21, 2025 14:17
-
-
Save TheAngryByrd/bcd07468ba191c8e7e6cc4b614cef7d3 to your computer and use it in GitHub Desktop.
F# Collecting logging metadata like namespace, function name, filepath, and line number
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System.Runtime.CompilerServices | |
type Log() = | |
static member inline GatherLogMetaStackFrame | |
(?name_space: string, [<CallerMemberName>] ?cmb: string, [<CallerLineNumber>] ?cln: int, [<CallerFilePath>] ?cfp: string) | |
= | |
let name_space = | |
match name_space with | |
| Some ns -> ns | |
| _ -> | |
let stackFrame = System.Diagnostics.StackFrame(0) | |
let method = stackFrame.GetMethod() | |
method.DeclaringType.FullName.Split('+') | |
|> String.concat "." | |
let cmb = defaultArg cmb "" | |
let cln = defaultArg cln 0 | |
let cfp = defaultArg cfp "" | |
printfn $"{name_space}.{cmb} was called {cfp}:{cln}" // Filepath:LineNumber is a format many tools know how to use for navigation to the source code | |
static member inline GatherLogMetaReflection | |
(?name_space: string, [<CallerMemberName>] ?cmb: string, [<CallerLineNumber>] ?cln: int, [<CallerFilePath>] ?cfp: string) | |
= | |
let name_space = | |
match name_space with | |
| Some ns -> ns | |
| _ -> | |
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName.Split('+') | |
|> String.concat "." | |
let cmb = defaultArg cmb "" | |
let cln = defaultArg cln 0 | |
let cfp = defaultArg cfp "" | |
printfn $"{name_space}.{cmb} was called {cfp}:{cln}" // Filepath:LineNumber is a format many tools know how to use for navigation to the source code | |
module Log = | |
open Microsoft.FSharp.Quotations.Patterns | |
let rec getModuleNamespace = | |
function | |
| PropertyGet(_, propertyInfo, _) -> | |
propertyInfo.DeclaringType.FullName.Split('+') | |
|> String.concat "." | |
| x -> failwithf "Expression is not a property. %A" x | |
module MyTestModule1 = | |
// this will use reflection to get the module namespace | |
let functionInlineReflection () = Log.GatherLogMetaReflection() | |
// this will use the stack frame to get the module namespace | |
[<MethodImpl(MethodImplOptions.AggressiveInlining)>] | |
let functionInlineStackFrame () = Log.GatherLogMetaStackFrame() | |
// To avoid using reflection or stack frame, we have a few boilerplate options: | |
// 0 - Hardcoding the module namespace works but is not as maintainable if the code moves around | |
// 1 - use a reflection/expression to get the module namespace | |
let rec moduleLocation = lazy Log.getModuleNamespace <@ moduleLocation @> | |
let inline functionRecMarker () = | |
Log.GatherLogMetaReflection(name_space = moduleLocation.Value) | |
// However, you can't "reuse" functionRecMarker in other functions, it will always print "functionRecMarker" as the CallerMemberName. | |
// This _can be_ addressed below with LogHelper | |
let indirectMarker () = functionRecMarker () | |
// 2 - use a marker type to get the module namespace | |
type internal Marker = interface end | |
let moduleLocation2 = | |
lazy | |
(typeof<Marker>.DeclaringType.FullName.Split('+') | |
|> String.concat ".") | |
let functionWithTypeOfMarker () = | |
Log.GatherLogMetaReflection(name_space = moduleLocation2.Value) | |
// 3 - use a marker type with inline gathering of the module namespace | |
type internal LogHelper() = | |
static let name_space = | |
lazy | |
(typeof<LogHelper>.DeclaringType.FullName.Split('+') | |
|> String.concat ".") | |
static member inline GatherLogMetaReflection | |
([<CallerMemberName>] ?cmb: string, [<CallerLineNumber>] ?cln: int, [<CallerFilePath>] ?cfp: string) | |
= | |
Log.GatherLogMetaReflection(name_space = name_space.Value, ?cmb = cmb, ?cln = cln, ?cfp = cfp) | |
// This does allow you to reuse the namespace for other functions but it's clearly the largest boilerplate. | |
let helperClassMarker () = LogHelper.GatherLogMetaReflection() | |
do MyTestModule1.functionInlineReflection () // FSI_0053.MyTestModule1.functionInlineReflection was called c:\Users\jimmy\Repositories\LogMetaData.fsx:57 | |
do MyTestModule1.functionInlineStackFrame () // FSI_0053.MyTestModule1.functionInlineStackFrame was called c:\Users\jimmy\Repositories\LogMetaData.fsx:58 | |
do MyTestModule1.functionRecMarker () // FSI_0053.MyTestModule1.functionRecMarker was called c:\Users\jimmy\Repositories\LogMetaData.fsx:62 | |
do MyTestModule1.indirectMarker () // this doesn't give you "indirectMarker" see above : FSI_0053.MyTestModule1.functionRecMarker was called c:\Users\jimmy\Repositories\LogMetaData.fsx:62 | |
do MyTestModule1.functionWithTypeOfMarker () // FSI_0053.MyTestModule1.functionWithTypeOfMarker was called c:\Users\jimmy\Repositories\LogMetaData.fsx:72 | |
do MyTestModule1.helperClassMarker () // FSI_0053.MyTestModule1.helperClassMarker was called c:\Users\jimmy\Repositories\LogMetaData.fsx:83 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment