Repository: uber-go/fx (go)

Issue:
Allow setting level of Zap Logger used for logging
The Zap logger that gets passed to fx via `WithLogger` gets logs at `Info` level for all messages except when things error out, which are logged at `Error` level.

We can consider exposing a way to set the level of these logs. 

Pull request description:
Allow setting Zap logger level
This extends the `fxevent.ZapLogger` struct to add two new methods: `UseLogLevel` and `UseErrorLevel`.

`UseLogLevel` sets the level of all non-error messages emitted by Fx.

`UseErrorLevel` sets the level of all error messages emitted by Fx. 

When these are not set, the log levels are kept identical to what they are now (Info for non-error logs, Error for error logs).

Fix #979.

Patch:
diff --git a/docs/go.mod b/docs/go.mod
index ec2ec5e..0c98d32 100644
--- a/docs/go.mod
+++ b/docs/go.mod
@@ -5,15 +5,15 @@ go 1.19
 require (
 	github.com/stretchr/testify v1.8.0
 	go.uber.org/fx v1.18.2
-	go.uber.org/zap v1.16.0
+	go.uber.org/zap v1.23.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	go.uber.org/atomic v1.6.0 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/dig v1.15.0 // indirect
-	go.uber.org/multierr v1.5.0 // indirect
+	go.uber.org/multierr v1.6.0 // indirect
 	golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/docs/go.sum b/docs/go.sum
index e665a2a..5592d30 100644
--- a/docs/go.sum
+++ b/docs/go.sum
@@ -1,67 +1,29 @@
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE=
 go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM=
 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
-go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
+go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
 golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
 golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/fxevent/zap.go b/fxevent/zap.go
index 9fb7e47..9ee7ee7 100644
--- a/fxevent/zap.go
+++ b/fxevent/zap.go
@@ -24,51 +24,77 @@ import (
 	"strings"
 
 	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 )
 
 // ZapLogger is an Fx event logger that logs events to Zap.
 type ZapLogger struct {
 	Logger *zap.Logger
+
+	logLevel   zapcore.Level // default: zapcore.InfoLevel
+	errorLevel *zapcore.Level
 }
 
 var _ Logger = (*ZapLogger)(nil)
 
+// UseErrorLevel sets the level of error logs emitted by Fx to level.
+func (l *ZapLogger) UseErrorLevel(level zapcore.Level) {
+	l.errorLevel = &level
+}
+
+// UseLogLevel sets the level of non-error logs emitted by Fx to level.
+func (l *ZapLogger) UseLogLevel(level zapcore.Level) {
+	l.logLevel = level
+}
+
+func (l *ZapLogger) logEvent(msg string, fields ...zap.Field) {
+	l.Logger.Log(l.logLevel, msg, fields...)
+}
+
+func (l *ZapLogger) logError(msg string, fields ...zap.Field) {
+	lvl := zapcore.ErrorLevel
+	if l.errorLevel != nil {
+		lvl = *l.errorLevel
+	}
+	l.Logger.Log(lvl, msg, fields...)
+}
+
 // LogEvent logs the given event to the provided Zap logger.
 func (l *ZapLogger) LogEvent(event Event) {
 	switch e := event.(type) {
 	case *OnStartExecuting:
-		l.Logger.Info("OnStart hook executing",
+		l.logEvent("OnStart hook executing",
 			zap.String("callee", e.FunctionName),
 			zap.String("caller", e.CallerName),
 		)
 	case *OnStartExecuted:
 		if e.Err != nil {
-			l.Logger.Info("OnStart hook failed",
+			l.logError("OnStart hook failed",
 				zap.String("callee", e.FunctionName),
 				zap.String("caller", e.CallerName),
 				zap.Error(e.Err),
 			)
 		} else {
-			l.Logger.Info("OnStart hook executed",
+			l.logEvent("OnStart hook executed",
 				zap.String("callee", e.FunctionName),
 				zap.String("caller", e.CallerName),
 				zap.String("runtime", e.Runtime.String()),
 			)
 		}
 	case *OnStopExecuting:
-		l.Logger.Info("OnStop hook executing",
+		l.logEvent("OnStop hook executing",
 			zap.String("callee", e.FunctionName),
 			zap.String("caller", e.CallerName),
 		)
 	case *OnStopExecuted:
 		if e.Err != nil {
-			l.Logger.Info("OnStop hook failed",
+			l.logError("OnStop hook failed",
 				zap.String("callee", e.FunctionName),
 				zap.String("caller", e.CallerName),
 				zap.Error(e.Err),
 			)
 		} else {
-			l.Logger.Info("OnStop hook executed",
+			l.logEvent("OnStop hook executed",
 				zap.String("callee", e.FunctionName),
 				zap.String("caller", e.CallerName),
 				zap.String("runtime", e.Runtime.String()),
@@ -76,63 +102,63 @@ func (l *ZapLogger) LogEvent(event Event) {
 		}
 	case *Supplied:
 		if e.Err != nil {
-			l.Logger.Error("error encountered while applying options",
+			l.logError("error encountered while applying options",
 				zap.String("type", e.TypeName),
 				moduleField(e.ModuleName),
 				zap.Error(e.Err))
 		} else {
-			l.Logger.Info("supplied",
+			l.logEvent("supplied",
 				zap.String("type", e.TypeName),
 				moduleField(e.ModuleName),
 			)
 		}
 	case *Provided:
 		for _, rtype := range e.OutputTypeNames {
-			l.Logger.Info("provided",
+			l.logEvent("provided",
 				zap.String("constructor", e.ConstructorName),
 				moduleField(e.ModuleName),
 				zap.String("type", rtype),
 			)
 		}
 		if e.Err != nil {
-			l.Logger.Error("error encountered while applying options",
+			l.logError("error encountered while applying options",
 				moduleField(e.ModuleName),
 				zap.Error(e.Err))
 		}
 	case *Replaced:
 		for _, rtype := range e.OutputTypeNames {
-			l.Logger.Info("replaced",
+			l.logEvent("replaced",
 				moduleField(e.ModuleName),
 				zap.String("type", rtype),
 			)
 		}
 		if e.Err != nil {
-			l.Logger.Error("error encountered while replacing",
+			l.logError("error encountered while replacing",
 				moduleField(e.ModuleName),
 				zap.Error(e.Err))
 		}
 	case *Decorated:
 		for _, rtype := range e.OutputTypeNames {
-			l.Logger.Info("decorated",
+			l.logEvent("decorated",
 				zap.String("decorator", e.DecoratorName),
 				moduleField(e.ModuleName),
 				zap.String("type", rtype),
 			)
 		}
 		if e.Err != nil {
-			l.Logger.Error("error encountered while applying options",
+			l.logError("error encountered while applying options",
 				moduleField(e.ModuleName),
 				zap.Error(e.Err))
 		}
 	case *Invoking:
 		// Do not log stack as it will make logs hard to read.
-		l.Logger.Info("invoking",
+		l.logEvent("invoking",
 			zap.String("function", e.FunctionName),
 			moduleField(e.ModuleName),
 		)
 	case *Invoked:
 		if e.Err != nil {
-			l.Logger.Error("invoke failed",
+			l.logError("invoke failed",
 				zap.Error(e.Err),
 				zap.String("stack", e.Trace),
 				zap.String("function", e.FunctionName),
@@ -140,29 +166,29 @@ func (l *ZapLogger) LogEvent(event Event) {
 			)
 		}
 	case *Stopping:
-		l.Logger.Info("received signal",
+		l.logEvent("received signal",
 			zap.String("signal", strings.ToUpper(e.Signal.String())))
 	case *Stopped:
 		if e.Err != nil {
-			l.Logger.Error("stop failed", zap.Error(e.Err))
+			l.logError("stop failed", zap.Error(e.Err))
 		}
 	case *RollingBack:
-		l.Logger.Error("start failed, rolling back", zap.Error(e.StartErr))
+		l.logError("start failed, rolling back", zap.Error(e.StartErr))
 	case *RolledBack:
 		if e.Err != nil {
-			l.Logger.Error("rollback failed", zap.Error(e.Err))
+			l.logError("rollback failed", zap.Error(e.Err))
 		}
 	case *Started:
 		if e.Err != nil {
-			l.Logger.Error("start failed", zap.Error(e.Err))
+			l.logError("start failed", zap.Error(e.Err))
 		} else {
-			l.Logger.Info("started")
+			l.logEvent("started")
 		}
 	case *LoggerInitialized:
 		if e.Err != nil {
-			l.Logger.Error("custom logger initialization failed", zap.Error(e.Err))
+			l.logError("custom logger initialization failed", zap.Error(e.Err))
 		} else {
-			l.Logger.Info("initialized custom fxevent.Logger", zap.String("function", e.ConstructorName))
+			l.logEvent("initialized custom fxevent.Logger", zap.String("function", e.ConstructorName))
 		}
 	}
 }
diff --git a/go.mod b/go.mod
index ffa4059..13f7f31 100644
--- a/go.mod
+++ b/go.mod
@@ -4,12 +4,12 @@ go 1.18
 
 require (
 	github.com/benbjohnson/clock v1.3.0
-	github.com/stretchr/testify v1.7.1
-	go.uber.org/atomic v1.6.0
+	github.com/stretchr/testify v1.8.0
+	go.uber.org/atomic v1.7.0
 	go.uber.org/dig v1.15.0
 	go.uber.org/goleak v1.1.11
-	go.uber.org/multierr v1.5.0
-	go.uber.org/zap v1.16.0
+	go.uber.org/multierr v1.6.0
+	go.uber.org/zap v1.23.0
 	golang.org/x/sys v0.0.0-20210903071746-97244b99971b
 )
 
diff --git a/go.sum b/go.sum
index 72fffb5..2c75e34 100644
--- a/go.sum
+++ b/go.sum
@@ -1,48 +1,38 @@
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
 github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE=
 go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM=
 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
-go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
+go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -62,24 +52,15 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=


Test patch:
diff --git a/fxevent/zap_test.go b/fxevent/zap_test.go
index b25358d..3e4ebe7 100644
--- a/fxevent/zap_test.go
+++ b/fxevent/zap_test.go
@@ -24,12 +24,14 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"strings"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 	"go.uber.org/zap/zaptest/observer"
 )
 
@@ -70,7 +72,7 @@ func TestZapLogger(t *testing.T) {
 		},
 		{
 
-			name: "OnStopExecutedError",
+			name: "OnStopExecuted/Error",
 			give: &OnStopExecuted{
 				FunctionName: "hook.onStart1",
 				CallerName:   "bytes.NewBuffer",
@@ -99,7 +101,7 @@ func TestZapLogger(t *testing.T) {
 		},
 		{
 
-			name: "OnStartExecutedError",
+			name: "OnStartExecuted/Error",
 			give: &OnStartExecuted{
 				FunctionName: "hook.onStart1",
 				CallerName:   "bytes.NewBuffer",
@@ -135,7 +137,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "SuppliedError",
+			name:        "Supplied/Error",
 			give:        &Supplied{TypeName: "*bytes.Buffer", Err: someError},
 			wantMessage: "error encountered while applying options",
 			wantFields: map[string]interface{}{
@@ -158,7 +160,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "Provide with Error",
+			name:        "Provide/Error",
 			give:        &Provided{Err: someError},
 			wantMessage: "error encountered while applying options",
 			wantFields: map[string]interface{}{
@@ -201,7 +203,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "Decorate with Error",
+			name:        "Decorate/Error",
 			give:        &Decorated{Err: someError},
 			wantMessage: "error encountered while applying options",
 			wantFields: map[string]interface{}{
@@ -228,7 +230,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "StartError",
+			name:        "Start/Error",
 			give:        &Started{Err: someError},
 			wantMessage: "start failed",
 			wantFields: map[string]interface{}{
@@ -244,7 +246,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "Stopped",
+			name:        "Stopped/Error",
 			give:        &Stopped{Err: someError},
 			wantMessage: "stop failed",
 			wantFields: map[string]interface{}{
@@ -252,7 +254,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "RollingBack",
+			name:        "RollingBack/Error",
 			give:        &RollingBack{StartErr: someError},
 			wantMessage: "start failed, rolling back",
 			wantFields: map[string]interface{}{
@@ -260,7 +262,7 @@ func TestZapLogger(t *testing.T) {
 			},
 		},
 		{
-			name:        "RolledBackError",
+			name:        "RolledBack/Error",
 			give:        &RolledBack{Err: someError},
 			wantMessage: "rollback failed",
 			wantFields: map[string]interface{}{
@@ -274,7 +276,7 @@ func TestZapLogger(t *testing.T) {
 			wantFields:  map[string]interface{}{},
 		},
 		{
-			name:        "LoggerInitialized Error",
+			name:        "LoggerInitialized/Error",
 			give:        &LoggerInitialized{Err: someError},
 			wantMessage: "custom logger initialization failed",
 			wantFields: map[string]interface{}{
@@ -291,20 +293,119 @@ func TestZapLogger(t *testing.T) {
 		},
 	}
 
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
+	t.Run("debug observer, log at default (info)", func(t *testing.T) {
+		for _, tt := range tests {
+			tt := tt
+			t.Run(tt.name, func(t *testing.T) {
+				t.Parallel()
 
-			core, observedLogs := observer.New(zap.DebugLevel)
-			(&ZapLogger{Logger: zap.New(core)}).LogEvent(tt.give)
+				core, observedLogs := observer.New(zap.DebugLevel)
+				(&ZapLogger{Logger: zap.New(core)}).LogEvent(tt.give)
 
+				logs := observedLogs.TakeAll()
+				require.Len(t, logs, 1)
+				got := logs[0]
+
+				assert.Equal(t, tt.wantMessage, got.Message)
+				assert.Equal(t, tt.wantFields, got.ContextMap())
+			})
+		}
+	})
+
+	t.Run("info observer, log at debug", func(t *testing.T) {
+		for _, tt := range tests {
+			tt := tt
+			t.Run(tt.name, func(t *testing.T) {
+				t.Parallel()
+
+				core, observedLogs := observer.New(zap.InfoLevel)
+				l := &ZapLogger{Logger: zap.New(core)}
+				l.UseLogLevel(zapcore.DebugLevel)
+				l.LogEvent(tt.give)
+
+				logs := observedLogs.TakeAll()
+				// logs are not visible unless they are errors
+				if strings.HasSuffix(tt.name, "/Error") {
+					require.Len(t, logs, 1)
+					got := logs[0]
+					assert.Equal(t, tt.wantMessage, got.Message)
+					assert.Equal(t, tt.wantFields, got.ContextMap())
+				} else {
+					require.Len(t, logs, 0)
+				}
+			})
+		}
+	})
+
+	t.Run("info observer, log/error at debug", func(t *testing.T) {
+		for _, tt := range tests {
+			tt := tt
+			t.Run(tt.name, func(t *testing.T) {
+				t.Parallel()
+
+				core, observedLogs := observer.New(zap.InfoLevel)
+				l := &ZapLogger{Logger: zap.New(core)}
+				l.UseLogLevel(zapcore.DebugLevel)
+				l.UseErrorLevel(zapcore.DebugLevel)
+				l.LogEvent(tt.give)
+
+				logs := observedLogs.TakeAll()
+				require.Len(t, logs, 0, "no logs should be visible")
+			})
+		}
+	})
+
+	t.Run("test setting log levels", func(t *testing.T) {
+		levels := []zapcore.Level{
+			zapcore.DebugLevel,
+			zapcore.WarnLevel,
+			zapcore.DPanicLevel,
+			zapcore.PanicLevel,
+		}
+
+		for _, level := range levels {
+			core, observedLogs := observer.New(level)
+			logger := &ZapLogger{Logger: zap.New(core)}
+			logger.UseLogLevel(level)
+			func() {
+				defer func() {
+					recover()
+				}()
+				logger.LogEvent(&OnStartExecuting{
+					FunctionName: "hook.onStart",
+					CallerName:   "bytes.NewBuffer",
+				})
+			}()
 			logs := observedLogs.TakeAll()
 			require.Len(t, logs, 1)
-			got := logs[0]
+		}
+	})
 
-			assert.Equal(t, tt.wantMessage, got.Message)
-			assert.Equal(t, tt.wantFields, got.ContextMap())
-		})
-	}
+	t.Run("test setting error log levels", func(t *testing.T) {
+		levels := []zapcore.Level{
+			zapcore.DebugLevel,
+			zapcore.WarnLevel,
+			zapcore.DPanicLevel,
+			zapcore.PanicLevel,
+			zapcore.FatalLevel,
+		}
+
+		for _, level := range levels {
+			core, observedLogs := observer.New(level)
+			logger := &ZapLogger{Logger: zap.New(core, zap.WithFatalHook(zapcore.WriteThenPanic))}
+			logger.UseErrorLevel(level)
+			func() {
+				defer func() {
+					recover()
+				}()
+				logger.LogEvent(&OnStopExecuted{
+					FunctionName: "hook.onStart1",
+					CallerName:   "bytes.NewBuffer",
+					Err:          fmt.Errorf("some error"),
+				})
+			}()
+			logs := observedLogs.TakeAll()
+			require.Len(t, logs, 1)
+		}
+	})
 }