Repository: go-kratos/kratos
Pull request #2403 (2022-09-24 13:38:20)

feat: add app Before and Afters option
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a semantic commits format title, such as `<type>[optional scope]: <description>`, see: https://go-kratos.dev/docs/community/contribution#type
5. at the same time, please note that similar work should be submitted in one PR as far as possible to reduce the workload of reviewers. Do not split a work into multiple PR unless it should.
-->

<!--
🎉 感谢您向 Kratos 发送 PR 请求！以下是一些提示：
如果这是你第一次为 Kratos 贡献，请阅读我们的贡献指南：https://go-kratos.dev/en/docs/community/contribution/
2、确保您已经为您的 PR 添加或运行了适当的测试和lint，请在提交PR之前使用“make lint”和“make test”，使用“make clean”整理您的 go.mod。
3、如果请购单未完成，您可能需要将其标记为 WIP（在制品）PR 或 draft PR
4、请使用语义提交格式标题，如“<类型>[可选范围]：<说明>`，请参阅：https://go-kratos.dev/docs/community/contribution#type
5. 同时请注意，同类的工作请尽量在一个PR中提交，以减轻 review 者的工作负担，不要把一项工作拆分成很多个PR，除非它应该这样做。
-->

<!--
-->

#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->

add app Before and Afters option

#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If your PR is not fully resolved the issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->


#### Other special notes for the reviewers:
<!--
* Somethings that need extra attention for the reviewers
* Some additional notes, TODO list, etc.
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2403?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2403](https://codecov.io/gh/go-kratos/kratos/pull/2403?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (eee8e48) into [main](https://codecov.io/gh/go-kratos/kratos/commit/8743f3e50c4039679de72a365c9d5246dfda3bbc?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (8743f3e) will **decrease** coverage by `0.01%`.
> The diff coverage is `83.33%`.

> :exclamation: Current head eee8e48 differs from pull request most recent head 06c3ffd. Consider uploading reports for the commit 06c3ffd to get more accurate results

```diff
@@            Coverage Diff             @@
##             main    #2403      +/-   ##
==========================================
- Coverage   84.95%   84.94%   -0.02%     
==========================================
  Files          88       88              
  Lines        3788     3812      +24     
==========================================
+ Hits         3218     3238      +20     
- Misses        402      404       +2     
- Partials      168      170       +2     
```


| [Impacted Files](https://codecov.io/gh/go-kratos/kratos/pull/2403?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) | Coverage Δ | |
|---|---|---|
| [app.go](https://codecov.io/gh/go-kratos/kratos/pull/2403/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-YXBwLmdv) | `83.78% <72.22%> (-2.08%)` | :arrow_down: |
| [options.go](https://codecov.io/gh/go-kratos/kratos/pull/2403/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos#diff-b3B0aW9ucy5nbw==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)


Patch:
diff --git a/app.go b/app.go
index e3d81ffb..436be294 100644
--- a/app.go
+++ b/app.go
@@ -89,8 +89,15 @@ func (a *App) Run() error {
 	a.mu.Lock()
 	a.instance = instance
 	a.mu.Unlock()
-	eg, ctx := errgroup.WithContext(NewContext(a.ctx, a))
+	sctx := NewContext(a.ctx, a)
+	eg, ctx := errgroup.WithContext(sctx)
 	wg := sync.WaitGroup{}
+
+	for _, fn := range a.opts.beforeStart {
+		if err = fn(sctx); err != nil {
+			return err
+		}
+	}
 	for _, srv := range a.opts.servers {
 		srv := srv
 		eg.Go(func() error {
@@ -102,17 +109,23 @@ func (a *App) Run() error {
 		wg.Add(1)
 		eg.Go(func() error {
 			wg.Done() // here is to ensure server start has begun running before register, so defer is not needed
-			return srv.Start(NewContext(a.opts.ctx, a))
+			return srv.Start(sctx)
 		})
 	}
 	wg.Wait()
 	if a.opts.registrar != nil {
 		rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout)
 		defer rcancel()
-		if err := a.opts.registrar.Register(rctx, instance); err != nil {
+		if err = a.opts.registrar.Register(rctx, instance); err != nil {
+			return err
+		}
+	}
+	for _, fn := range a.opts.afterStart {
+		if err = fn(sctx); err != nil {
 			return err
 		}
 	}
+
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, a.opts.sigs...)
 	eg.Go(func() error {
@@ -123,28 +136,36 @@ func (a *App) Run() error {
 			return a.Stop()
 		}
 	})
-	if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
+	if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
 		return err
 	}
-	return nil
+	for _, fn := range a.opts.afterStop {
+		err = fn(sctx)
+	}
+	return err
 }
 
 // Stop gracefully stops the application.
-func (a *App) Stop() error {
+func (a *App) Stop() (err error) {
+	sctx := NewContext(a.ctx, a)
+	for _, fn := range a.opts.beforeStop {
+		err = fn(sctx)
+	}
+
 	a.mu.Lock()
 	instance := a.instance
 	a.mu.Unlock()
 	if a.opts.registrar != nil && instance != nil {
 		ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout)
 		defer cancel()
-		if err := a.opts.registrar.Deregister(ctx, instance); err != nil {
+		if err = a.opts.registrar.Deregister(ctx, instance); err != nil {
 			return err
 		}
 	}
 	if a.cancel != nil {
 		a.cancel()
 	}
-	return nil
+	return err
 }
 
 func (a *App) buildInstance() (*registry.ServiceInstance, error) {
diff --git a/config/config.go b/config/config.go
index 126a6379..9efc9024 100644
--- a/config/config.go
+++ b/config/config.go
@@ -15,13 +15,13 @@ import (
 	"github.com/go-kratos/kratos/v2/log"
 )
 
+var _ Config = (*config)(nil)
+
 var (
 	// ErrNotFound is key not found.
 	ErrNotFound = errors.New("key not found")
 	// ErrTypeAssert is type assert error.
 	ErrTypeAssert = errors.New("type assert error")
-
-	_ Config = (*config)(nil)
 )
 
 // Observer is config observer.
@@ -44,7 +44,7 @@ type config struct {
 	watchers  []Watcher
 }
 
-// New new a config with options.
+// New a config with options.
 func New(opts ...Option) Config {
 	o := options{
 		decoder:  defaultDecoder,
@@ -62,11 +62,11 @@ func New(opts ...Option) Config {
 func (c *config) watch(w Watcher) {
 	for {
 		kvs, err := w.Next()
-		if errors.Is(err, context.Canceled) {
-			log.Infof("watcher's ctx cancel : %v", err)
-			return
-		}
 		if err != nil {
+			if errors.Is(err, context.Canceled) {
+				log.Infof("watcher's ctx cancel : %v", err)
+				return
+			}
 			time.Sleep(time.Second)
 			log.Errorf("failed to watch next config: %v", err)
 			continue
diff --git a/config/env/env.go b/config/env/env.go
index 30e011b9..cdd30e1b 100644
--- a/config/env/env.go
+++ b/config/env/env.go
@@ -19,11 +19,11 @@ func (e *env) Load() (kv []*config.KeyValue, err error) {
 	return e.load(os.Environ()), nil
 }
 
-func (e *env) load(envStrings []string) []*config.KeyValue {
+func (e *env) load(envs []string) []*config.KeyValue {
 	var kv []*config.KeyValue
-	for _, envstr := range envStrings {
+	for _, env := range envs {
 		var k, v string
-		subs := strings.SplitN(envstr, "=", 2) //nolint:gomnd
+		subs := strings.SplitN(env, "=", 2) //nolint:gomnd
 		k = subs[0]
 		if len(subs) > 1 {
 			v = subs[1]
diff --git a/config/env/watcher.go b/config/env/watcher.go
index 2ea02931..84e7932e 100644
--- a/config/env/watcher.go
+++ b/config/env/watcher.go
@@ -6,13 +6,13 @@ import (
 	"github.com/go-kratos/kratos/v2/config"
 )
 
+var _ config.Watcher = (*watcher)(nil)
+
 type watcher struct {
 	ctx    context.Context
 	cancel context.CancelFunc
 }
 
-var _ config.Watcher = (*watcher)(nil)
-
 func NewWatcher() (config.Watcher, error) {
 	ctx, cancel := context.WithCancel(context.Background())
 	return &watcher{ctx: ctx, cancel: cancel}, nil
diff --git a/config/file/watcher.go b/config/file/watcher.go
index ea309fa8..1f067d1a 100644
--- a/config/file/watcher.go
+++ b/config/file/watcher.go
@@ -10,6 +10,8 @@ import (
 	"github.com/go-kratos/kratos/v2/config"
 )
 
+var _ config.Watcher = (*watcher)(nil)
+
 type watcher struct {
 	f  *file
 	fw *fsnotify.Watcher
@@ -18,8 +20,6 @@ type watcher struct {
 	cancel context.CancelFunc
 }
 
-var _ config.Watcher = (*watcher)(nil)
-
 func newWatcher(f *file) (config.Watcher, error) {
 	fw, err := fsnotify.NewWatcher()
 	if err != nil {
diff --git a/config/reader.go b/config/reader.go
index 6858a0f6..2202de6b 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -8,11 +8,11 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/go-kratos/kratos/v2/log"
-
 	"github.com/imdario/mergo"
 	"google.golang.org/protobuf/encoding/protojson"
 	"google.golang.org/protobuf/proto"
+
+	"github.com/go-kratos/kratos/v2/log"
 )
 
 // Reader is config reader.
@@ -38,9 +38,7 @@ func newReader(opts options) Reader {
 }
 
 func (r *reader) Merge(kvs ...*KeyValue) error {
-	r.lock.Lock()
-	merged, err := cloneMap(r.values)
-	r.lock.Unlock()
+	merged, err := r.cloneMap()
 	if err != nil {
 		return err
 	}
@@ -79,6 +77,12 @@ func (r *reader) Resolve() error {
 	return r.opts.resolver(r.values)
 }
 
+func (r *reader) cloneMap() (map[string]interface{}, error) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	return cloneMap(r.values)
+}
+
 func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
 	// https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d
 	var buf bytes.Buffer
@@ -90,12 +94,12 @@ func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
 	if err != nil {
 		return nil, err
 	}
-	var copy map[string]interface{}
-	err = dec.Decode(&copy)
+	var clone map[string]interface{}
+	err = dec.Decode(&clone)
 	if err != nil {
 		return nil, err
 	}
-	return copy, nil
+	return clone, nil
 }
 
 func convertMap(src interface{}) interface{} {
diff --git a/config/value.go b/config/value.go
index 9f6234f5..11de2e89 100644
--- a/config/value.go
+++ b/config/value.go
@@ -1,7 +1,7 @@
 package config
 
 import (
-	stdjson "encoding/json"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"strconv"
@@ -10,7 +10,7 @@ import (
 
 	"google.golang.org/protobuf/proto"
 
-	"github.com/go-kratos/kratos/v2/encoding/json"
+	kratosjson "github.com/go-kratos/kratos/v2/encoding/json"
 )
 
 var (
@@ -36,6 +36,10 @@ type atomicValue struct {
 	atomic.Value
 }
 
+func (v *atomicValue) typeAssertError() error {
+	return fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+}
+
 func (v *atomicValue) Bool() (bool, error) {
 	switch val := v.Load().(type) {
 	case bool:
@@ -43,7 +47,7 @@ func (v *atomicValue) Bool() (bool, error) {
 	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string:
 		return strconv.ParseBool(fmt.Sprint(val))
 	}
-	return false, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	return false, v.typeAssertError()
 }
 
 func (v *atomicValue) Int() (int64, error) {
@@ -73,35 +77,37 @@ func (v *atomicValue) Int() (int64, error) {
 	case float64:
 		return int64(val), nil
 	case string:
-		return strconv.ParseInt(val, 10, 64) //nolint:gomnd
+		return strconv.ParseInt(val, 10, 64)
 	}
-	return 0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	return 0, v.typeAssertError()
 }
 
 func (v *atomicValue) Slice() ([]Value, error) {
-	if vals, ok := v.Load().([]interface{}); ok {
-		var slices []Value
-		for _, val := range vals {
-			a := &atomicValue{}
-			a.Store(val)
-			slices = append(slices, a)
-		}
-		return slices, nil
+	vals, ok := v.Load().([]interface{})
+	if !ok {
+		return nil, v.typeAssertError()
 	}
-	return nil, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	slices := make([]Value, 0, len(vals))
+	for _, val := range vals {
+		a := new(atomicValue)
+		a.Store(val)
+		slices = append(slices, a)
+	}
+	return slices, nil
 }
 
 func (v *atomicValue) Map() (map[string]Value, error) {
-	if vals, ok := v.Load().(map[string]interface{}); ok {
-		m := make(map[string]Value, len(vals))
-		for key, val := range vals {
-			a := new(atomicValue)
-			a.Store(val)
-			m[key] = a
-		}
-		return m, nil
+	vals, ok := v.Load().(map[string]interface{})
+	if !ok {
+		return nil, v.typeAssertError()
+	}
+	m := make(map[string]Value, len(vals))
+	for key, val := range vals {
+		a := new(atomicValue)
+		a.Store(val)
+		m[key] = a
 	}
-	return nil, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	return m, nil
 }
 
 func (v *atomicValue) Float() (float64, error) {
@@ -131,9 +137,9 @@ func (v *atomicValue) Float() (float64, error) {
 	case float64:
 		return val, nil
 	case string:
-		return strconv.ParseFloat(val, 64) //nolint:gomnd
+		return strconv.ParseFloat(val, 64)
 	}
-	return 0.0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	return 0.0, v.typeAssertError()
 }
 
 func (v *atomicValue) String() (string, error) {
@@ -147,7 +153,7 @@ func (v *atomicValue) String() (string, error) {
 	case fmt.Stringer:
 		return val.String(), nil
 	}
-	return "", fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
+	return "", v.typeAssertError()
 }
 
 func (v *atomicValue) Duration() (time.Duration, error) {
@@ -159,14 +165,14 @@ func (v *atomicValue) Duration() (time.Duration, error) {
 }
 
 func (v *atomicValue) Scan(obj interface{}) error {
-	data, err := stdjson.Marshal(v.Load())
+	data, err := json.Marshal(v.Load())
 	if err != nil {
 		return err
 	}
 	if pb, ok := obj.(proto.Message); ok {
-		return json.UnmarshalOptions.Unmarshal(data, pb)
+		return kratosjson.UnmarshalOptions.Unmarshal(data, pb)
 	}
-	return stdjson.Unmarshal(data, obj)
+	return json.Unmarshal(data, obj)
 }
 
 type errValue struct {
diff --git a/contrib/config/apollo/README.md b/contrib/config/apollo/README.md
index c03cff0f..a8eea493 100644
--- a/contrib/config/apollo/README.md
+++ b/contrib/config/apollo/README.md
@@ -97,5 +97,4 @@ config := map[string]interface{}{
         }
     }
 }
-_ = config
 ```
diff --git a/contrib/config/apollo/apollo.go b/contrib/config/apollo/apollo.go
index 259ed1a9..bd96ef11 100644
--- a/contrib/config/apollo/apollo.go
+++ b/contrib/config/apollo/apollo.go
@@ -3,15 +3,14 @@ package apollo
 import (
 	"strings"
 
-	"github.com/go-kratos/kratos/v2/config"
-	"github.com/go-kratos/kratos/v2/log"
-
 	"github.com/apolloconfig/agollo/v4"
 	"github.com/apolloconfig/agollo/v4/constant"
 	apolloConfig "github.com/apolloconfig/agollo/v4/env/config"
 	"github.com/apolloconfig/agollo/v4/extension"
 
+	"github.com/go-kratos/kratos/v2/config"
 	"github.com/go-kratos/kratos/v2/encoding"
+	"github.com/go-kratos/kratos/v2/log"
 )
 
 type apollo struct {
diff --git a/contrib/config/apollo/watcher.go b/contrib/config/apollo/watcher.go
index 94ef2ef0..ea5ef108 100644
--- a/contrib/config/apollo/watcher.go
+++ b/contrib/config/apollo/watcher.go
@@ -4,12 +4,11 @@ import (
 	"context"
 	"strings"
 
-	"github.com/go-kratos/kratos/v2/encoding"
+	"github.com/apolloconfig/agollo/v4/storage"
 
 	"github.com/go-kratos/kratos/v2/config"
+	"github.com/go-kratos/kratos/v2/encoding"
 	"github.com/go-kratos/kratos/v2/log"
-
-	"github.com/apolloconfig/agollo/v4/storage"
 )
 
 type watcher struct {
diff --git a/contrib/config/consul/README.md b/contrib/config/consul/README.md
index a3d39a18..9dc5a066 100644
--- a/contrib/config/consul/README.md
+++ b/contrib/config/consul/README.md
@@ -2,20 +2,21 @@
 
 ```go
 import (
-    "github.com/go-kratos/kratos/contrib/config/consul/v2"
     "github.com/hashicorp/consul/api"
+
+    "github.com/go-kratos/kratos/contrib/config/consul/v2"
 )
-func main() {
 
+func main() {
     consulClient, err := api.NewClient(&api.Config{
-    Address: "127.0.0.1:8500",
+        Address: "127.0.0.1:8500",
     })
     if err != nil {
         panic(err)
     }
     cs, err := consul.New(consulClient, consul.WithPath("app/cart/configs/"))
-    //consul中需要标注文件后缀，kratos读取配置需要适配文件后缀
-    //The file suffix needs to be marked, and kratos needs to adapt the file suffix to read the configuration.
+    // consul中需要标注文件后缀，kratos读取配置需要适配文件后缀
+    // The file suffix needs to be marked, and kratos needs to adapt the file suffix to read the configuration.
     if err != nil {
         panic(err)
     }
diff --git a/contrib/config/etcd/README.md b/contrib/config/etcd/README.md
index d69edd82..131619c1 100644
--- a/contrib/config/etcd/README.md
+++ b/contrib/config/etcd/README.md
@@ -4,10 +4,11 @@
 import (
 	"log"
 
-	cfg "github.com/go-kratos/kratos/contrib/config/etcd/v2"
-	"github.com/go-kratos/kratos/v2/config"
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"google.golang.org/grpc"
+
+	cfg "github.com/go-kratos/kratos/contrib/config/etcd/v2"
+	"github.com/go-kratos/kratos/v2/config"
 )
 
 // create an etcd client
diff --git a/contrib/config/nacos/README.md b/contrib/config/nacos/README.md
index 71f186bf..fa54caf0 100644
--- a/contrib/config/nacos/README.md
+++ b/contrib/config/nacos/README.md
@@ -2,9 +2,10 @@
 
 ```go
 import (
-	kconfig "github.com/go-kratos/kratos/v2/config"
 	"github.com/nacos-group/nacos-sdk-go/clients"
 	"github.com/nacos-group/nacos-sdk-go/common/constant"
+
+	kconfig "github.com/go-kratos/kratos/v2/config"
 )
 
 
diff --git a/contrib/config/nacos/go.mod b/contrib/config/nacos/go.mod
index 7890e058..6917f24a 100644
--- a/contrib/config/nacos/go.mod
+++ b/contrib/config/nacos/go.mod
@@ -5,6 +5,9 @@ go 1.16
 require (
 	github.com/go-kratos/kratos/v2 v2.4.0
 	github.com/nacos-group/nacos-sdk-go v1.0.9
+)
+
+require (
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
diff --git a/contrib/config/polaris/README.md b/contrib/config/polaris/README.md
index 4c2694c4..b39e78f2 100644
--- a/contrib/config/polaris/README.md
+++ b/contrib/config/polaris/README.md
@@ -2,24 +2,23 @@
 
 ```go
 import (
- "log"
-
- "github.com/polarismesh/polaris-go"
- "github.com/go-kratos/kratos/contrib/config/polaris/v2"
+	"log"
+	
+	"github.com/polarismesh/polaris-go"
+	
+	"github.com/go-kratos/kratos/contrib/config/polaris/v2"
 )
 
 func main() {
+    configApi, err := polaris.NewConfigAPI()
+    if err != nil {
+    	log.Fatalln(err)
+    }
 
- configApi, err := polaris.NewConfigAPI()
- if err != nil {
-  log.Fatalln(err)
- }
-
- source, err := New(&configApi, WithNamespace("default"), WithFileGroup("default"), WithFileName("default.yaml"))
-
- if err != nil {
-  log.Fatalln(err)
- }
- source.Load()
+    source, err := New(&configApi, WithNamespace("default"), WithFileGroup("default"), WithFileName("default.yaml"))
+    if err != nil {
+    	log.Fatalln(err)
+    }
+    source.Load()
 }
 ```
diff --git a/contrib/config/polaris/config.go b/contrib/config/polaris/config.go
index c3c39ece..288db438 100644
--- a/contrib/config/polaris/config.go
+++ b/contrib/config/polaris/config.go
@@ -6,9 +6,9 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/go-kratos/kratos/v2/config"
-
 	"github.com/polarismesh/polaris-go"
+
+	"github.com/go-kratos/kratos/v2/config"
 )
 
 // Option is polaris config option.
diff --git a/contrib/config/polaris/watcher.go b/contrib/config/polaris/watcher.go
index dcd89055..70305550 100644
--- a/contrib/config/polaris/watcher.go
+++ b/contrib/config/polaris/watcher.go
@@ -5,11 +5,11 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/go-kratos/kratos/v2/config"
-	"github.com/go-kratos/kratos/v2/log"
-
 	"github.com/polarismesh/polaris-go"
 	"github.com/polarismesh/polaris-go/pkg/model"
+
+	"github.com/go-kratos/kratos/v2/config"
+	"github.com/go-kratos/kratos/v2/log"
 )
 
 type Watcher struct {
@@ -28,7 +28,7 @@ func getFullPath(namespace string, fileGroup string, fileName string) string {
 	return fmt.Sprintf("%s/%s/%s", namespace, fileGroup, fileName)
 }
 
-func recieve(event model.ConfigFileChangeEvent) {
+func receive(event model.ConfigFileChangeEvent) {
 	meta := event.ConfigFileMetadata
 	ec := eventChanMap[getFullPath(meta.GetNamespace(), meta.GetFileGroup(), meta.GetFileName())]
 	defer func() {
@@ -42,7 +42,7 @@ func recieve(event model.ConfigFileChangeEvent) {
 }
 
 func newWatcher(configFile polaris.ConfigFile) *Watcher {
-	configFile.AddChangeListener(recieve)
+	configFile.AddChangeListener(receive)
 
 	fullPath := getFullPath(configFile.GetNamespace(), configFile.GetFileGroup(), configFile.GetFileName())
 	if _, ok := eventChanMap[fullPath]; !ok {
diff --git a/contrib/log/aliyun/go.mod b/contrib/log/aliyun/go.mod
index 83029a5d..1c7b5fa8 100644
--- a/contrib/log/aliyun/go.mod
+++ b/contrib/log/aliyun/go.mod
@@ -3,7 +3,7 @@ module github.com/go-kratos/kratos/contrib/log/aliyun/v2
 go 1.16
 
 require (
-	github.com/aliyun/aliyun-log-go-sdk v0.1.37
+	github.com/aliyun/aliyun-log-go-sdk v0.1.39
 	github.com/go-kratos/kratos/v2 v2.5.2
 	google.golang.org/protobuf v1.28.1
 )
diff --git a/contrib/log/aliyun/go.sum b/contrib/log/aliyun/go.sum
index 6fde6344..ab2ac488 100644
--- a/contrib/log/aliyun/go.sum
+++ b/contrib/log/aliyun/go.sum
@@ -12,8 +12,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/aliyun/aliyun-log-go-sdk v0.1.37 h1:GvswbgLqVOHNeMWssQ9zA+R7YVDP6arLUP92bKyGZNw=
-github.com/aliyun/aliyun-log-go-sdk v0.1.37/go.mod h1:1QQ59pEJiVVXqKgbHcU6FWIgxT5RKBt+CT8AiQ2bEts=
+github.com/aliyun/aliyun-log-go-sdk v0.1.39 h1:oVLVcRbtOhE4a1D4HhO3OYPR6wMADl3ldcbVeFrRL8Y=
+github.com/aliyun/aliyun-log-go-sdk v0.1.39/go.mod h1:1QQ59pEJiVVXqKgbHcU6FWIgxT5RKBt+CT8AiQ2bEts=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
diff --git a/contrib/metrics/datadog/go.mod b/contrib/metrics/datadog/go.mod
index 42d43453..aebfd094 100644
--- a/contrib/metrics/datadog/go.mod
+++ b/contrib/metrics/datadog/go.mod
@@ -4,8 +4,11 @@ go 1.16
 
 require (
 	github.com/DataDog/datadog-go v4.8.3+incompatible
-	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/go-kratos/kratos/v2 v2.5.2
 )
 
+require (
+	github.com/Microsoft/go-winio v0.5.2 // indirect
+)
+
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/registry/consul/go.mod b/contrib/registry/consul/go.mod
index db17f9cc..c94149ce 100644
--- a/contrib/registry/consul/go.mod
+++ b/contrib/registry/consul/go.mod
@@ -4,7 +4,7 @@ go 1.16
 
 require (
 	github.com/go-kratos/kratos/v2 v2.5.2
-	github.com/hashicorp/consul/api v1.14.0
+	github.com/hashicorp/consul/api v1.15.3
 )
 
 replace github.com/go-kratos/kratos/v2 => ../../../
diff --git a/contrib/registry/consul/go.sum b/contrib/registry/consul/go.sum
index 41a5b15b..b1d94b2a 100644
--- a/contrib/registry/consul/go.sum
+++ b/contrib/registry/consul/go.sum
@@ -91,10 +91,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.14.0 h1:Y64GIJ8hYTu+tuGekwO4G4ardXoiCivX9wv1iP/kihk=
-github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ=
-github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI=
-github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
+github.com/hashicorp/consul/api v1.15.3 h1:WYONYL2rxTXtlekAqblR2SCdJsizMDIj/uXb5wNy9zU=
+github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY=
+github.com/hashicorp/consul/sdk v0.11.0 h1:HRzj8YSCln2yGgCumN5CL8lYlD3gBurnervJRJAZyC4=
+github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
diff --git a/log/filter.go b/log/filter.go
index ab572377..993fa926 100644
--- a/log/filter.go
+++ b/log/filter.go
@@ -64,20 +64,18 @@ func (f *Filter) Log(level Level, keyvals ...interface{}) error {
 	if level < f.level {
 		return nil
 	}
-	// fkv is used to provide a slice to contains both logger.prefix and keyvals for filter
-	var fkv []interface{}
+	// prefixkv contains the slice of arguments defined as prefixes during the log initialization
+	var prefixkv []interface{}
 	l, ok := f.logger.(*logger)
 	if ok && len(l.prefix) > 0 {
-		fkv = make([]interface{}, 0, len(l.prefix)+len(keyvals))
-		fkv = append(fkv, l.prefix...)
-		fkv = append(fkv, keyvals...)
+		prefixkv = make([]interface{}, 0, len(l.prefix))
+		prefixkv = append(prefixkv, l.prefix...)
 	}
-	if !ok {
-		fkv = keyvals
-	}
-	if f.filter != nil && f.filter(level, fkv...) {
+
+	if f.filter != nil && (f.filter(level, prefixkv...) || f.filter(level, keyvals...)) {
 		return nil
 	}
+
 	if len(f.key) > 0 || len(f.value) > 0 {
 		for i := 0; i < len(keyvals); i += 2 {
 			v := i + 1
diff --git a/options.go b/options.go
index 8f9e9a55..5e337a38 100644
--- a/options.go
+++ b/options.go
@@ -30,6 +30,12 @@ type options struct {
 	registrarTimeout time.Duration
 	stopTimeout      time.Duration
 	servers          []transport.Server
+
+	// Before and After funcs
+	beforeStart []func(context.Context) error
+	beforeStop  []func(context.Context) error
+	afterStart  []func(context.Context) error
+	afterStop   []func(context.Context) error
 }
 
 // ID with service id.
@@ -91,3 +97,33 @@ func RegistrarTimeout(t time.Duration) Option {
 func StopTimeout(t time.Duration) Option {
 	return func(o *options) { o.stopTimeout = t }
 }
+
+// Before and Afters
+
+// BeforeStart run funcs before app starts
+func BeforeStart(fn func(context.Context) error) Option {
+	return func(o *options) {
+		o.beforeStart = append(o.beforeStart, fn)
+	}
+}
+
+// BeforeStop run funcs before app stops
+func BeforeStop(fn func(context.Context) error) Option {
+	return func(o *options) {
+		o.beforeStop = append(o.beforeStop, fn)
+	}
+}
+
+// AfterStart run funcs after app starts
+func AfterStart(fn func(context.Context) error) Option {
+	return func(o *options) {
+		o.afterStart = append(o.afterStart, fn)
+	}
+}
+
+// AfterStop run funcs after app stops
+func AfterStop(fn func(context.Context) error) Option {
+	return func(o *options) {
+		o.afterStop = append(o.afterStop, fn)
+	}
+}
diff --git a/selector/node/ewma/node.go b/selector/node/ewma/node.go
index 46d5c41c..3b8d2a02 100644
--- a/selector/node/ewma/node.go
+++ b/selector/node/ewma/node.go
@@ -161,7 +161,7 @@ func (n *Node) Pick() selector.DoneFunc {
 			}
 			var netErr net.Error
 			if errors.Is(context.DeadlineExceeded, di.Err) || errors.Is(context.Canceled, di.Err) ||
-				errors.IsServiceUnavailable(di.Err) || errors.IsGatewayTimeout(di.Err) || errors.As(di.Err, netErr) {
+				errors.IsServiceUnavailable(di.Err) || errors.IsGatewayTimeout(di.Err) || errors.As(di.Err, &netErr) {
 				success = 0
 			}
 		}


Test patch:
diff --git a/app_test.go b/app_test.go
index 2b8c8c9d..92a2ca29 100644
--- a/app_test.go
+++ b/app_test.go
@@ -47,6 +47,22 @@ func TestApp(t *testing.T) {
 		Name("kratos"),
 		Version("v1.0.0"),
 		Server(hs, gs),
+		BeforeStart(func(_ context.Context) error {
+			t.Log("BeforeStart...")
+			return nil
+		}),
+		BeforeStop(func(_ context.Context) error {
+			t.Log("BeforeStop...")
+			return nil
+		}),
+		AfterStart(func(_ context.Context) error {
+			t.Log("AfterStart...")
+			return nil
+		}),
+		AfterStop(func(_ context.Context) error {
+			t.Log("AfterStop...")
+			return nil
+		}),
 		Registrar(&mockRegistry{service: make(map[string]*registry.ServiceInstance)}),
 	)
 	time.AfterFunc(time.Second, func() {
diff --git a/config/config_test.go b/config/config_test.go
index 5699dec1..9a166b00 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -2,7 +2,6 @@ package config
 
 import (
 	"errors"
-	"reflect"
 	"testing"
 )
 
@@ -124,7 +123,7 @@ func TestConfig(t *testing.T) {
 	)
 	err = c.Close()
 	if err != nil {
-		t.Fatal("t is not nil")
+		t.Fatal(err)
 	}
 
 	jSource := newTestJSONSource(_testJSON)
@@ -139,21 +138,21 @@ func TestConfig(t *testing.T) {
 
 	err = cf.Load()
 	if err != nil {
-		t.Fatal("t is not nil")
+		t.Fatal(err)
 	}
 
-	val, err := cf.Value("data.database.driver").String()
+	driver, err := cf.Value("data.database.driver").String()
 	if err != nil {
-		t.Fatal("t is not nil")
+		t.Fatal(err)
 	}
-	if !reflect.DeepEqual(databaseDriver, val) {
-		t.Fatal(`databaseDriver is not equal to val`)
+	if databaseDriver != driver {
+		t.Fatal("databaseDriver is not equal to val")
 	}
 
 	err = cf.Watch("endpoints", func(key string, value Value) {
 	})
 	if err != nil {
-		t.Fatal("t is not nil")
+		t.Fatal(err)
 	}
 
 	jSource.sig <- struct{}{}
@@ -162,24 +161,24 @@ func TestConfig(t *testing.T) {
 	var testConf testConfigStruct
 	err = cf.Scan(&testConf)
 	if err != nil {
-		t.Fatal("t is not nil")
+		t.Fatal(err)
 	}
-	if !reflect.DeepEqual(httpAddr, testConf.Server.HTTP.Addr) {
-		t.Fatal(`httpAddr is not equal to testConf.Server.HTTP.Addr`)
+	if httpAddr != testConf.Server.HTTP.Addr {
+		t.Errorf("testConf.Server.HTTP.Addr want: %s, got: %s", httpAddr, testConf.Server.HTTP.Addr)
 	}
-	if !reflect.DeepEqual(httpTimeout, testConf.Server.HTTP.Timeout) {
-		t.Fatal(`httpTimeout is not equal to testConf.Server.HTTP.Timeout`)
+	if httpTimeout != testConf.Server.HTTP.Timeout {
+		t.Errorf("testConf.Server.HTTP.Timeout want: %.1f, got: %.1f", httpTimeout, testConf.Server.HTTP.Timeout)
 	}
-	if !reflect.DeepEqual(true, testConf.Server.HTTP.EnableSSL) {
-		t.Fatal(`testConf.Server.HTTP.EnableSSL is not equal to true`)
+	if !testConf.Server.HTTP.EnableSSL {
+		t.Error("testConf.Server.HTTP.EnableSSL is not equal to true")
 	}
-	if !reflect.DeepEqual(grpcPort, testConf.Server.GRPC.Port) {
-		t.Fatal(`grpcPort is not equal to testConf.Server.GRPC.Port`)
+	if grpcPort != testConf.Server.GRPC.Port {
+		t.Errorf("testConf.Server.GRPC.Port want: %d, got: %d", grpcPort, testConf.Server.GRPC.Port)
 	}
-	if !reflect.DeepEqual(endpoint1, testConf.Endpoints[0]) {
-		t.Fatal(`endpoint1 is not equal to testConf.Endpoints[0]`)
+	if endpoint1 != testConf.Endpoints[0] {
+		t.Errorf("testConf.Endpoints[0] want: %s, got: %s", endpoint1, testConf.Endpoints[0])
 	}
-	if !reflect.DeepEqual(len(testConf.Endpoints), 2) {
-		t.Fatal(`len(testConf.Endpoints) is not equal to 2`)
+	if len(testConf.Endpoints) != 2 {
+		t.Error("len(testConf.Endpoints) is not equal to 2")
 	}
 }
diff --git a/config/file/file_test.go b/config/file/file_test.go
index 0eb2b24f..ac6ad843 100644
--- a/config/file/file_test.go
+++ b/config/file/file_test.go
@@ -121,10 +121,10 @@ func testWatchFile(t *testing.T, path string) {
 	}
 	kvs, err := watch.Next()
 	if err != nil {
-		t.Errorf(`watch.Next() error(%v)`, err)
+		t.Errorf("watch.Next() error(%v)", err)
 	}
 	if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) {
-		t.Errorf(`string(kvs[0].Value(%v) is  not equal to _testJSONUpdate(%v)`, kvs[0].Value, _testJSONUpdate)
+		t.Errorf("string(kvs[0].Value(%v) is  not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate)
 	}
 
 	newFilepath := filepath.Join(filepath.Dir(path), "test1.json")
@@ -133,15 +133,15 @@ func testWatchFile(t *testing.T, path string) {
 	}
 	kvs, err = watch.Next()
 	if err == nil {
-		t.Errorf(`watch.Next() error(%v)`, err)
+		t.Errorf("watch.Next() error(%v)", err)
 	}
 	if kvs != nil {
-		t.Errorf(`watch.Next() error(%v)`, err)
+		t.Errorf("watch.Next() error(%v)", err)
 	}
 
 	err = watch.Stop()
 	if err != nil {
-		t.Errorf(`watch.Stop() error(%v)`, err)
+		t.Errorf("watch.Stop() error(%v)", err)
 	}
 
 	if err := os.Rename(newFilepath, path); err != nil {
@@ -171,10 +171,10 @@ func testWatchDir(t *testing.T, path, file string) {
 
 	kvs, err := watch.Next()
 	if err != nil {
-		t.Errorf(`watch.Next() error(%v)`, err)
+		t.Errorf("watch.Next() error(%v)", err)
 	}
 	if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) {
-		t.Errorf(`string(kvs[0].Value(%v) is  not equal to _testJSONUpdate(%v)`, kvs[0].Value, _testJSONUpdate)
+		t.Errorf("string(kvs[0].Value(%s) is  not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate)
 	}
 }
 
diff --git a/config/file/format_test.go b/config/file/format_test.go
index 509a9837..75185cc6 100644
--- a/config/file/format_test.go
+++ b/config/file/format_test.go
@@ -1,6 +1,8 @@
 package file
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestFormat(t *testing.T) {
 	tests := []struct {
diff --git a/config/options_test.go b/config/options_test.go
index 51e8b48e..c8cd2bf8 100644
--- a/config/options_test.go
+++ b/config/options_test.go
@@ -15,7 +15,7 @@ func TestDefaultDecoder(t *testing.T) {
 	target := make(map[string]interface{})
 	err := defaultDecoder(src, target)
 	if err != nil {
-		t.Fatal("err is not nil")
+		t.Fatal(err)
 	}
 	if !reflect.DeepEqual(target, map[string]interface{}{"service": []byte("config")}) {
 		t.Fatal(`target is not equal to map[string]interface{}{"service": "config"}`)
@@ -29,7 +29,7 @@ func TestDefaultDecoder(t *testing.T) {
 	target = make(map[string]interface{})
 	err = defaultDecoder(src, target)
 	if err != nil {
-		t.Fatal("err is not nil")
+		t.Fatal(err)
 	}
 	if !reflect.DeepEqual(map[string]interface{}{
 		"service": map[string]interface{}{
@@ -150,7 +150,7 @@ func TestDefaultResolver(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			err := defaultResolver(data)
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			rd := reader{
 				values: data,
@@ -161,25 +161,25 @@ func TestDefaultResolver(t *testing.T) {
 				case int:
 					if actual, err = v.Int(); err == nil {
 						if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) {
-							t.Fatal(`expect is not equal to actual`)
+							t.Fatal("expect is not equal to actual")
 						}
 					}
 				case string:
 					if actual, err = v.String(); err == nil {
 						if !reflect.DeepEqual(test.expect, actual) {
-							t.Fatal(`expect is not equal to actual`)
+							t.Fatal("expect is not equal to actual")
 						}
 					}
 				case bool:
 					if actual, err = v.Bool(); err == nil {
 						if !reflect.DeepEqual(test.expect, actual) {
-							t.Fatal(`expect is not equal to actual`)
+							t.Fatal("expect is not equal to actual")
 						}
 					}
 				case float64:
 					if actual, err = v.Float(); err == nil {
 						if !reflect.DeepEqual(test.expect, actual) {
-							t.Fatal(`expect is not equal to actual`)
+							t.Fatal("expect is not equal to actual")
 						}
 					}
 				default:
diff --git a/config/reader_test.go b/config/reader_test.go
index 15f8f617..11b2d990 100644
--- a/config/reader_test.go
+++ b/config/reader_test.go
@@ -29,7 +29,7 @@ func TestReader_Merge(t *testing.T) {
 		Format: "json",
 	})
 	if err == nil {
-		t.Fatal(`err is nil`)
+		t.Fatal("err is nil")
 	}
 
 	err = r.Merge(&KeyValue{
@@ -38,15 +38,15 @@ func TestReader_Merge(t *testing.T) {
 		Format: "json",
 	})
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	vv, ok := r.Value("nice")
 	if !ok {
-		t.Fatal(`ok is false`)
+		t.Fatal("ok is false")
 	}
 	vvv, err := vv.String()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	if vvv != "boat" {
 		t.Fatal(`vvv is not equal to "boat"`)
@@ -58,18 +58,18 @@ func TestReader_Merge(t *testing.T) {
 		Format: "json",
 	})
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	vv, ok = r.Value("x")
 	if !ok {
-		t.Fatal(`ok is false`)
+		t.Fatal("ok is false")
 	}
 	vvx, err := vv.Int()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
-	if int64(2) != vvx {
-		t.Fatal(`vvx is not equal to 2`)
+	if vvx != 2 {
+		t.Fatal("vvx is not equal to 2")
 	}
 }
 
@@ -118,27 +118,27 @@ a:
 			r := newReader(opts)
 			err := r.Merge(&test.kv)
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			vv, ok := r.Value("a.b.X")
 			if !ok {
-				t.Fatal(`ok is false`)
+				t.Fatal("ok is false")
 			}
 			vvv, err := vv.Int()
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			if int64(1) != vvv {
-				t.Fatal(`vvv is not equal to 1`)
+				t.Fatal("vvv is not equal to 1")
 			}
 
 			vv, ok = r.Value("a.b.Y")
 			if !ok {
-				t.Fatal(`ok is false`)
+				t.Fatal("ok is false")
 			}
 			vvy, err := vv.String()
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			if vvy != "lol" {
 				t.Fatal(`vvy is not equal to "lol"`)
@@ -146,29 +146,29 @@ a:
 
 			vv, ok = r.Value("a.b.z")
 			if !ok {
-				t.Fatal(`ok is false`)
+				t.Fatal("ok is false")
 			}
 			vvz, err := vv.Bool()
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			if !vvz {
-				t.Fatal(`vvz is not equal to true`)
+				t.Fatal("vvz is not equal to true")
 			}
 
 			_, ok = r.Value("aasasdg=234l.asdfk,")
 			if ok {
-				t.Fatal(`ok is true`)
+				t.Fatal("ok is true")
 			}
 
 			_, ok = r.Value("aas......asdg=234l.asdfk,")
 			if ok {
-				t.Fatal(`ok is true`)
+				t.Fatal("ok is true")
 			}
 
 			_, ok = r.Value("a.b.Y.")
 			if ok {
-				t.Fatal(`ok is true`)
+				t.Fatal("ok is true")
 			}
 		})
 	}
@@ -192,11 +192,11 @@ func TestReader_Source(t *testing.T) {
 		Format: "json",
 	})
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	b, err := r.Source()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	if !reflect.DeepEqual([]byte(`{"a":{"b":{"X":1}}}`), b) {
 		t.Fatal("[]byte(`{\"a\":{\"b\":{\"X\":1}}}`) is not equal to b")
diff --git a/config/value_test.go b/config/value_test.go
index 68558bf8..df771757 100644
--- a/config/value_test.go
+++ b/config/value_test.go
@@ -6,17 +6,17 @@ import (
 	"time"
 )
 
-func Test_atomicValue_Bool(t *testing.T) {
+func TestAtomicValue_Bool(t *testing.T) {
 	vlist := []interface{}{"1", "t", "T", "true", "TRUE", "True", true, 1, int32(1)}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
 		b, err := v.Bool()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if !b {
-			t.Fatal(`b is not equal to true`)
+			t.Fatal("b is not equal to true")
 		}
 	}
 
@@ -26,10 +26,10 @@ func Test_atomicValue_Bool(t *testing.T) {
 		v.Store(x)
 		b, err := v.Bool()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b {
-			t.Fatal(`b is not equal to false`)
+			t.Fatal("b is not equal to false")
 		}
 	}
 
@@ -39,22 +39,22 @@ func Test_atomicValue_Bool(t *testing.T) {
 		v.Store(x)
 		_, err := v.Bool()
 		if err == nil {
-			t.Fatal(`err is nil`)
+			t.Fatal("err is nil")
 		}
 	}
 }
 
-func Test_atomicValue_Int(t *testing.T) {
+func TestAtomicValue_Int(t *testing.T) {
 	vlist := []interface{}{"123123", float64(123123), int64(123123), int32(123123), 123123}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
 		b, err := v.Int()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b != 123123 {
-			t.Fatal(`b is not equal to 123123`)
+			t.Fatal("b is not equal to 123123")
 		}
 	}
 
@@ -64,22 +64,22 @@ func Test_atomicValue_Int(t *testing.T) {
 		v.Store(x)
 		_, err := v.Int()
 		if err == nil {
-			t.Fatal(`err is nil`)
+			t.Fatal("err is nil")
 		}
 	}
 }
 
-func Test_atomicValue_Float(t *testing.T) {
+func TestAtomicValue_Float(t *testing.T) {
 	vlist := []interface{}{"123123.1", 123123.1}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
 		b, err := v.Float()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b != 123123.1 {
-			t.Fatal(`b is not equal to 123123.1`)
+			t.Fatal("b is not equal to 123123.1")
 		}
 	}
 
@@ -89,7 +89,7 @@ func Test_atomicValue_Float(t *testing.T) {
 		v.Store(x)
 		_, err := v.Float()
 		if err == nil {
-			t.Fatal(`err is nil`)
+			t.Fatal("err is nil")
 		}
 	}
 }
@@ -103,17 +103,17 @@ func (t ts) String() string {
 	return fmt.Sprintf("%s%d", t.Name, t.Age)
 }
 
-func Test_atomicValue_String(t *testing.T) {
+func TestAtomicValue_String(t *testing.T) {
 	vlist := []interface{}{"1", float64(1), int64(1), 1, int64(1)}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
 		b, err := v.String()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b != "1" {
-			t.Fatal(`b is not equal to 1`)
+			t.Fatal("b is not equal to 1")
 		}
 	}
 
@@ -121,7 +121,7 @@ func Test_atomicValue_String(t *testing.T) {
 	v.Store(true)
 	b, err := v.String()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	if b != "true" {
 		t.Fatal(`b is not equal to "true"`)
@@ -134,48 +134,48 @@ func Test_atomicValue_String(t *testing.T) {
 	})
 	b, err = v.String()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	if b != "test10" {
 		t.Fatal(`b is not equal to "test10"`)
 	}
 }
 
-func Test_atomicValue_Duration(t *testing.T) {
+func TestAtomicValue_Duration(t *testing.T) {
 	vlist := []interface{}{int64(5)}
 	for _, x := range vlist {
 		v := atomicValue{}
 		v.Store(x)
 		b, err := v.Duration()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b != time.Duration(5) {
-			t.Fatal(`b is not equal to time.Duration(5)`)
+			t.Fatal("b is not equal to time.Duration(5)")
 		}
 	}
 }
 
-func Test_atomicValue_Slice(t *testing.T) {
+func TestAtomicValue_Slice(t *testing.T) {
 	vlist := []interface{}{int64(5)}
 	v := atomicValue{}
 	v.Store(vlist)
 	slices, err := v.Slice()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	for _, v := range slices {
 		b, err := v.Duration()
 		if err != nil {
-			t.Fatal(`err is not nil`)
+			t.Fatal(err)
 		}
 		if b != time.Duration(5) {
-			t.Fatal(`b is not equal to time.Duration(5)`)
+			t.Fatal("b is not equal to time.Duration(5)")
 		}
 	}
 }
 
-func Test_atomicValue_Map(t *testing.T) {
+func TestAtomicValue_Map(t *testing.T) {
 	vlist := make(map[string]interface{})
 	vlist["5"] = int64(5)
 	vlist["text"] = "text"
@@ -183,21 +183,21 @@ func Test_atomicValue_Map(t *testing.T) {
 	v.Store(vlist)
 	m, err := v.Map()
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 	for k, v := range m {
 		if k == "5" {
 			b, err := v.Duration()
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			if b != time.Duration(5) {
-				t.Fatal(`b is not equal to time.Duration(5)`)
+				t.Fatal("b is not equal to time.Duration(5)")
 			}
 		} else {
 			b, err := v.String()
 			if err != nil {
-				t.Fatal(`err is not nil`)
+				t.Fatal(err)
 			}
 			if b != "text" {
 				t.Fatal(`b is not equal to "text"`)
@@ -206,20 +206,19 @@ func Test_atomicValue_Map(t *testing.T) {
 	}
 }
 
-func Test_atomicValue_Scan(t *testing.T) {
-	var err error
+func TestAtomicValue_Scan(t *testing.T) {
 	v := atomicValue{}
-	err = v.Scan(&struct {
+	err := v.Scan(&struct {
 		A string `json:"a"`
 	}{"a"})
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 
 	err = v.Scan(&struct {
 		A string `json:"a"`
 	}{"a"})
 	if err != nil {
-		t.Fatal(`err is not nil`)
+		t.Fatal(err)
 	}
 }
diff --git a/contrib/config/kubernetes/config_test.go b/contrib/config/kubernetes/config_test.go
index 6cdb1c98..b110b634 100644
--- a/contrib/config/kubernetes/config_test.go
+++ b/contrib/config/kubernetes/config_test.go
@@ -8,14 +8,14 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/go-kratos/kratos/v2/config"
-
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
+
+	"github.com/go-kratos/kratos/v2/config"
 )
 
 const (
diff --git a/contrib/log/aliyun/aliyun_test.go b/contrib/log/aliyun/aliyun_test.go
index c4bfde48..e3e224ad 100644
--- a/contrib/log/aliyun/aliyun_test.go
+++ b/contrib/log/aliyun/aliyun_test.go
@@ -116,21 +116,22 @@ func TestToString(t *testing.T) {
 		{"float64", 6.66, "6.66"},
 		{"max float64", math.MaxFloat64, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, //nolint:lll
 		{"float32", float32(6.66), "6.66"},
-		{"max float32", math.MaxFloat32, "340282346638528860000000000000000000000"},
-		{"int", int(math.MaxInt64), "9223372036854775807"},
+		{"max float32", float32(math.MaxFloat32), "340282350000000000000000000000000000000"},
+		{"int", math.MaxInt64, "9223372036854775807"},
 		{"uint", uint(math.MaxUint64), "18446744073709551615"},
-		{"int8", math.MaxInt8, "127"},
-		{"uint8", math.MaxUint8, "255"},
-		{"int16", math.MaxInt16, "32767"},
-		{"uint16", math.MaxUint16, "65535"},
-		{"int32", math.MaxInt32, "2147483647"},
-		{"uint32", math.MaxUint32, "4294967295"},
-		{"int64", math.MaxInt64, "9223372036854775807"},
+		{"int8", int8(math.MaxInt8), "127"},
+		{"uint8", uint8(math.MaxUint8), "255"},
+		{"int16", int16(math.MaxInt16), "32767"},
+		{"uint16", uint16(math.MaxUint16), "65535"},
+		{"int32", int32(math.MaxInt32), "2147483647"},
+		{"uint32", uint32(math.MaxUint32), "4294967295"},
+		{"int64", int64(math.MaxInt64), "9223372036854775807"},
 		{"uint64", uint64(math.MaxUint64), "18446744073709551615"},
 		{"string", "abc", "abc"},
 		{"bool", false, "false"},
 		{"[]byte", []byte("abc"), "abc"},
 		{"struct", struct{ Name string }{}, `{"Name":""}`},
+		{"nil", nil, ""},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
diff --git a/contrib/log/tencent/tencent_test.go b/contrib/log/tencent/tencent_test.go
index 76bc13bd..df1fe367 100644
--- a/contrib/log/tencent/tencent_test.go
+++ b/contrib/log/tencent/tencent_test.go
@@ -120,21 +120,22 @@ func TestToString(t *testing.T) {
 		{"float64", 6.66, "6.66"},
 		{"max float64", math.MaxFloat64, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, //nolint:lll
 		{"float32", float32(6.66), "6.66"},
-		{"max float32", math.MaxFloat32, "340282346638528860000000000000000000000"},
-		{"int", int(math.MaxInt64), "9223372036854775807"},
+		{"max float32", float32(math.MaxFloat32), "340282350000000000000000000000000000000"},
+		{"int", math.MaxInt64, "9223372036854775807"},
 		{"uint", uint(math.MaxUint64), "18446744073709551615"},
-		{"int8", math.MaxInt8, "127"},
-		{"uint8", math.MaxUint8, "255"},
-		{"int16", math.MaxInt16, "32767"},
-		{"uint16", math.MaxUint16, "65535"},
-		{"int32", math.MaxInt32, "2147483647"},
-		{"uint32", math.MaxUint32, "4294967295"},
-		{"int64", math.MaxInt64, "9223372036854775807"},
+		{"int8", int8(math.MaxInt8), "127"},
+		{"uint8", uint8(math.MaxUint8), "255"},
+		{"int16", int16(math.MaxInt16), "32767"},
+		{"uint16", uint16(math.MaxUint16), "65535"},
+		{"int32", int32(math.MaxInt32), "2147483647"},
+		{"uint32", uint32(math.MaxUint32), "4294967295"},
+		{"int64", int64(math.MaxInt64), "9223372036854775807"},
 		{"uint64", uint64(math.MaxUint64), "18446744073709551615"},
 		{"string", "abc", "abc"},
 		{"bool", false, "false"},
 		{"[]byte", []byte("abc"), "abc"},
 		{"struct", struct{ Name string }{}, `{"Name":""}`},
+		{"nil", nil, ""},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
diff --git a/log/filter_test.go b/log/filter_test.go
index d3c8d548..3e36deac 100644
--- a/log/filter_test.go
+++ b/log/filter_test.go
@@ -102,13 +102,19 @@ func TestFilterFuncWitchLoggerPrefix(t *testing.T) {
 			want:   "",
 		},
 		{
+			// Filtered value
 			logger: NewFilter(With(NewStdLogger(buf), "caller", "caller"), FilterFunc(testFilterFuncWithLoggerPrefix)),
-			want:   "INFO caller=caller msg=msg\n",
+			want:   "INFO caller=caller msg=msg filtered=***\n",
+		},
+		{
+			// NO prefix
+			logger: NewFilter(With(NewStdLogger(buf)), FilterFunc(testFilterFuncWithLoggerPrefix)),
+			want:   "INFO msg=msg filtered=***\n",
 		},
 	}
 
 	for _, tt := range tests {
-		err := tt.logger.Log(LevelInfo, "msg", "msg")
+		err := tt.logger.Log(LevelInfo, "msg", "msg", "filtered", "true")
 		if err != nil {
 			t.Fatal("err should be nil")
 		}
@@ -128,6 +134,9 @@ func testFilterFuncWithLoggerPrefix(level Level, keyvals ...interface{}) bool {
 		if keyvals[i] == "prefix" {
 			return true
 		}
+		if keyvals[i] == "filtered" {
+			keyvals[i+1] = fuzzyStr
+		}
 	}
 	return false
 }
diff --git a/options_test.go b/options_test.go
index 438de3d8..60600173 100644
--- a/options_test.go
+++ b/options_test.go
@@ -152,3 +152,39 @@ func TestStopTimeout(t *testing.T) {
 		t.Fatal("o.stopTimeout is not equal to v")
 	}
 }
+
+func TestBeforeStart(t *testing.T) {
+	o := &options{}
+	v := func(_ context.Context) error {
+		t.Log("BeforeStart...")
+		return nil
+	}
+	BeforeStart(v)(o)
+}
+
+func TestBeforeStop(t *testing.T) {
+	o := &options{}
+	v := func(_ context.Context) error {
+		t.Log("BeforeStop...")
+		return nil
+	}
+	BeforeStop(v)(o)
+}
+
+func TestAfterStart(t *testing.T) {
+	o := &options{}
+	v := func(_ context.Context) error {
+		t.Log("AfterStart...")
+		return nil
+	}
+	AfterStart(v)(o)
+}
+
+func TestAfterStop(t *testing.T) {
+	o := &options{}
+	v := func(_ context.Context) error {
+		t.Log("AfterStop...")
+		return nil
+	}
+	AfterStop(v)(o)
+}
diff --git a/selector/node/ewma/node_test.go b/selector/node/ewma/node_test.go
index feac09f2..d930495c 100644
--- a/selector/node/ewma/node_test.go
+++ b/selector/node/ewma/node_test.go
@@ -2,6 +2,7 @@ package ewma
 
 import (
 	"context"
+	"net"
 	"reflect"
 	"testing"
 	"time"
@@ -100,11 +101,15 @@ func TestDirectErrorHandler(t *testing.T) {
 			Endpoints: []string{"http://127.0.0.1:9090"},
 			Metadata:  map[string]string{"weight": "10"},
 		}))
-
+	errs := []error{
+		context.DeadlineExceeded,
+		context.Canceled,
+		net.ErrClosed,
+	}
 	for i := 0; i < 5; i++ {
 		var err error
 		if i != 0 {
-			err = context.DeadlineExceeded
+			err = errs[i%len(errs)]
 		}
 		done := wn.Pick()
 		if done == nil {