[ 
https://issues.apache.org/jira/browse/NIP-46?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Mark Payne reassigned NIP-46:
-----------------------------

    Assignee: Mark Payne

> Increase Error Observability in Stateless NiFi
> ----------------------------------------------
>
>                 Key: NIP-46
>                 URL: https://issues.apache.org/jira/browse/NIP-46
>             Project: NiFi Improvement Proposal
>          Issue Type: Improvement
>            Reporter: Mike Thomsen
>            Assignee: Mark Payne
>            Priority: Major
>
> h2. Increase Error Observability in Stateless NiFi
> h2. Motivation
> Creating a simple observability framework for Stateless NiFi would allow 
> applications that start stateless NiFi to hook into the flow components and 
> more effectively track how, where and why a flow failed. One particularly 
> strong use case this could open for stateless NiFi is to be used as a CI/CD 
> test harness for NiFi flows wherein they would be run in an integration test 
> and the results cleanly reported to the CI/CD pipeline.
> h2. Scope
> This proposal aims to actively avoid any changes to nifi-api and focus 
> entirely on non-breaking changes to the stateless NiFi framework. It would 
> add a callback framework focused on functional interfaces and event handlers 
> that would default to no-op implementations in the absence of a developer 
> defining callbacks for their particular use case.
> h2. Description
> The following will be added to provide a standard callback API that Stateless 
> NiFi can use to link the calling Java code and stateless NiFi. It will be 
> wired into the classes in org.apache.nifi.stateless as needed to ensure that 
> component errors can be tracked effectively by the calling application.
> There are two independent callbacks, each with a no-op default and each 
> purely observational: the
> dataflow fails exactly as it would have with no listener registered, and a 
> listener that throws has its
> exception logged and discarded. {{DataflowTriggerContext}} exposes both via 
> defaulted methods
> ({{getComponentFailureListener()}}, {{getFailurePortListener()}}), each 
> returning its {{NOP}} instance.
> h3. Component failures
> Notified whenever a component throws from {{onTrigger}}.
> {code:language=|borderStyle=solid|theme=RDark|linenumbers=true|collapse=true}
> public record ComponentFailure(String componentId, String componentName, 
> String componentType, Throwable cause) {
> }
> @FunctionalInterface
> public interface ComponentFailureListener {
>     ComponentFailureListener NOP = failure -> {
>     };
>     /**
>      * Invoked after a component has thrown and before the exception is 
> propagated.
>      */
>     void onComponentFailure(ComponentFailure failure);
> }
> public class ComponentFailureNotifier {
>     private final ComponentFailureListener listener;
>     // By identity: two distinct exceptions with equal messages are still two 
> separate failures.
>     private final Set<Throwable> alreadyReported = 
> Collections.synchronizedSet(Collections.newSetFromMap(new 
> IdentityHashMap<>()));
>     public ComponentFailureNotifier(final ComponentFailureListener listener) {
>         this.listener = listener == null ? ComponentFailureListener.NOP : 
> listener;
>     }
>     public void notifyFailure(final Connectable connectable, final Throwable 
> cause) {
>         // FailurePortEncounteredException, DataflowAbortedException and 
> TerminatedTaskException unwind
>         // through whichever component was committing a session; they are the 
> dataflow's outcome, not a
>         // component failure, so a cause chain containing one is skipped.
>         if (isDataflowOutcome(cause) || !alreadyReported.add(cause)) {
>             return;
>         }
>         try {
>             listener.onComponentFailure(new 
> ComponentFailure(connectable.getIdentifier(), connectable.getName(),
>                 connectable.getComponentType(), cause));
>         } catch (final Throwable listenerFailure) {
>             logger.error("Failed to notify {} that {} threw an exception", 
> listener, connectable, listenerFailure);
>         }
>     }
> }
> {code}
> The notifier is invoked from the main scheduling loop, from inline 
> synchronous-commit unwinding (where
> only the innermost point knows which component threw, hence the identity 
> de-duplication), and from
> asynchronous commit success-callbacks that run after the dataflow has 
> otherwise completed.
> h3. Failure-port routing
> Notified when a FlowFile reaches a port marked as a failure port. This is the 
> observational counterpart
> to the {{FailurePortEncounteredException}} that aborts the dataflow, adding 
> the identity of the routing
> component when it is known. The routing component is carried by value, not 
> reference, so the event can
> cross the dataflow's ClassLoader; it is absent when no component in the flow 
> routed the FlowFile, e.g.
> an input port wired straight to a failure port.
> {code:language=|borderStyle=solid|theme=RDark|linenumbers=true|collapse=false}
> public record FailurePortEvent(String portName, String sourceComponentId, 
> String sourceComponentName,
>     String sourceComponentType) {
> }
> @FunctionalInterface
> public interface FailurePortListener {
>     FailurePortListener NOP = event -> {
>     };
>     /**
>      * Invoked when a FlowFile has reached a failure port, before the 
> dataflow's failure is propagated.
>      */
>     void onFailurePort(FailurePortEvent event);
> }
> {code}
> {{ExecutionProgress.reportFailurePort(Connectable sourceComponent, String 
> portName)}} fires the listener,
> at most once per port name, from the synchronous and asynchronous commit 
> paths in
> {{StatelessProcessSession}} and from the output check performed when the 
> {{TriggerResult}} is built.
> h2. Compatibility
> This change will aim to make no changes to nifi-api and no breaking changes 
> to the NiFi stateless implementations.
> h2. Verification
> Integration tests will be added to verify the new functionality and existing 
> integration tests will be retained to ensure that existing implementations 
> remain fully support.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to