[NIFI-6498] Set error event listener on both the TransformerFactory a…#6798
[NIFI-6498] Set error event listener on both the TransformerFactory a…#6798dan-s1 wants to merge 6 commits intoapache:mainfrom
Conversation
…nd Transformer to enable logging errors when processing transformation instructions and when running the transformation itself. Also added ability to log errors from messages that are emitted with <xsl:message/>.
exceptionfactory
left a comment
There was a problem hiding this comment.
Thanks for putting together this improvement @dan-s1. The ErrorListener implementation looks straightforward as an implementation of the standard JAXP interface.
I'm not sure the Saxon-specific XMLEmitter implementation should be included. The code introduces tighter coupling to the Saxon library Although the xsl:message element is a standard feature, it is of questionable value in the context of a NiFi Processor. I would prefer to scope these changes to the ErrorListener implementation, which would satisfy the Jira issue description.
|
@exceptionfactory Thank you for taking the time and reviewing this PR. I would just like to clarify what you would like me to remove. I understand you would like me to remove method setMessageReceiver which has the Saxon specific code. Would you like me also to remove unit tests testMessageTerminate and testMessageNonTerminate also or should I keep them as they still demonstrate behavior that TransformXml exhibits when xsl:message is used with terminate and without terminate? |
|
Thanks for the reply @dan-s1. Although the Reviewing the Saxon Configuration Features there is a reference to a message emitter class named |
|
@exceptionfactory They actually do assert behavior when terminate is used whether the transform succeeds (terminate=no) or fails (terminate=yes). In addition I noticed with setting the ErrorListener on the Transformer it seems to redirect the xsl:message messages to the log when there is an error (terminate is yes ) and not the nifi-bootstrap.log but when there is only a warning (i.e. terminate is no) then the message is written out to the bootstrap.log. I had not noticed that until I removed the custom Saxon code. |
|
Thanks @dan-s1, I was expecting something related to the message output, but on further review, I see the success and failure output scenarios, which seem worth maintaining. |
|
@exceptionfactory So i ended up only removing the method setMessageReceiver. With that, the only issue which remains is when there is a warning from xsl:message for some reason its not redirected to use the ErrorListener configured. I did find the following Saxon issue which details a similar issue but not the exact one where it gives a lot of history behind Saxon's implementation on handling xsl:message. It does seem to indicate though the only path for correctly redirecting messages from xsl:message is to use custom Saxon code with the use of MessageWarner as you had mentioned previously. |
exceptionfactory
left a comment
There was a problem hiding this comment.
Thanks for the updates @dan-s1, the approach looks good. I noted several minor adjustments to include the exception in the log arguments, and removing some unnecessary lines. Otherwise this looks close to completion.
| } | ||
| } | ||
|
|
||
| class ErrorListenerLogger implements ErrorListener { |
There was a problem hiding this comment.
This can be private and static:
| class ErrorListenerLogger implements ErrorListener { | |
| private static class ErrorListenerLogger implements ErrorListener { |
|
|
||
| @Override | ||
| public void warning(TransformerException exception) throws TransformerException { | ||
| logger.warn(exception.getMessageAndLocation()); |
There was a problem hiding this comment.
| logger.warn(exception.getMessageAndLocation()); | |
| logger.warn(exception.getMessageAndLocation(), exception); |
|
|
||
| @Override | ||
| public void error(TransformerException exception) throws TransformerException { | ||
| logger.error(exception.getMessageAndLocation()); |
There was a problem hiding this comment.
| logger.error(exception.getMessageAndLocation()); | |
| logger.error(exception.getMessageAndLocation(), exception); |
|
|
||
| @Override | ||
| public void fatalError(TransformerException exception) throws TransformerException { | ||
| logger.log(LogLevel.FATAL, exception.getMessageAndLocation()); |
There was a problem hiding this comment.
| logger.log(LogLevel.FATAL, exception.getMessageAndLocation()); | |
| logger.log(LogLevel.FATAL, exception.getMessageAndLocation(), exception); |
| } | ||
|
|
||
| @Override | ||
| public void fatalError(TransformerException exception) throws TransformerException { |
There was a problem hiding this comment.
| public void fatalError(TransformerException exception) throws TransformerException { | |
| public void fatalError(TransformerException exception) { |
There was a problem hiding this comment.
Isn't throws TransformerException part of the signature of the method defined in the interface?
There was a problem hiding this comment.
Yes, but if it isn't thrown, it doesn't need to be declared on the implementing method.
In this particular case, on further review, fatalError should throw the exception to prevent further processing.
There was a problem hiding this comment.
So I just rethrow the exception argument like below ?
throw exception;
| } | ||
|
|
||
| @Override | ||
| public void error(TransformerException exception) throws TransformerException { |
There was a problem hiding this comment.
| public void error(TransformerException exception) throws TransformerException { | |
| public void error(TransformerException exception) { |
| } | ||
|
|
||
| @Override | ||
| public void warning(TransformerException exception) throws TransformerException { |
There was a problem hiding this comment.
| public void warning(TransformerException exception) throws TransformerException { | |
| public void warning(TransformerException exception) { |
| final Map<String, String> attributes = new HashMap<>(); | ||
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml"), attributes); |
There was a problem hiding this comment.
| final Map<String, String> attributes = new HashMap<>(); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml"), attributes); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml")); |
| final Map<String, String> attributes = new HashMap<>(); | ||
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes); |
There was a problem hiding this comment.
| final Map<String, String> attributes = new HashMap<>(); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml")); |
| final Map<String, String> attributes = new HashMap<>(); | ||
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes); |
There was a problem hiding this comment.
| final Map<String, String> attributes = new HashMap<>(); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes); | |
| runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml")); |
|
@exceptionfactory I made the changes you requested. I am not sure though what to do as two of the ci builds timed out. Please advise. Thanks! |
exceptionfactory
left a comment
There was a problem hiding this comment.
Thanks for making the adjustments @dan-s1, the latest version looks good. I restarted the failed automated builds for good measure, and will plan on merging soon.
|
@exceptionfactory Thanks for the review and restarting those builds. I just learnt something about the TestRunner that we could access logging that occurred during running a processor. Do you think I should add assertions to the unit tests I added that confirms there was logging when ever a compile time or runtime error occurs when transforming XML? |
If you are inclined to add a check for a log at a given level, that would be a helpful addition. It is not necessary to check the content of the message, but check for a warning or error log as appropriate could be useful. |
|
@exceptionfactory Ok great! I will see if I can add that. |
|
@exceptionfactory Do you mean just asserting for example getErrorMessages on the MockComponentLog is not empty? I thought it also may be helpful to have the contents of the message similar to in the ticket
in order to demonstrate the issue was fixed. |
Asserting an exact message makes the unit test more brittle when it comes to library changes. In this particular case, asserting that the message contains the word |
|
@exceptionfactory I added the assertions. Please let me know if that is enough or if there should be more. Thanks! |
exceptionfactory
left a comment
There was a problem hiding this comment.
Thanks for the additional logging assertions, recommend streamlining several of them to focus checking based on the presence of messages, and the existence of the element name.
| assertTrue(logger.getErrorMessages().size() >= 1); | ||
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | ||
| assertTrue(firstMessage.contains("xsl:template")); | ||
| assertTrue(firstMessage.contains("Line#")); | ||
| assertTrue(firstMessage.contains("Column#")); |
There was a problem hiding this comment.
Recommend simplifying the assertions as follows:
| assertTrue(logger.getErrorMessages().size() >= 1); | |
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | |
| assertTrue(firstMessage.contains("xsl:template")); | |
| assertTrue(firstMessage.contains("Line#")); | |
| assertTrue(firstMessage.contains("Column#")); | |
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | |
| assertTrue(firstMessage.contains("xsl:template")); |
There was a problem hiding this comment.
No problem. I thought asserting the existence of the Line# and Column# would be helpful as it addresses the request in the ticket to have them included and that they end up as part of a bulletin in the UI.
| assertTrue(logger.getErrorMessages().size() >= 1); | ||
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | ||
| assertTrue(firstMessage.contains("xsl:message")); | ||
| assertTrue(firstMessage.contains("Line#")); | ||
| assertTrue(firstMessage.contains("Column#")); |
There was a problem hiding this comment.
| assertTrue(logger.getErrorMessages().size() >= 1); | |
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | |
| assertTrue(firstMessage.contains("xsl:message")); | |
| assertTrue(firstMessage.contains("Line#")); | |
| assertTrue(firstMessage.contains("Column#")); | |
| String firstMessage = logger.getErrorMessages().get(0).getMsg(); | |
| assertTrue(firstMessage.contains("xsl:message")); |
|
@exceptionfactory I believe I took care of all of your requests. |
exceptionfactory
left a comment
There was a problem hiding this comment.
Thanks for working through the feedback @dan-s1, the latest version looks good! +1 merging
This closes apache#6798 Signed-off-by: David Handermann <exceptionfactory@apache.org>
…nd Transformer to enable logging errors when processing transformation instructions and when running the transformation itself. Also added ability to log errors from messages that are emitted with xsl:message/.
Summary
NIFI-6498
Tracking
Please complete the following tracking steps prior to pull request creation.
Issue Tracking
Pull Request Tracking
NIFI-00000NIFI-00000Pull Request Formatting
mainbranchVerification
Please indicate the verification steps performed prior to pull request creation.
Build
mvn clean install -P contrib-checkLicensing
LICENSEandNOTICEfilesDocumentation