Intercept SQL statements containing parameter values generated by NHibernate - c#

I use a simple interceptor to intercept the sql string that nhibernate generates for loging purposes and it works fine.
public class SessionManagerSQLInterceptor : EmptyInterceptor, IInterceptor
{
NHibernate.SqlCommand.SqlString IInterceptor.OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
{
NHSessionManager.Instance.NHibernateSQL = sql.ToString();
return sql;
}
}
This however captures the sql statement without the parameter values.. They are replaced by '?'
Eg: .... WHERE USER0_.USERNAME = ?
The only alternative approach i found so far is using log4nets nhibernate.sql appender which logs sql statement including parameter values but that is not serving me well..
I need to use an interceptor so that for eg. when i catch an exception i want to log the specific sql statement that caused the persistence problem including the values it contained and log it mail it etc. This speeds up debuging great deal compared to going into log files looking for the query that caused the problem..
How can i get full sql statements including parameter values that nhibernate generate on runtime?

Here is (roughly sketched) what I did:
Create a custom implementation of the IDbCommand interface, which internally delegates all to the real work to SqlCommand (assume it is called LoggingDbCommand for the purpose of discussion).
Create a derived class of the NHibernate class SqlClientDriver. It should look something like this:
public class LoggingSqlClientDriver : SqlClientDriver
{
public override IDbCommand CreateCommand()
{
return new LoggingDbCommand(base.CreateCommand());
}
}
Register your Client Driver in the NHibernate Configuration (see NHibernate docs for details).
Mind you, I did all this for NHibernate 1.1.2 so there might be some changes required for newer versions. But I guess the idea itself will still be working.
OK, the real meat will be in your implementation of LoggingDbCommand. I will only draft you some example method implementations, but I guess you'll get the picture and can do likewise for the other Execute*() methods.:
public int ExecuteNonQuery()
{
try
{
// m_command holds the inner, true, SqlCommand object.
return m_command.ExecuteNonQuery();
}
catch
{
LogCommand();
throw; // pass exception on!
}
}
The guts are, of course, in the LogCommand() method, in which you have "full access" to all the details of the executed command:
The command text (with the parameter placeholders in it like specified) through m_command.CommandText
The parameters and their values through to the m_command.Parameters collection
What is left to do (I've done it but can't post due to contracts - lame but true, sorry) is to assemble that information into a proper SQL-string (hint: don't bother replacing the parameters in the command text, just list them underneath like NHibernate's own logger does).
Sidebar: You might want to refrain from even attempting to log if the the exception is something considered fatal (AccessViolationException, OOM, etc.) to make sure you don't make things worse by trying to log in the face of something already pretty catastrophic.
Example:
try
{
// ... same as above ...
}
catch (Exception ex)
{
if (!(ex is OutOfMemoryException || ex is AccessViolationException || /* others */)
LogCommand();
throw; // rethrow! original exception.
}

It is simpler (and works for all NH versions) to do this :
public class LoggingSqlClientDriver : SqlClientDriver
{
public override IDbCommand GenerateCommand(CommandType type, NHibernate.SqlCommand.SqlString sqlString, NHibernate.SqlTypes.SqlType[] parameterTypes)
{
SqlCommand command = (SqlCommand)base.GenerateCommand(type, sqlString, parameterTypes);
LogCommand(command);
return command;
}}

Just an idea: can you implement a new log4net-appender, which takes all sqlstatements(debug with parameter) and holds the last one. When an error occured you can ask him for the last sqlstatement and email it.

Implement custom ILoggerFactory and filter in LoggerFor for keyName equals NHibernate.SQL and set via LoggerProvider.SetLoggersFactory. Works for SQLite driver, should work for other.
LoggerProvider by default creates log4net via reflection if log4net assembly presented. So best implementation would be if custom ILoggerFactory will delegate log into default, until NHibernate.SQL log requested.

Related

Acumatica - Another process has added/updated record - Creating Inventory Items with DAC and Graph Extensions

I am trying to create a process that will run daily that will import records from another database as Inventory Items. In order to do this, I need to create an extension of the InventoryItemMaint graph (to give me my custom action), as well as an extension of the InventoryItem DAC (to give me a custom field). I have tried to follow the guidelines laid out specifically in the T-300 manual to do this.
Here is the code for my InventoryItemMaint extension:
namespace PX.Objects.IN
{
public class InventoryItemMaint_Extension:PXGraphExtension<InventoryItemMaint>
{
public PXAction<PX.Objects.IN.InventoryItem> DailyOnixImport;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Daily Onix Import")]
protected void dailyOnixImport()
{
var invItemMaintExtInstance = Base.GetExtension<InventoryItemMaint_Extension>();
string todaysDate = DateTime.Today.ToString("MM/dd/yyyy");
foreach (STOnixItem currentOnixItem in PXSelect<STOnixItem,
Where<STOnixItem.addedDate, Equal<Required<STOnixItem.addedDate>>>>
.Select(this.Base, todaysDate))
{
InventoryItem currentInventoryItem = invItemMaintExtInstance.Base.Item.Current;
PXCache inventoryItemCache = invItemMaintExtInstance.Base.Item.Cache;
InventoryItemExt inventoryItemExtension = inventoryItemCache.GetExtension<InventoryItemExt>(currentInventoryItem);
inventoryItemCache.Clear();
currentInventoryItem.InventoryCD = currentOnixItem.ISBN13;
currentInventoryItem.Descr = currentOnixItem.Title;
currentInventoryItem.ItemClassID = currentOnixItem.ItemClass;
currentInventoryItem.RecPrice = decimal.Parse(currentOnixItem.MSRP);
currentInventoryItem.BasePrice = decimal.Parse(currentOnixItem.DefaultPrice);
currentInventoryItem.BaseItemWeight = decimal.Parse(currentOnixItem.Weight);
currentInventoryItem.WeightUOM = "POUND";
currentInventoryItem.ImageUrl = currentOnixItem.ImageLink;
//Assigning to the custom DAC Extension
inventoryItemExtension.UsrFromOnixFile = currentOnixItem.FromFile;
inventoryItemCache.Update(currentInventoryItem);
Base.Actions.PressSave();
}
}
}
}
I am currently getting an error that reads:
Error: Another process has updated the 'InventoryItem' record. Your
changes will be lost.
And here is the error trace text:
9/20/2018 3:26:05 PM Error: Error: Another process has added the
'InventoryItem' record. Your changes will be lost.
at PX.Data.PXCache1.PersistInserted(Object row) at
PX.Data.PXCache1.Persist(PXDBOperation operation) at
PX.Data.PXGraph.Persist(Type cacheType, PXDBOperation operation)
at PX.Data.PXGraph.Persist() at
PX.Objects.IN.InventoryItemMaint.Persist() at
PX.Data.PXSave1.d__2.MoveNext() at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXActionCollection.PressSave(PXAction caller) at
PX.Objects.IN.InventoryItemMaint_Extension.dailyOnixImport() at
PX.Data.PXAction1.<>c__DisplayClass3_0.<.ctor>b__0(PXAdapter adapter)
at PX.Data.PXAction1.a(PXAdapter A_0) at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXAction`1.d__31.MoveNext() at
PX.Web.UI.PXBaseDataSource.tryExecutePendingCommand(String viewName,
String[] sortcolumns, Boolean[] descendings, Object[] searches,
Object[] parameters, PXFilterRow[] filters, DataSourceSelectArguments
arguments, Boolean& closeWindowRequired, Int32& adapterStartRow,
Int32& adapterTotalRows) at
PX.Web.UI.PXBaseDataSource.ExecuteSelect(String viewName,
DataSourceSelectArguments arguments, PXDSSelectArguments pxarguments)
I have done a lot of searching around StackOverflow and other places, but haven't found any answers that seem to address my issue exactly. Tweaks I've made have resulted in other errors like variations on what I'm getting now (another process added vs another process updated) and MoveNext errors.
If anyone is able to help me out, I would be very appreciative.
guys,
I faced the same exception in the EmployeeMaint when we tried to update one of the complex fields in the DAC.
baseAction() throws this exception even though no code runs before saving.
I followed your suggestions #Hugues Beauséjour and found out that reason may be:
Means, the cache is dirty.
So, to resolve my issue I just needed to clear the cache for the object that throws the exception.
IN my case the exception was:
Update employee class error - Another process has added the 'VendorPaymentMethodDetail' record. Your changes will be lost
So I needed to clear the cache for VendorPaymentMethodDetail:
Base.Caches<VendorPaymentMethodDetail>().Clear();
Be careful to clear the cache in the case you need to read from the cache during your code after you clear the cache. In that case, you need to copy objects from the cache before cleaning and used this copy afterwards.
Hope it will be helpful to someone.
There seem to be a logical flaw in the code. You are updating the same current object in a loop. This serve no purpose as it will always overwrite with the last item returned by the loop. Invoking the Save action in a loop can also lead to errors if you're not careful.
As I mentioned in the comment, clearing the cache seems wrong. You want to keep the current data there. When you call clear you're invalidating the main document of the graph, that will lead to errors.
Changing fields closely tied to the key like InventoryCD can lead to the document being cleared and invalidated. If you have to modify key fields considering inserting new record instead of updating the current one.
There are other changes I would recommend.
Code:
// Consider replacing the default namespace to avoid conflicts
namespace MyNamespace
{
public class InventoryItemMaint_Extension:PXGraphExtension<InventoryItemMaint>
{
public PXAction<PX.Objects.IN.InventoryItem> DailyOnixImport;
// '(CommitChanges = true)' is not necessary
[PXButton]
[PXUIField(DisplayName = "Daily Onix Import")]
protected void dailyOnixImport()
{
InventoryItemMaint_Extension invItemMaintExtInstance = Base.GetExtension<InventoryItemMaint_Extension>();
string todaysDate = DateTime.Today.ToString("MM/dd/yyyy");
// You need to rethink that 'foreach' logic
STOnixItem currentOnixItem in PXSelect<STOnixItem,
Where<STOnixItem.addedDate, Equal<Required<STOnixItem.addedDate>>>>.Select(Base, todaysDate);
// You can access Base directly, no need to fetch it from the extension
InventoryItem currentInventoryItem = Base.Item.Current;
// Consider using more null check
if (currentOnixItem != null && currentInventoryItem != null)
{
// Consider using similar names for similar variables
InventoryItemExt currentInventoryItemExt = currentInventoryItem.GetExtension<InventoryItemExt>();
// Avoid setting key related fields like InventoryCD when updating
currentInventoryItem.Descr = currentOnixItem.Title;
currentInventoryItem.ItemClassID = currentOnixItem.ItemClass;
currentInventoryItem.RecPrice = decimal.Parse(currentOnixItem.MSRP);
currentInventoryItem.BasePrice = decimal.Parse(currentOnixItem.DefaultPrice);
currentInventoryItem.BaseItemWeight = decimal.Parse(currentOnixItem.Weight);
currentInventoryItem.WeightUOM = "POUND";
currentInventoryItem.ImageUrl = currentOnixItem.ImageLink;
currentInventoryItemExt.UsrFromOnixFile = currentOnixItem.FromFile;
// You fetched the item from the DataView
// you can update it in the DataView too.
Base.Item.Update(currentInventoryItem);
// Is it really needed to save here?
// This coupled with cache clearing and the loop updating
// the same record triggers the error in your question.
Base.Actions.PressSave();
}
}
}
}
I found another reason that can cause the same exception:
That may happen when you select from the same table in different caches, use one for select and another for insert. for instance:
we have a view:
public PXSelect<MPEmployeeWorkSchedule, Where<MPEmployeeWorkSchedule.employeeID, Equal<Current<MPEmployeeTermination.employeeID>>>> EmployeeWorkSchedule;
and in event we have code's segment:
we use the same DAC as in the view above and then we insert into the view:
EmployeeWorkSchedule.Cache.Insert(workSchedule);
Afterwards Persist() throws exception.
Solution was again to create another view instead of query and clear the cache:
This may be helpful, I hope.
Want to share one more way of finding the same error message. In my case the problem was in structure of database.
In order to fix the error message, I've did those steps:
dropped my table
generated sql script from existing table
deleted not needed columns
copy/pasted existing columns, with replacing names
As outcome, I've got error message to disappear.

Fortify cleanse rule doesn't cleanse everything

My team has recently started using Fortify Static Code Analyzer (version 17.10.0 156) on our .NET code base (C# 6 and VB.NET), and are experiencing some pain with the amount of false positives it reports. For any given issue we can't know if it is a false positive without looking at it, and we don't want any actual problems to get lost in the clutter.
We have a utilities library with a method ReadEmbeddedSql which extracts sql from resources embedded in the assembly to execute. Fortify flags any OracleCommand (from Oracle.ManagedDataAccess.Client) which execute the sql returned from this method with a Sql Injection vulnerability.
This vulnerability is reported at the point the sql is set on the command, be it via constructor, or via the CommandText property.
It does not do this if the ReadEmbeddedSql method is defined in the local assembly.
A pared down listing of the source code which produces this result follows below. In the example code, ExecuteSomeSql() and ExecuteSomeSqlDifferently() are flagged with a vulnerability where ExecuteSomeLocalSql() is not.
For Analysis Evidence it only lists the line the OracleCommand is created:
TestDao.cs:27 - OracleCommand()
RuleID: 31D4607A-A3FF-447C-908A-CA2BBE4CE4B7
in the details for the issue it provides:
On line 27 of TestDao.cs, the method ExecuteSomeSql() invokes a SQL query
built using input coming from an untrusted source. This call could
allow an attacker to modify the statement's meaning or to execute
arbitrary SQL commands.
A sample diagram presented by Fortify for this issue:
After much searching, I came across this post describing a similar problem and proposed solution: Can I register a trusted source for SQL statements
After following the instructions there, and verifying the instructions independently in a different version of the user guide (page 90)
The result is unchanged. I added an additional 'SQL Injection Validation Rule' rule which is specifically described as "... identifies a function that properly validates data before using them in a SQL query."
Still no avail.
EDIT:
I have played around with custom rules more, and have been able to determine that the CustomCleanseRules are actually being applied (they do remove other types of taint), but not removing some trust specific flag Fortify applies to our in-house library.
Every value returned from any method of my libraries is distrusted, and none of the rules I've created seem to be able to remove this distrust.
Is there something I am doing wrong, or does Fortify just not work?
Is there a different type of rule needed to cleanse this general distrust?
Example Source code:
In library:
namespace Our.Utilities.Database
{
public abstract class BaseDao
{
protected string ReadEmbeddedSql(string key)
{
//... extract sql from assembly
return sql;
}
}
}
In application:
namespace Our.Application.DataAccess
{
public class TestDao: Our.Utilities.Database.BaseDao
{
public void ExecuteSomeSql()
{
//... connection is created
// Fortify Does not trust sqlText returned from library method.
var sqlText = ReadEmbeddedSql("sql.for.ExecuteSomeSql");
using(var someSqlCommand = new OracleCommand(sqlText, connection)) // Fortify flags creation of OracleCommand as SqlInjection vulnerability.
{
someSqlCommand.ExecuteNonQuery();
}
}
public void ExecuteSomeSqlDifferently()
{
//... connection is created
// Fortify Does not trust sqlText returned from library method.
var sqlText = ReadEmbeddedSql("sql.for.ExecuteSomeSql");
using(var someSqlCommand = connection.CreateCommand())
{
someSqlCommand.CommandText = sqlText; //Fortify flags setting CommandText as SqlInjection vulnerability.
someSqlCommand.ExecuteNonQuery();
}
}
public void ExecuteSomeLocalSql()
{
//... connection is created
var sqlText = ReadEmbeddedSqlLocallyDefined("sql.for.ExecuteSomeSql");
using(var someSqlCommand = new OracleCommand(sqlText, connection))
{
someSqlCommand.ExecuteNonQuery();
}
}
protected string ReadEmbeddedSqlLocallyDefined(string key)
{
//... extract sql from assembly
return sql;
}
}
}
XML of custom rules:
<?xml version="1.0" encoding="UTF-8"?>
<RulePack xmlns="xmlns://www.fortifysoftware.com/schema/rules">
<RulePackID>5A78FC44-4EEB-49C7-91DA-6564805C3F23</RulePackID>
<SKU>SKU-C:\local\path\to\custom\rules\Our-Utilities</SKU>
<Name><![CDATA[C:\local\path\to\custom\rules\Our-Utilities]]></Name>
<Version>1.0</Version>
<Description><![CDATA[]]></Description>
<Rules version="17.10">
<RuleDefinitions>
<DataflowCleanseRule formatVersion="17.10" language="dotnet">
<RuleID>7C49FEDA-AA67-490D-8820-684F3BDD58B7</RuleID>
<FunctionIdentifier>
<NamespaceName>
<Pattern>Our.Utilities.Database</Pattern>
</NamespaceName>
<ClassName>
<Pattern>BaseDao</Pattern>
</ClassName>
<FunctionName>
<Pattern>ReadSqlTemplate</Pattern>
</FunctionName>
<ApplyTo implements="true" overrides="true" extends="true"/>
</FunctionIdentifier>
<OutArguments>return</OutArguments>
</DataflowCleanseRule>
<DataflowCleanseRule formatVersion="17.10" language="dotnet">
<RuleID>14C423ED-5A51-4BA1-BAE1-075E566BE58D</RuleID>
<TaintFlags>+VALIDATED_SQL_INJECTION</TaintFlags>
<FunctionIdentifier>
<NamespaceName>
<Pattern>Our.Utilities.Database</Pattern>
</NamespaceName>
<ClassName>
<Pattern>BaseDao</Pattern>
</ClassName>
<FunctionName>
<Pattern>ReadSqlTemplate</Pattern>
</FunctionName>
<ApplyTo implements="true" overrides="true" extends="true"/>
</FunctionIdentifier>
<OutArguments>return</OutArguments>
</DataflowCleanseRule>
</RuleDefinitions>
</Rules>
</RulePack>
When I run your sample code (I do have to modify it since it will not compile as is). When I run it with SCA 17.10 with 2017Q3 (I also did this on 2017Q2) I did not get a the same SQL Injection Rule ID as you.
Looking at your Analysis Evidence, I assume that the analyzer that found this was not Dataflow or Control flow but maybe the Semantic or Structural?
You can see the type of analyzer that found the finding by looking at the summary tab:
Either way, I don't think a Custom Rule is what I would do here.
An option you can do is to use a Filter file.
This a file that can contain
RuleIDs
InstanceIDs
Categories
When this file is passed into the scan command, any finding that matches any of the fields in the filter file will be filtered out from the results.
You can find examples of using the filter file in <SCA Install Dir>\Samples\Advanced\filter
or you can look in the Fortify SCA Users Guide in Appendix B: Filtering the Analysis
*Note: Your analysis of using filters (in comment) are spot on.

How do you actually implement the Search Contract? (C#)

I'm having some trouble understanding and getting the search contract to work in my Store app. I have been unable to find any sort of documentation or guide that explains the structure of using the contract. (I've looked at the quickstarts on MSDN, the Search Contract sample and the build video, but that only really deals with javascript)
So far I've been able to run a query and get a list (of Custom Objects) into my search contract page, and from there I try to assign that to defaultviewmodel.results, but no matter what query I type nothing shows up I on the page. Is there something else I need to set?
What I have so far is as follows (excerpts):
App.xaml.cs
protected override void OnSearchActivated(Windows.ApplicationModel.Activation.SearchActivatedEventArgs args)
{
SearchCharmResultsPage.Activate(args.QueryText, args.PreviousExecutionState);
SearchCharmResultsPage.ProcessSearchQuery(args.QueryText);
}
public async static void ProcessSearchQuery(string queryString)
{
try
{
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync("recipeCustomObject Debug.WriteLine("Database exists, connecting");
SQLiteAsyncConnection connection = new SQLiteAsyncConnection("CustomObject_db");
List<CustomObject> resultsList = new List<CustomObject>();
string query = "SELECT * FROM CustomObjectDB";
resultsList = await connection.QueryAsync<RecipeRecord>(query);
}
catch (FileNotFoundException fnfExc)
{
Debug.WriteLine("FNFEXC: " + fnfExc.ToString());
}
}
I think it is possible that here lies the problem, though I'm not sure if it is, or how to change it.
the resultsList list is created here, but because the method it asynchronous, I can't return from the method. Because of this I'm guess that when I try to assign this.DefaultViewModel[Results] = resultsList; in the LoadStateMethod, the object doesn't exist (thought the program throws no error). When I try to add the same line in the ProcessSearchQuery method, i'm told that this is not valid in a static method, but I think I need the method to be static? My problem might just be a fundamental logic error?
Finally got it! found the solution here: http://jeffblankenburg.com/2012/11/06/31-days-of-windows-8-day-6-search-contract
For those looking for an answer in the future, the key is to make sure you have your search logic within the Filter_SelectionChanged method, which was something I wasn't doing. Look at the guide within the above link to get an idea of the structure.
Have you looked at the Search contract sample on the developer center? There's a C#/XAML version there as well.
My open source Win8 RSS Reader framework implements Search (and Share) have a look at the source and if you still got questions, I'll be happy to help http://win8rssreader.codeplex.com/

IDataRecord.IsDBNull causes an System.OverflowException (Arithmetic Overflow)

I have a OdbcDataReader that gets data from a database and returns a set of records.
The code that executes the query looks as follows:
OdbcDataReader reader = command.ExecuteReader();
while (reader.Read())
{
yield return reader.AsMovexProduct();
}
The method returns an IEnumerable of a custom type (MovexProduct). The convertion from an IDataRecord to my custom type MovexProduct happens in an extension-method that looks like this (abbrev.):
public static MovexProduct AsMovexProduct(this IDataRecord record)
{
var movexProduct = new MovexProduct
{
ItemNumber = record.GetString(0).Trim(),
Name = record.GetString(1).Trim(),
Category = record.GetString(2).Trim(),
ItemType = record.GetString(3).Trim()
};
if (!record.IsDBNull(4))
movexProduct.Status1 = int.Parse(record.GetString(4).Trim());
// Additional properties with IsDBNull checks follow here.
return movexProduct;
}
As soon as I hit the if (!record.IsDBNull(4)) I get an OverflowException with the exception message "Arithmetic operation resulted in an overflow."
StackTrace:
System.OverflowException was unhandled by user code
Message=Arithmetic operation resulted in an overflow.
Source=System.Data
StackTrace:
at System.Data.Odbc.OdbcDataReader.GetSqlType(Int32 i)
at System.Data.Odbc.OdbcDataReader.GetValue(Int32 i)
at System.Data.Odbc.OdbcDataReader.IsDBNull(Int32 i)
at JulaAil.DataService.Movex.Data.ExtensionMethods.AsMovexProduct(IDataRecord record) [...]
I've never encountered this problem before and I cannot figure out why I get it. I have verified that the record exists and that it contains data and that the indexes I provide are correct. I should also mention that I get the same exception if I change the if-statemnt to this: if (record.GetString(4) != null). What does work is encapsulating the property-assignment in a try {} catch (NullReferenceException) {} block - but that can lead to performance-loss (can it not?).
I am running the x64 version of Visual Studio and I'm using a 64-bit odbc driver.
Has anyone else come across this? Any suggestions as to how I could solve / get around this issue?
Many thanks!
For any one experiencing the same issue, the way I solved this was to switch from the Odbc* classes to their OleDb* counterparts. This of course demands that your data driver has support for OleDb connections.
Which DB are you trying to talk to? If it uses some "private" column types that can cause problems. That doesn't apply to SQL Server of course :-)
Also check that you are compiling and running as x64 (Process Explorer will show you thsi and even plain tack manager shows it). devenv.exe will still be x86 but your actual binary should run as x64. The reason I mention is that crossing 32/64 bit boundary is notorious for breaking 3rd party ODBC drivers.

Exception.Data info is missing in EntLib log

How do I get the Exception Handling Application Block (EHAB) to write the values from the Exception.Data property in the log?
try
{
// ...
}
catch (Exception ex)
{
ex.Data.Add("Hello", "World");
throw ex;
}
The exception is logged correctly, but I can’t find the added data anywhere in the log entry created by EHAB.
As far as I understand, it’s a recommended practice to add additional relevant information to the exception itself like in the above example. That’s why I’m a little surprised that EHAB doesn’t include this by default.
Can I fix this by modifying the template with the EntLib Text Formatter Template Editor (screen shot below)? I can't find any info on the various "tokens" provided, but I assume the answer is hidden somewhere with them.
Text Formatter Template Editor http://img195.imageshack.us/img195/6614/capturegmg.png
Or do I really need to implement my own custom Text Formatter to accomplish this?
EDIT/UPDATE:
I do this in my Global.asax.cs in order to avoid having to add the HandleException method call everywhere in my code:
using EntLib = Microsoft.Practices.EnterpriseLibrary;
using System;
namespace MyApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Error(object sender, EventArgs e)
{
// I have an "All Exceptions" policy in place...
EntLib.ExceptionHandling.ExceptionPolicy.HandleException(Server.GetLastError(), "All Exceptions");
// I believe it's the GetLastError that's somehow returning a "lessor" exception
}
}
}
It turns out this is not the same as this (which works fine, and essentially solves my problem):
try
{
// ...
}
catch (Exception ex)
{
ex.Data.Add("Hello", "World");
bool rethrow = ExceptionPolicy.HandleException(ex, "Log Only Policy");
throw ex;
}
Going through all the code and adding try-catch's with a HandleException call just seems ... well, stupid.
I guess my problem really is how to use EHAB correctly, not configuration.
Any suggestion on how I can properly log all exceptions on a "global level" in an ASP.NET web application??
You shouldn't have to create your own formatter.
Each item in the Data IDictionary on the Exception object is added to the ExtendedProperties Dictionary of the LogEntry object and is logged (if specified by the formatter).
The Extended Properties:{dictionary({key} - {value})} config snippet should log all key/value pairs in the extended properties dictionary. If you want to log just one item from the collection you can use "keyvalue". In your example it would be something along the lines of:
Hello Key: {keyvalue(Hello)}
I modified the ExceptionHandlingWithLoggingQuickStart to add Data.Add("Hello", "World") and I see "Extended Properties: Hello - World" at the end of the Event Log entry. So it is working.
If you aren't seeing that behavior, you need to ensure:
That your exception with the Data items added is passed in to ExceptionPolicy.HandleException
That the policy specified in the HandleException method is configured for logging with a LoggingExceptionHandler in the configuration
That the formatter that is specified in the LoggingConfiguration is configured to support ExtendedProperties
If you can't see what the problem is try comparing what you have with what is in the QuickStart. If it's still not working, post your code and your config.
UPDATE:
If you are going to handle Application_Error in the global.asax then you will be receiving an HttpUnhandledException since the page did not handle the error. You can retrieve the Exception you are interested in by using the InnerException Property; the InnerException will contain the original Exception (unless it is in that exception's InnerException :) ). Alternately, you can use Server.GetLastError().GetBaseException() to retrieve the exception which is the root cause.

Categories

Resources