2

Hard to believe, but I can't seem to find a straight answer for this: How can I get the SQL statement including the parameter values when the statement generates an exception and only when it generates an exception. I know how to log the statement+parameters for every SQL generated, but that's way too much. When there's a System.Data.SqlClient.SqlException, though, it only provides the SQL, not the parameter values. How can I catch that at a point where I have access to the that data so that I can log it?

Michael
  • 1,351
  • 1
  • 11
  • 25
  • To help this be more easily found when searching; the exception message contains only question marks for the values, ex: `VALUES (?, ?, ?, ?)`. – Bernhard Hofmann Mar 14 '18 at 14:00
  • @BernhardHofmann: Thanks. Do I understand correctly that you added your comment so that others could more-easily find this post? If so, much appreciated. – Michael Mar 15 '18 at 15:14
  • If you get hold of DbCommand somehow, you can build the query with parameters as mentioned in [this](https://stackoverflow.com/q/44194760/5779732) question. – Amit Joshi Jan 11 '19 at 15:54

2 Answers2

3

Based on a number of responses to various questions (not just mine), I've cobbled something together that does the trick. I think it could be useful to others as well, so I'm including a good deal of it here:

The basic idea is to

  1. Have NH log all queries, pretty-printed and with the parameter values in situ
  2. Throw all those logs out except the one just prior to the exception.

I use Log4Net, and the setup is like this:

<?xml version="1.0"?>

<log4net>
  <appender name="RockAndRoll" type="Util.PrettySqlRollingFileAppender, Util">
    <file type="log4net.Util.PatternString" >
      <conversionPattern value="%env{Temp}\\%property{LogDir}\\MyApp.log" />
    </file>
    <DatePattern value="MM-dd-yyyy" />
    <appendToFile value="true" />
    <immediateFlush value="true" />
    <rollingStyle value="Composite" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="100MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%date %-5level %logger - %message%newline" />
    </layout>
  </appender>

  <appender name="ErrorBufferingAppender" type="log4net.Appender.BufferingForwardingAppender">
    <bufferSize value="2" />
    <lossy value="true" />
    <evaluator type="log4net.Core.LevelEvaluator">
      <threshold value="ERROR" />
    </evaluator>
    <appender-ref ref="RockAndRoll" />
    <Fix value="0" />
  </appender>

  <logger name="NHibernate.SQL">
    <additivity>false</additivity>
    <appender-ref ref="ErrorBufferingAppender" />
    <level value="debug" />
  </logger>

  <logger name="error-buffer">
    <additivity>false</additivity>
    <appender-ref ref="ErrorBufferingAppender" />
    <level value="debug" />
  </logger>

  <root>
    <level value="info" />
    <appender-ref ref="RockAndRoll" />
  </root>

</log4net>

The NHibernate.SQL logger logs all queries to the ErrorBufferingAppender, which keeps throwing them out and saves only the last one in its buffer. When I catch an exception I log one line at ERROR level to logger error-buffer, which passes it to ErrorBufferingAppender which -- because it's at ERROR level -- pushes it, along with the last query, out to RockAndRoll, my RollingFileAppender.

I implemented a subclass of RollingFileAppender called PrettySqlRollingFileAppender (which I'm happy to provide if anyone's interested) that takes the parameters from the end of the query and substitutes them inside the query itself, making it much more readable.

Michael
  • 1,351
  • 1
  • 11
  • 25
1

If you are using for querying the db (as the tag presence on your question suggests), and your SQL dialect/driver relies on ADO, you should get a GenericADOException from the failing query.

Its Message property normally already include parameters values.

For example, executing the following failing query (provided you have at least one row in DB):

var result = session.Query<Entity>()
    .Where(e => e.Name.Length / 0 == 1);

Yields a GenericADOException with message:

could not execute query  
[ select entity0_.Id as Id1_0_, entity0_.Name as Name2_0_ from Entity entity0_ where len(entity0_.Name)/@p0=@p1 ]  
  Name:p1 - Value:0  Name:p2 - Value:1

The two literals, 0 and 1, of the query have been parameterized and their values are included in the message (with an index base mismatch: on hibernate queries, they are 1 based, while on the SQL query with my setup, they end up 0 based).

So there is nothing special to do to have them. Just log the exception message.

Have you just missed it, or were you asking something else indeed?
Your question was not explicit enough in my opinion. You should include a MVCE. It would have show me more precisely in which case you were not able of getting those parameters values.

Community
  • 1
  • 1
Frédéric
  • 9,364
  • 3
  • 62
  • 112
  • First, thanks for responding. Next, yes I'm using NH. Finally, in this particular instance I'm getting a System.Data.SqlClient.SqlException, printed by the NHibernate.Util.ADOExceptionReporter and it doesn't have the parameter values. – Michael Mar 21 '17 at 05:53
  • Ok, so, your application does not handle and log exception itself it looks, and relies on NHibernate logs solely. Then activate the `Debug` log level for the `ADOExceptionReporter`, and you should have those parameters. – Frédéric Mar 21 '17 at 07:52
  • Thanks again. I set the logger for ADOExceptionReporter to DEBUG level, but it still doesn't put out the values. – Michael Mar 22 '17 at 10:35
  • I cannot help you more without a [MVCE](http://stackoverflow.com/help/mcve). I do not have your issue with my own cases. – Frédéric Mar 22 '17 at 11:01
  • I'll see if I can put something together. – Michael Mar 23 '17 at 15:32
  • @Frédéric I have a somewhat inverse question: is it possible to suppress the inclusion of parameter values in such exceptions? Asked as a new question [here](https://stackoverflow.com/q/66554985/639520) – E-Riz Mar 09 '21 at 21:27