diff --git a/app.go b/app.go index 8ede19eb5..8189f9d71 100644 --- a/app.go +++ b/app.go @@ -480,10 +480,9 @@ func New(opts ...Option) *App { m.build(app, app.container) } - for _, m := range app.modules { - m.provideAll() - } - + // Provide Fx types first to increase the chance a custom logger + // can be successfully built in the face of unrelated DI failure. + // E.g., for a custom logger that relies on the Lifecycle type. frames := fxreflect.CallerStack(0, 0) // include New in the stack for default Provides app.root.provide(provide{ Target: func() Lifecycle { return app.lifecycle }, @@ -492,6 +491,10 @@ func New(opts ...Option) *App { app.root.provide(provide{Target: app.shutdowner, Stack: frames}) app.root.provide(provide{Target: app.dotGraph, Stack: frames}) + for _, m := range app.modules { + m.provideAll() + } + // Run decorators before executing any Invokes -- including the one // inside constructCustomLogger. app.err = multierr.Append(app.err, app.root.decorateAll()) diff --git a/app_test.go b/app_test.go index 2ac6ef2bf..cc17a4f19 100644 --- a/app_test.go +++ b/app_test.go @@ -113,7 +113,13 @@ func TestNewApp(t *testing.T) { []string{"Provided", "Provided", "Provided", "Provided", "LoggerInitialized", "Started"}, spy.EventTypes()) - assert.Contains(t, spy.Events()[0].(*fxevent.Provided).OutputTypeNames, "struct {}") + // Fx types get provided first to increase chance of + // successful custom logger build. + assert.Contains(t, spy.Events()[0].(*fxevent.Provided).OutputTypeNames, "fx.Lifecycle") + assert.Contains(t, spy.Events()[1].(*fxevent.Provided).OutputTypeNames, "fx.Shutdowner") + assert.Contains(t, spy.Events()[2].(*fxevent.Provided).OutputTypeNames, "fx.DotGraph") + // Our type should be index 3. + assert.Contains(t, spy.Events()[3].(*fxevent.Provided).OutputTypeNames, "struct {}") }) t.Run("CircularGraphReturnsError", func(t *testing.T) { @@ -575,7 +581,7 @@ func TestWithLogger(t *testing.T) { ) assert.Equal(t, []string{ - "Supplied", "Provided", "Provided", "Provided", "Run", "LoggerInitialized", + "Provided", "Provided", "Provided", "Supplied", "Run", "LoggerInitialized", }, spy.EventTypes()) spy.Reset() @@ -605,7 +611,7 @@ func TestWithLogger(t *testing.T) { "must provide constructor function, got (type *bytes.Buffer)", ) - assert.Equal(t, []string{"Supplied", "Provided", "Run", "LoggerInitialized"}, spy.EventTypes()) + assert.Equal(t, []string{"Provided", "Provided", "Provided", "Supplied", "Provided", "Run", "LoggerInitialized"}, spy.EventTypes()) }) t.Run("logger failed to build", func(t *testing.T) { @@ -1166,8 +1172,9 @@ func TestOptions(t *testing.T) { Provide(&bytes.Buffer{}), // error, not a constructor WithLogger(func() fxevent.Logger { return spy }), ) - require.Equal(t, []string{"Provided", "LoggerInitialized"}, spy.EventTypes()) - assert.Contains(t, spy.Events()[0].(*fxevent.Provided).Err.Error(), "must provide constructor function") + require.Equal(t, []string{"Provided", "Provided", "Provided", "Provided", "LoggerInitialized"}, spy.EventTypes()) + // First 3 provides are Fx types (Lifecycle, Shutdowner, DotGraph). + assert.Contains(t, spy.Events()[3].(*fxevent.Provided).Err.Error(), "must provide constructor function") }) } diff --git a/module_test.go b/module_test.go index 1a6fd9b71..3ac20527b 100644 --- a/module_test.go +++ b/module_test.go @@ -261,7 +261,7 @@ func TestModuleSuccess(t *testing.T) { desc: "custom logger for module", giveWithLogger: fx.NopLogger, wantEvents: []string{ - "Supplied", "Provided", "Provided", "Provided", + "Provided", "Provided", "Provided", "Supplied", "Run", "LoggerInitialized", "Invoking", "Invoked", }, }, @@ -269,7 +269,7 @@ func TestModuleSuccess(t *testing.T) { desc: "Not using a custom logger for module defaults to app logger", giveWithLogger: fx.Options(), wantEvents: []string{ - "Supplied", "Provided", "Provided", "Provided", "Provided", "Run", + "Provided", "Provided", "Provided", "Supplied", "Provided", "Run", "LoggerInitialized", "Invoking", "Run", "Invoked", "Invoking", "Invoked", }, }, @@ -660,7 +660,7 @@ func TestModuleFailures(t *testing.T) { giveAppOpts: spyAsLogger, wantErrContains: []string{"error building logger"}, wantEvents: []string{ - "Supplied", "Provided", "Provided", "Provided", "Run", + "Provided", "Provided", "Provided", "Supplied", "Run", "LoggerInitialized", "Provided", "LoggerInitialized", }, }, @@ -678,7 +678,7 @@ func TestModuleFailures(t *testing.T) { giveAppOpts: spyAsLogger, wantErrContains: []string{"error building logger dependency"}, wantEvents: []string{ - "Supplied", "Provided", "Provided", "Provided", "Run", + "Provided", "Provided", "Provided", "Supplied", "Run", "LoggerInitialized", "Provided", "Provided", "Run", "LoggerInitialized", }, }, @@ -690,7 +690,7 @@ func TestModuleFailures(t *testing.T) { "fx.WithLogger", "from:", "Failed", }, wantEvents: []string{ - "Supplied", "Provided", "Provided", "Provided", "Run", + "Provided", "Provided", "Provided", "Supplied", "Run", "LoggerInitialized", "Provided", "LoggerInitialized", }, },