How to trace log method calls, including the parameter values

I want to log method calls, including their parameter names and values, and what called the method. I want to minimize the amount of coding involved.

For example:

Program.Main() -> Add(a=1, b=2)

What options are available?

  • Aspect-Oriented Programming (AOP) approach that proxies your method calls. It logs the method calls, and then calls your method. In .NET, you can either use PostSharp Logging (license required) or roll your own proxy.
  • A simple built-in approach that uses System.Diagnostics.StackFrame and reflection to get the method’s info.

In this article I’ll explain how to use the simple built-in approach.

Create LogMethodCall() utility method

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace TraceLogMethods
{
    public static class TraceUtil
    {
        [Conditional("TRACE")]
        public static void LogMethodCall(params object[] callingMethodParamValues)
        {
            var method = new StackFrame(skipFrames: 1).GetMethod();
            var methodParams = method.GetParameters();
            var methodCalledBy = new StackFrame(skipFrames: 2).GetMethod();

            var methodCaller = "";
            if (methodCalledBy != null)
            {
               methodCaller = $"{methodCalledBy.DeclaringType.Name}.{methodCalledBy.Name}()";
            }

            if(methodParams.Length == callingMethodParamValues.Length)
            {
                List<string> paramList = new List<string>();
                foreach (var param in methodParams)
                {
                    paramList.Add($"{param.Name}={callingMethodParamValues[param.Position]}");
                }

                Log(method.Name, string.Join(", ", paramList), methodCaller);

            }
            else
            {
               Log(method.Name, "/* Please update to pass in all parameters */", methodCaller);
            } 
            
            
        }

        private static void Log(string methodName, string parameterList, string methodCaller)
        {
            Trace.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.fffff")}\t{methodCaller} -> {methodName}({parameterList})");
        }
    
    }
}

The System.Diagnostics.StackFrame class gives us the call stack. If we look one stack frame up, we get the info about the calling method.

What is [Conditional(“TRACE”)]?

The Conditional attribute tells the compiler to conditionally include the method. In this case, the compiler will only include the LogMethodCall() method if the TRACE constant is defined.

This constant is defined in the project properties. Typically you’d want this turned on in a Debug build, and turned off in a Release build.

Project properties showing the TRACE constant

Turn on trace logging in app.config

I am using the built-in trace logger (you can use whatever logging you want), so I need to turn on trace logging by updating the app.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <system.diagnostics>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <add name="traceListener" type="System.Diagnostics.TextWriterTraceListener"
        initializeData="C:\Temp\trace.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

Call LogMethodCall()

Add a call to TraceUtil.LogMethodCall() in any method you want to trace log, passing in all of that method’s parameters. This may seem tedious, but remember that the alternative is to use an AOP approach (and possibly pay for a license).

using System;

namespace TraceLogMethods
{
    class Program
    {
        static void Main(string[] args)
        {
            //base case - pass in params
            int sum = Add(1, 2);

            //Works with void
            HasNoParams();

            //Works with default parameters
            int sum1 = AddHasDefaultParams(1, 2);
            int sum2 = AddHasDefaultParams(1, 1, 1);

            //Only logs method name if wrong # of parameters passed in to trace method
            PassesWrongNumberOfParams(DateTime.Now, 2);

        }
        static int Add(int a, int b)
        {
            TraceUtil.LogMethodCall(a, b);

            HasNoParams();

            return a + b;
        }
        static void HasNoParams()
        {
            TraceUtil.LogMethodCall();

        }
        static int AddHasDefaultParams(int a, int b, int c=0)
        {
            TraceUtil.LogMethodCall(a, b, c);

            return a + b + c;

        }
        static void PassesWrongNumberOfParams(DateTime calledAt, int b)
        {
            TraceUtil.LogMethodCall(calledAt);
        }

    }
}

Trace log results

After running the program above, here are the contents of the trace log file:

07:46:18.99440	Program.Main() -> Add(a=1, b=2)
07:46:18.99544	Program.Add() -> HasNoParams()
07:46:18.99544	Program.Main() -> HasNoParams()
07:46:18.99544	Program.Main() -> AddHasDefaultParams(a=1, b=2, c=0)
07:46:18.99544	Program.Main() -> AddHasDefaultParams(a=1, b=1, c=1)
07:46:18.99544	Program.Main() -> PassesWrongNumberOfParams(/* Please update to pass in all parameters */)

Leave a Comment