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 //------------------------------------------------------------------------------