@@ -122,6 +122,7 @@ type graphNode struct {
122122 dependents []* graphNode
123123 dependentsByKey map [ID ][]* graphNode
124124 tracer trace.Tracer
125+ logger Logger
125126}
126127
127128const (
@@ -150,8 +151,8 @@ func (gn *graphNode) execute(ctx context.Context, rs *runState) (err error) {
150151 tCtx , span := gn .tracer .Start (ctx , gn .task .Name ())
151152 defer span .End ()
152153
153- log .Debugf ("Starting task %s" , gn .task .Name ())
154- defer log .Debugf ("Finished task %s" , gn .task .Name ())
154+ gn . logger .Debugf ("Starting task %s" , gn .task .Name ())
155+ defer gn . logger .Debugf ("Finished task %s" , gn .task .Name ())
155156
156157 bindings , err := gn .task .Execute (tCtx , rs )
157158 if err != nil {
@@ -169,20 +170,28 @@ func (gn *graphNode) execute(ctx context.Context, rs *runState) (err error) {
169170 }
170171 }
171172 var extra []string
173+ errors := []string {}
172174 providesSet := set .NewSet [ID ](gn .task .Provides ()... )
173175 for _ , binding := range bindings {
174176 if ! providesSet .Contains (binding .ID ()) {
175177 extra = append (extra , binding .ID ().String ())
176178 }
179+
177180 if binding .Status () == Absent {
181+ err := binding .Error ()
182+ if err != nil {
183+ errors = append (errors , fmt .Sprintf ("[%s: %s]" , binding .ID ().String (), err ))
184+ }
185+
178186 span .SetAttributes (
179187 attribute .String (
180188 traceTaskgraphAbsentKeysPrefix + binding .ID ().String (),
181- fmt .Sprintf ("%v" , binding . Error () ),
189+ fmt .Sprintf ("%v" , err ),
182190 ),
183191 )
184192 }
185193 }
194+
186195 if len (extra ) > 0 || len (missing ) > 0 {
187196 return wrapStackErrorf (
188197 "task %s: mismatch between task Provides declaration and returned bindings: missing bindings [%s], got extra bindings [%s]" ,
@@ -192,8 +201,16 @@ func (gn *graphNode) execute(ctx context.Context, rs *runState) (err error) {
192201 )
193202 }
194203
204+ if len (errors ) > 0 {
205+ gn .logger .Debugf (
206+ "task %s has binding errors: %s" ,
207+ gn .task .Name (),
208+ strings .Join (errors , ", " ),
209+ )
210+ }
211+
195212 for _ , dependent := range gn .dependents {
196- log .Debugf ("task %s signalling dependent %s\n " , gn .task .Name (), dependent .task .Name ())
213+ gn . logger .Debugf ("task %s signalling dependent %s\n " , gn .task .Name (), dependent .task .Name ())
197214 if err := rs .signal (tCtx , dependent .id ); err != nil {
198215 return err
199216 }
@@ -212,10 +229,11 @@ func (gn *graphNode) canExecute(b Binder) bool {
212229func (gn * graphNode ) runFunc (ctx context.Context , rs * runState ) func () error {
213230 return func () error {
214231 if gn .canExecute (rs ) {
215- log .Debugf ("task %s starting immediately\n " , gn .task .Name ())
232+ gn . logger .Debugf ("task %s starting immediately\n " , gn .task .Name ())
216233 return gn .execute (ctx , rs )
217234 }
218- log .Debugf ("task %s has dependencies missing; cannot start immediately\n " , gn .task .Name ())
235+ gn .logger .Debugf ("task %s has dependencies missing; cannot start immediately\n " ,
236+ gn .task .Name ())
219237 signal , ok := rs .signals [gn .id ]
220238 if ! ok {
221239 return wrapStackErrorf ("signal channel missing for id %q" , gn .id )
@@ -224,10 +242,10 @@ func (gn *graphNode) runFunc(ctx context.Context, rs *runState) func() error {
224242 select {
225243 case <- signal :
226244 if gn .canExecute (rs ) {
227- log .Debugf ("task %s starting\n " , gn .task .Name ())
245+ gn . logger .Debugf ("task %s starting\n " , gn .task .Name ())
228246 return gn .execute (ctx , rs )
229247 }
230- log .Debugf ("task %s still has dependencies missing\n " , gn .task .Name ())
248+ gn . logger .Debugf ("task %s still has dependencies missing\n " , gn .task .Name ())
231249 case <- ctx .Done ():
232250 return nil
233251 }
@@ -241,6 +259,7 @@ type graph struct {
241259 allDependencies , allProvided set.Set [ID ]
242260 nodes []* graphNode
243261 tracer trace.Tracer
262+ logger Logger
244263}
245264
246265func (g * graph ) buildInputBinder (inputs ... Binding ) (Binder , error ) {
@@ -429,9 +448,15 @@ func (g *graph) Graphviz(includeInputs bool) string {
429448 return buf .String ()
430449}
431450
451+ // Logger logger interface for the graph.
452+ type Logger interface {
453+ Debugf (format string , args ... interface {})
454+ }
455+
432456type graphOptions struct {
433457 tasks []Task
434458 tracer trace.Tracer
459+ logger Logger
435460}
436461
437462// A GraphOption is used to configure a new Graph.
@@ -459,6 +484,15 @@ func WithTracer(tracer trace.Tracer) GraphOption {
459484 }
460485}
461486
487+ // WithLogger sets a logger for the graph.
488+ func WithLogger (logger Logger ) GraphOption {
489+ return func (opts * graphOptions ) error {
490+ opts .logger = logger
491+
492+ return nil
493+ }
494+ }
495+
462496// New creates a new Graph. Exactly one WithTasks option should be passed.
463497//
464498// Ideally, Graphs should be created on program startup, rather than creating them dynamically.
@@ -473,12 +507,17 @@ func New(name string, opts ...GraphOption) (Graph, error) {
473507 }
474508 }
475509
510+ if o .logger == nil {
511+ o .logger = log
512+ }
513+
476514 g := & graph {
477515 name : name ,
478516 tasks : o .tasks ,
479517 allDependencies : set .NewSet [ID ](),
480518 allProvided : set .NewSet [ID ](),
481519 tracer : o .tracer ,
520+ logger : o .logger ,
482521 }
483522
484523 provideTasks := map [string ][]string {}
@@ -498,6 +537,7 @@ func New(name string, opts ...GraphOption) (Graph, error) {
498537 task : t ,
499538 dependentsByKey : map [ID ][]* graphNode {},
500539 tracer : g .tracer ,
540+ logger : g .logger ,
501541 }
502542 g .nodes = append (g .nodes , node )
503543
0 commit comments