Skip to content

[NIFI-6498] Set error event listener on both the TransformerFactory a…#6798

Closed
dan-s1 wants to merge 6 commits intoapache:mainfrom
dan-s1:NIFI-6498
Closed

[NIFI-6498] Set error event listener on both the TransformerFactory a…#6798
dan-s1 wants to merge 6 commits intoapache:mainfrom
dan-s1:NIFI-6498

Conversation

@dan-s1
Copy link
Contributor

@dan-s1 dan-s1 commented Dec 19, 2022

…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

  • Pull Request title starts with Apache NiFi Jira issue number, such as NIFI-00000
  • Pull Request commit message starts with Apache NiFi Jira issue number, as such NIFI-00000

Pull Request Formatting

  • Pull Request based on current revision of the main branch
  • Pull Request refers to a feature branch with one commit containing changes

Verification

Please indicate the verification steps performed prior to pull request creation.

Build

  • Build completed using mvn clean install -P contrib-check
    • JDK 8
    • JDK 11
    • JDK 17

Licensing

  • New dependencies are compatible with the Apache License 2.0 according to the License Policy
  • New dependencies are documented in applicable LICENSE and NOTICE files

Documentation

  • Documentation formatting appears as expected in rendered files

…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/>.
Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 20, 2022

@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?

@exceptionfactory
Copy link
Contributor

Thanks for the reply @dan-s1.

Although the testMessage methods include the xsl:message examples, they do not appear to assert any particular behavior, which makes sense given that it just ends up writing messages. For that reason, it seems best to remove those test methods and related files.

Reviewing the Saxon Configuration Features there is a reference to a message emitter class named MessageWarner that notifies the standard JAXP ErrorListener. Introducing this feature would still place a direct relationship to the Saxon implementation, so it still seems like it would be best to avoid for now, since xsl:message is a defined element used for testing.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 20, 2022

@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.

@exceptionfactory
Copy link
Contributor

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.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 20, 2022

@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.
BTW I believe the code I had in setMessageReceiver essentially mimicked MessageWarner as MessageWarner extends net.sf.saxon.serialize.XMLEmitter.

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be private and static:

Suggested change
class ErrorListenerLogger implements ErrorListener {
private static class ErrorListenerLogger implements ErrorListener {


@Override
public void warning(TransformerException exception) throws TransformerException {
logger.warn(exception.getMessageAndLocation());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.warn(exception.getMessageAndLocation());
logger.warn(exception.getMessageAndLocation(), exception);


@Override
public void error(TransformerException exception) throws TransformerException {
logger.error(exception.getMessageAndLocation());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.error(exception.getMessageAndLocation());
logger.error(exception.getMessageAndLocation(), exception);


@Override
public void fatalError(TransformerException exception) throws TransformerException {
logger.log(LogLevel.FATAL, exception.getMessageAndLocation());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.log(LogLevel.FATAL, exception.getMessageAndLocation());
logger.log(LogLevel.FATAL, exception.getMessageAndLocation(), exception);

}

@Override
public void fatalError(TransformerException exception) throws TransformerException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void fatalError(TransformerException exception) throws TransformerException {
public void fatalError(TransformerException exception) {

Copy link
Contributor Author

@dan-s1 dan-s1 Dec 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't throws TransformerException part of the signature of the method defined in the interface?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I just rethrow the exception argument like below ?

throw exception;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks!

}

@Override
public void error(TransformerException exception) throws TransformerException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void error(TransformerException exception) throws TransformerException {
public void error(TransformerException exception) {

}

@Override
public void warning(TransformerException exception) throws TransformerException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public void warning(TransformerException exception) throws TransformerException {
public void warning(TransformerException exception) {

Comment on lines +307 to +308
final Map<String, String> attributes = new HashMap<>();
runner.enqueue(Paths.get("src/test/resources/TestTransformXml/math.xml"), attributes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"));

Comment on lines +320 to +321
final Map<String, String> attributes = new HashMap<>();
runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"));

Comment on lines +333 to +334
final Map<String, String> attributes = new HashMap<>();
runner.enqueue(Paths.get("src/test/resources/TestTransformXml/employee.xml"), attributes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"));

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@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!

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@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?

@exceptionfactory
Copy link
Contributor

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.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@exceptionfactory Ok great! I will see if I can add that.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@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

Error on line 17 column 5 SXXP0003: Error reported by XML parser: The element type "xsl:tomplate" must be terminated by the matching end-tag "</xsl:tomplate>".

in order to demonstrate the issue was fixed.

@exceptionfactory
Copy link
Contributor

@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

Error on line 17 column 5 SXXP0003: Error reported by XML parser: The element type "xsl:tomplate" must be terminated by the matching end-tag "</xsl:tomplate>".

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 tomplate seems like a good approach.

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@exceptionfactory I added the assertions. Please let me know if that is enough or if there should be more. Thanks!

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +317 to +321
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#"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend simplifying the assertions as follows:

Suggested change
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"));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +336 to +340
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#"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"));

@dan-s1
Copy link
Contributor Author

dan-s1 commented Dec 21, 2022

@exceptionfactory I believe I took care of all of your requests.

Copy link
Contributor

@exceptionfactory exceptionfactory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working through the feedback @dan-s1, the latest version looks good! +1 merging

lizhizhou pushed a commit to lizhizhou/nifi that referenced this pull request Jan 2, 2023
This closes apache#6798

Signed-off-by: David Handermann <exceptionfactory@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants