I'd go with the first option. The creation of a Stopwatch
is very cheap. And with a good wrapper the required code in each API method can be as simple as:
public int MyApiMethod()
{
using (new ExecutionTimeLogger())
{
// All API functionality goes inside this using block.
var theResultValue = 23;
return theResultValue;
}
}
The class ExecutionTimeLogger
would look something like this:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using log4net;
public class ExecutionTimeLogger : IDisposable
{
private readonly ILog log = LogManager.GetLogger("ExecutionTimes");
private readonly string methodName;
private readonly Stopwatch stopwatch;
public ExecutionTimeLogger([CallerMemberName] string methodName = "")
{
this.methodName = methodName;
stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
log.Debug(methodName + "() took " + stopwatch.ElapsedMilliseconds + " ms.");
GC.SuppressFinalize(this);
}
}
Depending on your logger implementation the output could look something like this:
15:04:23.4477 | DEBUG | ExecutionTimes | MyApiMethod() took 42 ms.
Note that the log output will also be generated when the API method threw an exception inside the using
, because the ExecutionTimeLogger
instance will be disposed nethertheless.
The methodName
parameter will be automatically filled by the compiler because it has the [CallerMemberName]
attribute. You don't need to pass it every time you create an ExecutionTimeLogger
.
The line GC.SuppressFinalize(this)
tells the garbage collector that a call to the finalizer of the ExecutionTimeLogger
instance doesn't have to be scheduled, because we know it never created unmanaged resources.
If you are using Unity as the DI framework you could also write a UnityContainerExtension
that wraps every method having a certain custom attribute (say LogExecutionTimeAttribute
for example) with the required measurement and logging code. That's much more complicated though.