8

If I have a function that is part of a module, and I want a log entry while inside the function,I have to manually print the function namespace and name e.g.

namespace MyModuleNamespace
 module MyModule = 
 let AddTwoNums logger x y =
    logger.Info("MyModuleNamespace.AddTwoNums - Start")
    let res = x+y
    logger.Info("MyModuleNamespace.AddTwoNums - End")
    res

Is there any way I can automatically work out what "MyModuleNamespace.AddTwoNums" is as it is very cumbersome/error prone especially when you have to rename functions and modules during refactoring of code

Even if that cannot be done, is there any way I can automatically work out what "AddTwoNums" is i.e. the function name?

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
bstack
  • 2,466
  • 3
  • 25
  • 38

3 Answers3

9

There are a couple ways to do this, and I'm not sure which would be the best one for your scenario. One way that I've used in the past is to get the information from the stack trace:

let stackTrace = StackTrace()
let topFrame = stackTrace.GetFrame(0)
let currentFunction = topFrame.GetMethod()
printfn "%s.%s" currentFunction.DeclaringType.Name currentFunction.Name

In order to keep from having to put these lines in every function, you can make an inline function that does this, which will give you the name of the calling function due to the inlining.

let inline getCurrentFunction () =
    let stackTrace = StackTrace()
    let topFrame = stackTrace.GetFrame(0)
    let currentFunction = topFrame.GetMethod()
    sprintf "%s.%s" currentFunction.DeclaringType.Name currentFunction.Name
Aaron M. Eshbach
  • 6,380
  • 12
  • 22
  • 2
    It's indeed worth noting that this is an *extremely* expensive way to get the function name. Getting a stack trace may cost *hundreds of thousands* instructions on some architectures, and reflection may result in disk I/O to get the metadata. Not that expensive if the code is guaranteed to call printf with the result, but not all logging code has this guarantee, naturally. And kudos for the appropriate use of `inline`! :) – kkm inactive - support strike Sep 30 '18 at 19:56
  • Great solution, may be expensive but ideal where performance is not too much of an issue. – bstack Oct 01 '18 at 07:49
  • BTW I tested this using #time across both linux (via dotnetcore) and windows - averages between 0 to 2 ms per execution. This is acceptable enough for our current application from perf perspective – bstack Oct 01 '18 at 10:08
  • @kkm Indeed, it can be very expensive. I once put this in a logging framework and found that logging quickly became the most CPU-intensive part of my application. Use with caution! – Aaron M. Eshbach Oct 01 '18 at 12:07
6

If you don't need the actual namespace / module name or are willing to replace that with the file name, there are special attributes that instruct the compiler to supply corresponding arguments to a function:

open System.Runtime.CompilerServices

type Logger =
    static member Trace(
                        msg: string,
                        [<CallerMemberName>] ?memberName: string,
                        [<CallerFilePath>] ?path: string,
                        [<CallerLineNumberAttribute>] ?line: int) =
        printfn "%s from %s.%s on line %i" msg path.Value memberName.Value line.Value

module Foo =
    let bar() =
        Logger.Trace("hello")

// hello from c:\path\to\Logging.fsx.bar on line 32
Foo.bar()

MS Docs has more to say about Caller Information.

Note that when your run this from within fsi, the line number is only accurate the first time.

CaringDev
  • 8,391
  • 1
  • 24
  • 43
4

Probably not what you want but you can extract it with Quotations:

let x<'T> : 'T = Unchecked.defaultof<'T>
<@ AddTwoNums x x x@> 
|> function Microsoft.FSharp.Quotations.Patterns.Call(_,mi,_)-> mi.Name | _ -> "unknown"

Im guessing you want to look at getting the stacktrace: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace?view=netframework-4.7.2

7sharp9
  • 2,147
  • 16
  • 27