-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Callback Functions for Constructor & Decorator Calls #377
Conversation
Recently folks have been asking for more visiblity into what constructors and decorators actually get called, especially Fx users. This implementation allows users to provide callback functions for Dig to call when it invokes a constructor or decorator. Dig will pass along info such as which function was actually called, the name (package + name) of the function, and what kind it was (provided or decorated), when it calls the provided callback function. Users can provide a callback by using the new `dig.Option`, `dig.WithCallback`: ```go type MyCallback struct {} func (MyCallback) Called(ci dig.CallbackInfo) { switch ci.Kind { case dig.Provided: fmt.Printf("My provided function %q was called!\n", ci.Name) case dig.Decorated: fmt.Printf("My decorator %q was called!\n", ci.Name) } } func main() { // ... c := dig.New(WithCallback(MyCallback{})) // ... } ``` With this addition, from the Fx side, we can register a callback function that will generate new Fx events when constructors and decorators get called. This will help users identify which _do not_ get called, and thus identify dead code. Internal Ref: GO-1873
Codecov Report
@@ Coverage Diff @@
## master #377 +/- ##
==========================================
+ Coverage 98.35% 98.38% +0.03%
==========================================
Files 21 22 +1
Lines 1458 1490 +32
==========================================
+ Hits 1434 1466 +32
Misses 15 15
Partials 9 9
... and 1 file with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
callback.go
Outdated
// Error contains the error returned by the [Callback]'s associated | ||
// function, if there was one. | ||
Error error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we can allow differentiation between regular errors vs panic errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users should be able to do this using errors.As
https://pkg.go.dev/go.uber.org/dig#PanicError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Folks have been asking for additional observability into what constructors/decorators they give to Fx actually get invoked. With uber-go/dig#377 being merged, we can use Dig callbacks to provide this observability point. This PR does this by registering callbacks with Dig that generate a new [fxevent](https://pkg.go.dev/go.uber.org/fx/fxevent#Event) called `fxevent.Run`, containing information about the provided/decorated function that was invoked by Dig: ```go // Run is emitted after a constructor, decorator, or supply/replace stub is run by Fx. type Run struct { // Name is the name of the function that was run. Name string // Kind indicates which Fx option was used to pass along the function. // It is either "provide", "decorate", "supply", or "replace". Kind string // ModuleName is the name of the module in which the function belongs. ModuleName string // Err is non-nil if the function returned an error. // If fx.RecoverFromPanics is used, this will include panics. Err error } ``` The default [fxevent.Logger](https://pkg.go.dev/go.uber.org/fx/fxevent#Logger)s have been updated to print to the console/log with Zap accordingly. Folks can use custom `fxevent.Logger`s to respond to these events as they would any other event: ```go type myLogger struct{ /* ... */ } func (l *myLogger) LogEvent(event fxevent.Event) { switch e := event.(type) { // ... case *fxevent.Run: // can access e.Name, e.Kind, e.ModuleName, and e.Err // ... } } func main() { app := fx.New( fx.WithLogger(func() fxevent.Logger { return &myLogger{} } ), // ... ) } ``` I wrote a small demo showing a custom logger like this can be used to identify "dead functions" within Fx: https://github.com/JacobOaks/fx_dead_code_demo. This PR also adds stack trace information into the `Provided`, `Decorated`, `Supplied`, and `Decorated` fxevents to aid folks with larger codebases that consume many modules, who might otherwise have a hard time finding where exactly a "dead function" is being given to Fx with just the `name` and `ModuleName` fields of the `Run` event. Fx was already keeping track of this information, so I don't think this addition incurs too much additional overhead for Fx, but if folks disagree we can remove this addition or perhaps make it opt-in by gating it behind a new option `fx.ReportStackTraces()` or something.
…go#377) Recently folks have been asking for more visiblity into what constructors and decorators actually get called, especially Fx users. This implementation allows users to provide callback functions for Dig to call when it invokes a constructor or decorator. Dig will pass along info such as which function was actually called, the name (package + name) of the function, and what kind it was (provided or decorated), when it calls the provided callback function. Users can provide a callback by using the new `dig.Option`, `dig.WithCallback`: ```go type MyCallback struct {} func (MyCallback) Called(ci dig.CallbackInfo) { switch ci.Kind { case dig.Provided: fmt.Printf("My provided function %q was called!\n", ci.Name) case dig.Decorated: fmt.Printf("My decorator %q was called!\n", ci.Name) } } func main() { // ... c := dig.New(WithCallback(MyCallback{})) // ... } ``` With this addition, from the Fx side, we can register a callback function that will generate new Fx events when constructors and decorators get called. This will help users identify which _do not_ get called, and thus identify dead code. Internal Ref: GO-1873
Recently folks have been asking for more visiblity into what constructors and decorators actually get called, especially Fx users (uber-go/fx#1039).
This PR allows users to provide callback functions for Dig to call when it invokes a constructor or decorator. Dig will pass along some basic information to the callback function: the name of the function, and the error it returned if any.
Users can provide a callback by using the two new APIs,
dig.WithProviderCallback
anddig.WithDecoratorCallback
, which return a type implementing bothdig.ProvideOption
anddig.DecorateOption
. For example, this code prints a simple message whenmyFunc
andmyDecorator
are called (presumably defined elsewhere):We can discuss Fx-side consumption of this functionality to aid observability in this way and help identify dead code, but one idea is to register callback functions that will generate new Fx events when constructors and decorators get called. I wrote a prototype implementation of this idea for Fx, and then wrote a demo showing how this can help identify dead code.
Internal Ref: GO-1873