Repository: uber-go/fx
Pull request #869 (2022-04-01 04:38:27)

Emit a separate log for fx.Replace
This adds a new type fxevent.Replaced, which is used for logging
values that are being decorated via fx.Replace. Currently, these
are being emitted as part of the fxevent.Decorated log, but that
can be misleading and confusing since the decorator name for such
values is not properly provided (they are generated at runtime using
reflection).

The new log entry for fx.Replace-d types contains the types being
replaced, as well as the module name (if there is one).

codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) Report
> Merging [#869](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (d4eee1f) into [master](https://codecov.io/gh/uber-go/fx/commit/30d87eee6413b99a00dfdcef36a3f416acae26e9?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (30d87ee) will **increase** coverage by `0.03%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #869      +/-   ##
==========================================
+ Coverage   98.49%   98.52%   +0.03%     
==========================================
  Files          30       30              
  Lines        1130     1155      +25     
==========================================
+ Hits         1113     1138      +25     
  Misses         11       11              
  Partials        6        6              
```


| [Impacted Files](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) | Coverage Δ | |
|---|---|---|
| [decorate.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-ZGVjb3JhdGUuZ28=) | `100.00% <ø> (ø)` | |
| [fxevent/console.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-ZnhldmVudC9jb25zb2xlLmdv) | `100.00% <100.00%> (ø)` | |
| [fxevent/event.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-ZnhldmVudC9ldmVudC5nbw==) | `100.00% <100.00%> (ø)` | |
| [fxevent/zap.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-ZnhldmVudC96YXAuZ28=) | `100.00% <100.00%> (ø)` | |
| [module.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-bW9kdWxlLmdv) | `100.00% <100.00%> (ø)` | |
| [replace.go](https://codecov.io/gh/uber-go/fx/pull/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-cmVwbGFjZS5nbw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Last update [30d87ee...d4eee1f](https://codecov.io/gh/uber-go/fx/pull/869?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).


Patch:
diff --git a/decorate.go b/decorate.go
index a241a3c..d40f6d1 100644
--- a/decorate.go
+++ b/decorate.go
@@ -205,6 +205,9 @@ type decorator struct {
 
 	// Stack trace of where this provide was made.
 	Stack fxreflect.Stack
+
+	// Whether this decorator was specified via fx.Replace
+	IsReplace bool
 }
 
 func runDecorator(c container, d decorator, opts ...dig.DecorateOption) (err error) {
diff --git a/fxevent/console.go b/fxevent/console.go
index 9f89853..b42a419 100644
--- a/fxevent/console.go
+++ b/fxevent/console.go
@@ -78,6 +78,18 @@ func (l *ConsoleLogger) LogEvent(event Event) {
 		if e.Err != nil {
 			l.logf("Error after options were applied: %v", e.Err)
 		}
+
+	case *Replaced:
+		for _, rtype := range e.OutputTypeNames {
+			if e.ModuleName != "" {
+				l.logf("REPLACE\t%v from module %q", rtype, e.ModuleName)
+			} else {
+				l.logf("REPLACE\t%v", rtype)
+			}
+		}
+		if e.Err != nil {
+			l.logf("ERROR\tFailed to replace: %+v", e.Err)
+		}
 	case *Decorated:
 		for _, rtype := range e.OutputTypeNames {
 			if e.ModuleName != "" {
diff --git a/fxevent/event.go b/fxevent/event.go
index 297c143..e81b1cb 100644
--- a/fxevent/event.go
+++ b/fxevent/event.go
@@ -37,6 +37,7 @@ func (*OnStopExecuting) event()   {}
 func (*OnStopExecuted) event()    {}
 func (*Supplied) event()          {}
 func (*Provided) event()          {}
+func (*Replaced) event()          {}
 func (*Decorated) event()         {}
 func (*Invoking) event()          {}
 func (*Invoked) event()           {}
@@ -133,6 +134,18 @@ type Provided struct {
 	Err error
 }
 
+// Replaced is emitted when a decorator is provided to Fx.
+type Replaced struct {
+	// OutputTypeNames is a list of names of types that were replaced.
+	OutputTypeNames []string
+
+	// ModuleName is the name of the module in which the value was added to.
+	ModuleName string
+
+	// Err is non-nil if we failed to supply the value.
+	Err error
+}
+
 // Decorated is emitted when a decorator is provided to Fx.
 type Decorated struct {
 	// DecoratorName is the name of the decorator function that was
diff --git a/fxevent/zap.go b/fxevent/zap.go
index 349f236..320dec9 100644
--- a/fxevent/zap.go
+++ b/fxevent/zap.go
@@ -92,6 +92,18 @@ func (l *ZapLogger) LogEvent(event Event) {
 				moduleField(e.ModuleName),
 				zap.Error(e.Err))
 		}
+	case *Replaced:
+		for _, rtype := range e.OutputTypeNames {
+			l.Logger.Info("replaced",
+				moduleField(e.ModuleName),
+				zap.String("type", rtype),
+			)
+		}
+		if e.Err != nil {
+			l.Logger.Error("error encountered while replacing",
+				moduleField(e.ModuleName),
+				zap.Error(e.Err))
+		}
 	case *Decorated:
 		for _, rtype := range e.OutputTypeNames {
 			l.Logger.Info("decorated",
diff --git a/module.go b/module.go
index 1412ec1..db6a703 100644
--- a/module.go
+++ b/module.go
@@ -190,12 +190,21 @@ func (m *module) decorate() (err error) {
 			outputNames[i] = o.String()
 		}
 
-		m.app.log.LogEvent(&fxevent.Decorated{
-			DecoratorName:   fxreflect.FuncName(decorator.Target),
-			ModuleName:      m.name,
-			OutputTypeNames: outputNames,
-			Err:             err,
-		})
+		if decorator.IsReplace {
+			m.app.log.LogEvent(&fxevent.Replaced{
+				ModuleName:      m.name,
+				OutputTypeNames: outputNames,
+				Err:             err,
+			})
+		} else {
+
+			m.app.log.LogEvent(&fxevent.Decorated{
+				DecoratorName:   fxreflect.FuncName(decorator.Target),
+				ModuleName:      m.name,
+				OutputTypeNames: outputNames,
+				Err:             err,
+			})
+		}
 		if err != nil {
 			return err
 		}
diff --git a/replace.go b/replace.go
index cd0a13a..4a09c81 100644
--- a/replace.go
+++ b/replace.go
@@ -105,8 +105,9 @@ type replaceOption struct {
 func (o replaceOption) apply(m *module) {
 	for _, target := range o.Targets {
 		m.decorators = append(m.decorators, decorator{
-			Target: target,
-			Stack:  o.Stack,
+			Target:    target,
+			Stack:     o.Stack,
+			IsReplace: true,
 		})
 	}
 }


Test patch:
diff --git a/fxevent/console_test.go b/fxevent/console_test.go
index 9ef06d5..3af0b45 100644
--- a/fxevent/console_test.go
+++ b/fxevent/console_test.go
@@ -116,6 +116,19 @@ func TestConsoleLogger(t *testing.T) {
 			},
 			want: "[Fx] PROVIDE	*bytes.Buffer <= bytes.NewBuffer() from module \"myModule\"\n",
 		},
+		{
+			name: "Replaced",
+			give: &Replaced{
+				ModuleName:      "myModule",
+				OutputTypeNames: []string{"*bytes.Buffer"},
+			},
+			want: "[Fx] REPLACE	*bytes.Buffer from module \"myModule\"\n",
+		},
+		{
+			name: "ReplacedError",
+			give: &Replaced{Err: errors.New("some error")},
+			want: "[Fx] ERROR	Failed to replace: some error\n",
+		},
 		{
 			name: "Decorated",
 			give: &Decorated{
diff --git a/fxevent/event_test.go b/fxevent/event_test.go
index d928fcc..77f3f83 100644
--- a/fxevent/event_test.go
+++ b/fxevent/event_test.go
@@ -37,6 +37,7 @@ func TestForCoverage(t *testing.T) {
 		&OnStopExecuted{},
 		&Supplied{},
 		&Provided{},
+		&Replaced{},
 		&Decorated{},
 		&Invoking{},
 		&Invoked{},
diff --git a/fxevent/zap_test.go b/fxevent/zap_test.go
index f4afdf0..6689535 100644
--- a/fxevent/zap_test.go
+++ b/fxevent/zap_test.go
@@ -165,6 +165,27 @@ func TestZapLogger(t *testing.T) {
 				"error": "some error",
 			},
 		},
+		{
+			name: "Replace",
+			give: &Replaced{
+				ModuleName:      "myModule",
+				OutputTypeNames: []string{"*bytes.Buffer"},
+			},
+			wantMessage: "replaced",
+			wantFields: map[string]interface{}{
+				"type":   "*bytes.Buffer",
+				"module": "myModule",
+			},
+		},
+		{
+			name: "Replace/Error",
+			give: &Replaced{Err: someError},
+
+			wantMessage: "error encountered while replacing",
+			wantFields: map[string]interface{}{
+				"error": "some error",
+			},
+		},
 		{
 			name: "Decorate",
 			give: &Decorated{