Logging custom exceptions to a database

Topics: Exception Handling Application Block, Logging Application Block
Feb 1, 2011 at 3:59 PM

I am looking for an example/tutorial on how to create a custom exception handler with Enterprise Library 5 that will log to an existing database. The standard version that is part of the Developer Guide walkthrough has some fields that we need for our existing database, but with different column names. The standard also seems to be missing useful info found in the Event Log logger, such as stack trace (stack trace is an existing column in our log database).

Feb 1, 2011 at 4:16 PM

The recently released (in beta) Enterprise Library Extensibility Labs includes a set of exercises on how to build a custom exception handler.

It doesn't build the handler you're asking for, but it's got all the required Entlib pieces for any custom handler. You should be able to extrapolate from the example to your specific scenario.

 

Feb 7, 2011 at 6:19 PM
Edited Feb 7, 2011 at 6:25 PM

The lab is interesting, but I am finding the documentation very confusing. I am trying to get through exercise two, but getting hopelessly lost in the implementation of double properties, parsing, matching, range converters, etc. in section 7. I tried running it in a debugger, but even that is hard to follow. My sense is that something that appears to be quite simple and trivial is being made unnecessarily complex and difficult.

Feb 8, 2011 at 6:17 PM

I'm sorry you are having trouble with the lab. If you have specific suggestions on how to improve the instructions, they would be very helpful.

Feb 22, 2011 at 4:38 PM
Edited Feb 22, 2011 at 5:05 PM

I am new to EntLib in general and currently beginning Excercise 2 of the Custom Exception Handler lab. The concept is rather straightforward and easy enough to follow in the SqlExceptionTranslatorHandler, however it is not always clear, for example, how a particular constructor is being being determined at run-time or where their parameters are coming from.

Here is what I am seeing:

1. HandlerFiresAndTranslatesSqlException Test
   1.1. Fires the "public SqlExceptionTranslatorHandler(NameValueCollection attributes)" constructor and calls, for instance, "private static NumericRange GetRange(NameValueCollection attributes)".
   1.2. This method, in turn, calls "return (NumericRange) typeConverter.ConvertFromString(attributes["errorRange"]);", passing "errorRange"/"50000" as the key/value pair, which was constructed in the TestFixture.
   1.3. However, stepping through the debugger, I find that the method being called in step 1.2 is NOT the one that is resolved, but, rather "public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)". What happened to ConvertFromString(string)? I was unable to step into this method.
   1.4. This confusing method call is calling "RangeSpecParser.Parse" on the value paramater, which is "50000".
   1.5. I am hopelessly lost in RangeSpecParser.Parse... it creates a stream for some reason, calls RangeSpec, lambda's, a Func, etc. and then returns... "50000" for errorNumberRange.
   1.6 HandleException is fired and is straightforward.

2. IfSqlErrorIsNotInRangePolicyDoesNothing Test
   2.1. Executes "var testDb = EnterpriseLibraryContainer.Current.GetInstance<Database>("TestDb");", which directly calls the "public SqlExceptionTranslatorHandler(NumericRange errorNumberRange, Type wrapExceptionType, string exceptionMessage, bool shieldSqlException)" with the same "50000" and wrapExceptionType as in Test 1 above, along with a "Database went boom" exceptionMessage. I have no idea where the paramater data came from or why this constructor was called directly rather than calling "public SqlExceptionTranslatorHandler(NameValueCollection attributes)" as in Test 1.
   2.2. This constructor is called AGAIN, only this time with "{1 - 10}" for errorNumberRange, so it is obvious that another instance of SqlExceptionTranslationHandler is being created. Again, I have NO idea where this data came from or who is insantiating the class with this particular constructor. There is nothing in the app.config file where it could be obtained and I don't see a config file being constructed for it, as it was in Test1 above.
   2.3. "IfSqlErrorIsNotInRangePolicyDoesNothing()" next executes "var ex = CaptureException(em, () => testDb.ExecuteNonQuery(CommandType.Text, "RAISERROR(N'This is my message', 15, 1)"), "Should Not Catch Policy");"
   2.4. CaptureExpression executes "em.Process(action, policyName);"
   2.5. HandleException is called and performs as expected.

Anyway, it looks like a very sophisticated lab, but I am really struggling with the implementation. As I said before, I am finding it very confusing and hard to follow. Even a little more discription on what helpers such as the parsers are doing and why they seem to be overly engineered might be helpful. Also, diagrams such as a sequence diagram, flow chart, etc. might also help to illuminate some of the detail.

.

Feb 23, 2011 at 8:04 AM

BillyM,

In HandlerFiresAndTranslatesSqlExceptionTest, the constructor with the NameValueCollection parameters was used because it was explicitly configured to be called.  Look at the test class's constructor how it creates the config it uses (using Fluent Configuration API).  It calls the GetTestConfig method which has this code

builder.ConfigureExceptionHandling()
                .GivenPolicyWithName("Translate")
                .ForExceptionType<SqlException>()
                .HandleCustom(typeof (SqlExceptionTranslatorHandler),
                    new NameValueCollection
                    {
                        {"errorRange", "50000"},
                        {"wrapExceptionType", typeof (InvalidOperationException).AssemblyQualifiedName}
                    })
                .ThenThrowNewException();

On the other hand, the other constructor was used in IfSqlErrorIsNotInRangePolicyDoesNothing because the instance of ExceptionManager was resolved from the default config (app.config).   In the app.config, the SqlExceptionTranslatorHandler is configured to be used under the two exception policies.  This configuration prompts EntLib to look for the associated configuration element type of the handler which in this case, is the SqlExceptionTranslatorData.  This class is where the actual configuration of the constructor is being done, specifically in the GetRegistrations method. 

 public override IEnumerable<TypeRegistration> GetRegistrations(string namePrefix)
{
            yield return new TypeRegistration<IExceptionHandler>( () => 
                new SqlExceptionTranslatorHandler(ErrorRange, WrapExceptionType, ExceptionMessage, ShieldSqlException))
                {
                    Name = BuildName(namePrefix), Lifetime = TypeRegistrationLifetime.Transient
                };
}

There was a change then in the parameter that was used because in the second call because the "Should Not Catch Policy" exception policy was called.  If you open the config in the xml editor, notice the difference of the errorRange attribute value of the Exception Translator exception handler between the two exception policies.  If you open it in the entlib config tool, you can see those values in the Attributes property collection.

Hope this helps.

 

Sarah Urmeneta
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com