I use a simple interceptor to intercept the sql string that nhibernate generates for loging purposes and it works fine.
public class SessionManagerSQLInterce
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:
m_command.CommandText
m_command.Parameters
collectionWhat 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.
}
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.
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;
}}
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.