简体   繁体   中英

Generic logging of function parameters in exception handling

A lot of my C# code follows this pattern:

void foo(string param1, string param2, string param3)
{
    try
    {
         // do something...
    }
    catch(Exception ex)
    {
        LogError(String.Format("Error in foo(param1={0}, param2={1}, param3={2}), exception={3}", param1, param2, param3, ex.Message));
    }
}

Is there a way in .NET to get a Key/Value list of the parameters to a function so that I can call another function to construct my error logging string? OR Do you have a more generic / better way of doing this?

You could use Reflection and the convention that you must pass the parameters to the LogError with the right order:

private static void MyMethod(string s, int x, int y)
{
    try
    {
        throw new NotImplementedException();
    }
    catch (Exception ex)
    {
        LogError(MethodBase.GetCurrentMethod(), ex, s, x, y);
    }
}

private static void LogError(MethodBase method, Exception ex, params object[] values)
{
    ParameterInfo[] parms = method.GetParameters();
    object[] namevalues = new object[2 * parms.Length];

    string msg = "Error in " + method.Name + "(";
    for (int i = 0, j = 0; i < parms.Length; i++, j += 2)
    {
        msg += "{" + j + "}={" + (j + 1) + "}, ";
        namevalues[j] = parms[i].Name;
        if (i < values.Length) namevalues[j + 1] = values[i];
    }
    msg += "exception=" + ex.Message + ")";
    Console.WriteLine(string.Format(msg, namevalues));
}

You could use aspect oriented programming with PostSharp (have a look at http://www.postsharp.org , and the tutorial at http://www.codeproject.com/KB/cs/ps-custom-attributes-1.aspx ). Basically you could do something like this:

public class LogExceptionAttribute : OnExceptionAspect
{
 public override void OnException(MethodExecutionEventArgs eventArgs)
 {
  log.error("Exception occurred in method {0}", eventArgs); 
 }
}

[LoggingOnExceptionAspect]
public foo(int number, string word, Person customer)
{
   // ... something here throws an exception
}

Perhaps not quite what you want, but I'm sure it can be adapted to suit your needs.

No there isn't a way to do this.

The normal practice is to not catch exceptions unless you can handle them.

Ie you would normally only catch exceptions and log them in a top-level exception handler. You will then get a stack trace, but won't of course get details of all the parameters of all method calls in the stack.

Obviously when debugging you want as much detail as possible. Other ways to achieve this are:

  • Use Debug.Assert statements liberally to test assumptions you are making.

  • Instrument your application with logging that can be activate selectively. I use Log4Net, but there are also other alternatives, including using the System.Diagnostics.Trace class.

In any case, if you do catch exceptions only to log them (I'd do this at a tier boundary in an n-tier application, so that exceptions are logged on the server), then you should always rethrow them:

try
{
    ...
}
catch(Exception ex)
{
    log(ex);
    throw;
}

When I have done this I just created a generic dictionary for the logging.

I have this LogArgs class. And logging in a base class that I call when I have an exception.

public class LogArgs
{

    public string MethodName { get; set; }
    public string ClassName { get; set; }
    public Dictionary<string, object> Paramters { get; set; }


    public LogArgs()
    {
        this.Paramters = new Dictionary<string, object>();
    }

}

Then at the start of every method I do

LogArgs args = new LogArgs { ClassName = "ClassName", MethodName = "MethodName" };
args.Paramters.Add("Param1", param1);
args.Paramters.Add("Param2", param2);
args.Paramters.Add("Param3", param3);

base.Logger.MethodStartLog(args);

When I have an error I log it this way.

base.Logger.LogError(args, ex);

You could use a similar style of constructing the message, but add the params keyword in your LogError method to handle the arguments. For example:

    public void LogError(string message, params object[] parameters)
    {
        if (parameters.Length > 0)
            LogError(string.Format(message, parameters));
        else
            LogError(message);
    }

This is little dated post but just in case someone comes across this like I did - I solved this issue by using PostSharp .

It's not practically free though. The Express license (downloadable via NuGet in VS) allows you to decorate your method with [Log] attribute and then choose your already configured mechanism for logging, like log4net nLog etc. Now you will start seeing Debug level entries in your log giving parameter details.

With express license I could only decorate a maximum of 50 methods in my project. If it fits your needs you're good to go!

Late to the party but I did something along these lines a year or so ago: Github Repo

The idea of this setup is much like what your after, but with the ability to hook it up globally, there is more code than I would like there but it works and once plugged in, works for what your after.

If you take a quick look at the ProxyLogger.cs, consider this a wrapper, it will encapsulate any method it is given and execute it while handling the logging of the error as set here. This can then be setup with dependency injection for anything and everything you wish to log, eg:

public void ConfigureServices(HostBuilderContext hostBuilder, IServiceCollection services)
    {
        services.AddOptions();
        services.AddSingleton<IHostedService, HostedService>();

        services.AddSingleton<IMyClass, MyClass>();

        // Logging added for both the hosted service and the example class
        services.Decorate<IMyClass, ProxyLogger<IMyClass>>();
        services.Decorate<IHostedService, ProxyLogger<IHostedService>>();
    }

You register your services as normal, but on top of that, you can decorate them with the proxy logger to handle the execution and log the details before, after, on failure etc, with FULL params. I was obsessed with this for a while and this is as best as I could get it, but it works really well.

There are scenarios of a few parameters or Large number of parameters...

  1. Few parameters, without much ado, better write them as part of the logging/exception message.

  2. In large parameters, a multi-layer application would be using ENTITIES ( like customer, CustomerOrder...) to transfer data between layers. These entities should implement override ToString() methods of class Object, there by,

Logmessage(" method started " + paramObj.ToString()) would give the list of data in the object.. Any opinions? :)

thanks

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM