github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/try.go (about)

     1  package processor
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/docs"
     8  	"github.com/Jeffail/benthos/v3/internal/interop"
     9  	"github.com/Jeffail/benthos/v3/lib/log"
    10  	"github.com/Jeffail/benthos/v3/lib/message"
    11  	"github.com/Jeffail/benthos/v3/lib/metrics"
    12  	"github.com/Jeffail/benthos/v3/lib/types"
    13  )
    14  
    15  //------------------------------------------------------------------------------
    16  
    17  func init() {
    18  	Constructors[TypeTry] = TypeSpec{
    19  		constructor: NewTry,
    20  		Categories: []Category{
    21  			CategoryComposition,
    22  		},
    23  		Summary: `Executes a list of child processors on messages only if no prior processors have failed (or the errors have been cleared).`,
    24  		Description: `
    25  This processor behaves similarly to the ` + "[`for_each`](/docs/components/processors/for_each)" + ` processor, where a list of child processors are applied to individual messages of a batch. However, if a message has failed any prior processor (before or during the try block) then that message will skip all following processors.
    26  
    27  For example, with the following config:
    28  
    29  ` + "```yaml" + `
    30  pipeline:
    31    processors:
    32      - resource: foo
    33      - try:
    34        - resource: bar
    35        - resource: baz
    36        - resource: buz
    37  ` + "```" + `
    38  
    39  If the processor ` + "`bar`" + ` fails for a particular message, that message will skip the processors ` + "`baz` and `buz`" + `. Similarly, if ` + "`bar`" + ` succeeds but ` + "`baz`" + ` does not then ` + "`buz`" + ` will be skipped. If the processor ` + "`foo`" + ` fails for a message then none of ` + "`bar`, `baz` or `buz`" + ` are executed on that message.
    40  
    41  This processor is useful for when child processors depend on the successful output of previous processors. This processor can be followed with a ` + "[catch](/docs/components/processors/catch)" + ` processor for defining child processors to be applied only to failed messages.
    42  
    43  More information about error handing can be found [here](/docs/configuration/error_handling).
    44  
    45  ### Nesting within a catch block
    46  
    47  In some cases it might be useful to nest a try block within a catch block, since the ` + "[`catch` processor](/docs/components/processors/catch)" + ` only clears errors _after_ executing its child processors this means a nested try processor will not execute unless the errors are explicitly cleared beforehand.
    48  
    49  This can be done by inserting an empty catch block before the try block like as follows:
    50  
    51  ` + "```yaml" + `
    52  pipeline:
    53    processors:
    54      - resource: foo
    55      - catch:
    56        - log:
    57            level: ERROR
    58            message: "Foo failed due to: ${! error() }"
    59        - catch: [] # Clear prior error
    60        - try:
    61          - resource: bar
    62          - resource: baz
    63  ` + "```" + `
    64  
    65  
    66  `,
    67  		config: docs.FieldComponent().Array().HasType(docs.FieldTypeProcessor),
    68  	}
    69  }
    70  
    71  //------------------------------------------------------------------------------
    72  
    73  // TryConfig is a config struct containing fields for the Try processor.
    74  type TryConfig []Config
    75  
    76  // NewTryConfig returns a default TryConfig.
    77  func NewTryConfig() TryConfig {
    78  	return []Config{}
    79  }
    80  
    81  //------------------------------------------------------------------------------
    82  
    83  // Try is a processor that applies a list of child processors to each message of
    84  // a batch individually, where processors are skipped for messages that failed a
    85  // previous processor step.
    86  type Try struct {
    87  	children []types.Processor
    88  
    89  	log log.Modular
    90  
    91  	mCount     metrics.StatCounter
    92  	mErr       metrics.StatCounter
    93  	mSent      metrics.StatCounter
    94  	mBatchSent metrics.StatCounter
    95  }
    96  
    97  // NewTry returns a Try processor.
    98  func NewTry(
    99  	conf Config, mgr types.Manager, log log.Modular, stats metrics.Type,
   100  ) (Type, error) {
   101  	var children []types.Processor
   102  	for i, pconf := range conf.Try {
   103  		pMgr, pLog, pStats := interop.LabelChild(fmt.Sprintf("%v", i), mgr, log, stats)
   104  		proc, err := New(pconf, pMgr, pLog, pStats)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  		children = append(children, proc)
   109  	}
   110  	return &Try{
   111  		children: children,
   112  		log:      log,
   113  
   114  		mCount:     stats.GetCounter("count"),
   115  		mErr:       stats.GetCounter("error"),
   116  		mSent:      stats.GetCounter("sent"),
   117  		mBatchSent: stats.GetCounter("batch.sent"),
   118  	}, nil
   119  }
   120  
   121  //------------------------------------------------------------------------------
   122  
   123  // ProcessMessage applies the processor to a message, either creating >0
   124  // resulting messages or a response to be sent back to the message source.
   125  func (p *Try) ProcessMessage(msg types.Message) ([]types.Message, types.Response) {
   126  	p.mCount.Incr(1)
   127  
   128  	resultMsgs := make([]types.Message, msg.Len())
   129  	msg.Iter(func(i int, p types.Part) error {
   130  		tmpMsg := message.New(nil)
   131  		tmpMsg.SetAll([]types.Part{p})
   132  		resultMsgs[i] = tmpMsg
   133  		return nil
   134  	})
   135  
   136  	var res types.Response
   137  	if resultMsgs, res = ExecuteTryAll(p.children, resultMsgs...); res != nil {
   138  		return nil, res
   139  	}
   140  
   141  	resMsg := message.New(nil)
   142  	for _, m := range resultMsgs {
   143  		m.Iter(func(i int, p types.Part) error {
   144  			resMsg.Append(p)
   145  			return nil
   146  		})
   147  	}
   148  
   149  	p.mBatchSent.Incr(1)
   150  	p.mSent.Incr(int64(resMsg.Len()))
   151  
   152  	resMsgs := [1]types.Message{resMsg}
   153  	return resMsgs[:], nil
   154  }
   155  
   156  // CloseAsync shuts down the processor and stops processing requests.
   157  func (p *Try) CloseAsync() {
   158  	for _, c := range p.children {
   159  		c.CloseAsync()
   160  	}
   161  }
   162  
   163  // WaitForClose blocks until the processor has closed down.
   164  func (p *Try) WaitForClose(timeout time.Duration) error {
   165  	stopBy := time.Now().Add(timeout)
   166  	for _, c := range p.children {
   167  		if err := c.WaitForClose(time.Until(stopBy)); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  //------------------------------------------------------------------------------