RE: When to log, when to throw - Help!
Duncan Woods <duncan.woods <at> garradhassan.com>
2006-07-03 21:50:30 GMT
Ron,
>> Isn't the automatic clean-up more a feature of the
>> using statement rather than the ThreadContext?
Sorry if I wasn't clear. I consider them intertwined because I wouldn't use explicit (push/pop)
ThreadContext without automatic clear-up. Too error prone.
I have tried to work out a scheme for anonymous delegates to provide context safe exceptions but sadly there
is too much dang strong typing so either you have to declare a delegate type for anything you wish to execute
contextually or do what I attach below which is ugly in so many different ways! Basically it requires that
each level of context is a separate method, which is perfectly acceptable (and often the case anyway), and
then call it as a delegate but sadly passing arguments requires filthy casting and its broken for ref, out,
value-types and return values.
I post it to see if it sparks the creativity of anyone else to find a more effective scheme that is simpler and type-safe.
On a related note - the NDC Method 'CloneStack' doesn't work as I expected - it doesn't return a copy but a
reference that is manipulated without consent plus the contained items are classes internal to log4net
that don't have a sensible ToString so you can't do diddly with them. This is why I had to pop and re-push the
ndc to copy it in NdcException. Doesn't FxCop warn about this?
anyway... on to the ugliness, believe it or not, it is functionally the same example Ron gave before:
using System;
using System.Collections.Generic;
using System.Text;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Layout;
namespace ConsoleApplication
{
public delegate void ExecuteInContext(params object[] p);
// execute code passed as an anonymous delegate with context safe exceptions
public abstract class Context
{
public static void Execute(string context, ExecuteInContext call, params object[] p)
{
using (NDC.Push(context))
{
try
{
call(p);
}
catch (Exception e)
{
if (!(e is NdcException)) throw new NdcException(e);
else throw;
}
}
}
}
// Wrap a normal exception and append the ndc stack - requires more effort than it should
public class NdcException : Exception
{
public NdcException(Exception e)
: base(e.Message, e)
{
Stack<string> stack = new Stack<string>();
StringBuilder sb = new StringBuilder("Exception : ");
while (NDC.Depth > 0)
{
stack.Push(NDC.Pop());
}
while (stack.Count > 0)
{
string s = stack.Pop();
sb.AppendFormat("{0} ", s);
NDC.Push(s);
}
Ndc = sb.ToString();
}
public string Ndc;
}
class Program
{
static ConsoleAppender consoleAppender;
static ILog log;
static void Main(string[] args)
{
consoleAppender = new ConsoleAppender();
consoleAppender.Layout = new PatternLayout("MESSAGE [%message] NDC [%ndc]%newline");
BasicConfigurator.Configure(consoleAppender);
log = LogManager.GetLogger(typeof(Program));
int[] oddNumbers = new int[5] { 1, 3, 5, 7, 9 };
int[] evenNumbers = new int[5] { 2, 4, 6, 8, 10 };
try
{
foreach (int oddNumber in oddNumbers)
{
Context.Execute(
"oddNumber: " + oddNumber,
delegate(object[] p) {EachEven((int) p[0], (int[]) p[1]);},
oddNumber, evenNumbers);
}
}
// Provide a contextual exception message
catch (NdcException ex)
{
using (NDC.Push(ex.Ndc))
{
log.Fatal(ex.Message);
}
}
catch (Exception ex)
{
log.Fatal("Should never happen: " + ex.Message, ex);
}
}
public static void EachEven(int oddNumber, int[] evenNumbers)
{
foreach (int evenNumber in evenNumbers)
{
Context.Execute(
"evenNumber: " + evenNumber,
delegate(object[] p) {TestOdd((int) p[0], (int) p[1]);},
oddNumber, evenNumber);
}
}
public static void TestOdd(int oddNumber, int evenNumber)
{
if (oddNumber == 7)
{
throw new ApplicationException("Oh no!");
}
log.DebugFormat("Sum: {0}", oddNumber + evenNumber);
}
}
}
Thanks,
Duncan