Repository: megaease/easegress (go)

Issue:
Proxy filter to propagate Openzipkin b3 headers
**Is your feature request related to a problem? Please describe.**
Easegress supports sending tracing spans per request to [Zipkin](https://zipkin.io/) tracing system. These spans can help to troubleshoot latency problems in the downstream back-end systems that Easegress pipelines connect to.

Though sometimes the span information comes in incoming requests in [b3-propagation](https://github.com/openzipkin/b3-propagation) headers and it should be passed along to the downstream back-ends. Currently Easegress does not propagate these headers.

**Describe the solution you'd like**
When b3 headers are present, propagate them in the Proxy filter to the downstream back-end servers, as desribed in https://github.com/openzipkin/b3-propagation . If b3 headers are not present, generate a new span and inject to it to the header.

**Describe alternatives you've considered**

**Additional context**


---
Thanks for contributing 🎉!


Pull request:
Proxy to send zipkin b3 headers
Fix #578 

- [opentracing](https://github.com/opentracing/opentracing-go) is deprecated (in favor of open-telemetry) so remove it and use "native" zipkin for tracing
- call zipkin-go SDK and remove explicit creation of spans and `span.Finish()`
- When tracing object is configured in HTTPServer, share the created `tracer` instance to Proxy filter via HTTPContext.
- Use zipkin to inject B3 headers if not present already

Patch:
diff --git a/go.mod b/go.mod
index b4611550..f13ff4a4 100644
--- a/go.mod
+++ b/go.mod
@@ -36,8 +36,6 @@ require (
 	github.com/megaease/grace v1.0.0
 	github.com/mitchellh/mapstructure v1.4.3
 	github.com/nacos-group/nacos-sdk-go v1.1.0
-	github.com/opentracing/opentracing-go v1.2.0
-	github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
 	github.com/openzipkin/zipkin-go v0.4.0
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
@@ -63,6 +61,7 @@ require (
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
 	gopkg.in/yaml.v2 v2.4.0
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	k8s.io/api v0.22.5
 	k8s.io/apimachinery v0.22.5
 	k8s.io/client-go v0.22.5
@@ -176,7 +175,6 @@ require (
 	github.com/nrdcg/dnspod-go v0.4.0 // indirect
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo v1.16.5 // indirect
-	github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
 	github.com/pelletier/go-toml v1.9.4 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
 	github.com/pierrec/lz4 v2.6.1+incompatible // indirect
@@ -242,7 +240,6 @@ require (
 	gopkg.in/ini.v1 v1.66.2 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	gotest.tools/v3 v3.0.3 // indirect
 	k8s.io/apiextensions-apiserver v0.22.5 // indirect
 	k8s.io/cli-runtime v0.22.5 // indirect
diff --git a/go.sum b/go.sum
index d68175e0..73ddfbdb 100644
--- a/go.sum
+++ b/go.sum
@@ -721,7 +721,6 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
@@ -1088,15 +1087,8 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo
 github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
 github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
 github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=
-github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
-github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
 github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
-github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
 github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw=
@@ -2023,7 +2015,6 @@ google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLD
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
diff --git a/pkg/context/httpcontext.go b/pkg/context/httpcontext.go
index dc5198a9..f45fc960 100644
--- a/pkg/context/httpcontext.go
+++ b/pkg/context/httpcontext.go
@@ -48,8 +48,6 @@ type (
 		Lock()
 		Unlock()
 
-		Span() tracing.Span
-
 		Request() HTTPRequest
 		Response() HTTPResponse
 
@@ -72,6 +70,8 @@ type (
 
 		CallNextHandler(lastResult string) string
 		SetHandlerCaller(caller HandlerCaller)
+
+		Tracing() *tracing.Tracing
 	}
 
 	// HTTPRequest is all operations for HTTP request.
@@ -153,13 +153,13 @@ type (
 		w *httpResponse
 
 		ht             *HTTPTemplate
-		span           tracing.Span
 		originalReqCtx stdcontext.Context
 		stdctx         stdcontext.Context
 		cancelFunc     stdcontext.CancelFunc
 		err            error
 
-		metric httpstat.Metric
+		metric  httpstat.Metric
+		tracing *tracing.Tracing
 	}
 )
 
@@ -167,19 +167,17 @@ type (
 // NOTE: We can't use sync.Pool to recycle context.
 // Reference: https://github.com/gin-gonic/gin/issues/1731
 func New(stdw http.ResponseWriter, stdr *http.Request,
-	tracer *tracing.Tracing, spanName string) HTTPContext {
+	tracingInstance *tracing.Tracing, spanName string) HTTPContext {
 	originalReqCtx := stdr.Context()
 	stdctx, cancelFunc := stdcontext.WithCancel(originalReqCtx)
 	stdr = stdr.WithContext(stdctx)
 	startTime := fasttime.Now()
-	span := tracing.NewSpanWithStart(tracer, spanName, startTime)
-	if !span.IsNoopSpan() {
-		span.SetTag("http.method", stdr.Method)
-		span.SetTag("http.path", stdr.URL.Path)
+	if !tracingInstance.IsNoopTracer() {
+		// add span to context
+		stdctx = tracing.CreateSpanWithContext(stdctx, tracingInstance, spanName, startTime)
 	}
 	ctx := &httpContext{
 		startTime:      startTime,
-		span:           span,
 		originalReqCtx: originalReqCtx,
 		stdctx:         stdctx,
 		cancelFunc:     cancelFunc,
@@ -187,6 +185,7 @@ func New(stdw http.ResponseWriter, stdr *http.Request,
 		w:              newHTTPResponse(stdw, stdr),
 		lazyTags:       make([]LazyTagFunc, 0, 5),
 		finishFuncs:    make([]FinishFunc, 0, 1),
+		tracing:        tracingInstance,
 	}
 	return ctx
 }
@@ -216,10 +215,6 @@ func (ctx *httpContext) Unlock() {
 	ctx.mutex.Unlock()
 }
 
-func (ctx *httpContext) Span() tracing.Span {
-	return ctx.span
-}
-
 // Add new Tag.
 func (ctx *httpContext) AddTag(tag string) {
 	ctx.lazyTags = append(ctx.lazyTags, func() string {
@@ -362,3 +357,8 @@ func (ctx *httpContext) SaveReqToTemplate(filterName string) error {
 func (ctx *httpContext) SaveRspToTemplate(filterName string) error {
 	return ctx.ht.SaveResponse(filterName, ctx)
 }
+
+// Tracing returns tracing object
+func (ctx *httpContext) Tracing() *tracing.Tracing {
+	return ctx.tracing
+}
diff --git a/pkg/filter/proxy/pool.go b/pkg/filter/proxy/pool.go
index 20d06fdc..fdfa444e 100644
--- a/pkg/filter/proxy/pool.go
+++ b/pkg/filter/proxy/pool.go
@@ -24,13 +24,11 @@ import (
 	"strconv"
 	"sync"
 
-	"github.com/opentracing/opentracing-go"
 	gohttpstat "github.com/tcnksm/go-httpstat"
 
 	"github.com/megaease/easegress/pkg/context"
 	"github.com/megaease/easegress/pkg/logger"
 	"github.com/megaease/easegress/pkg/supervisor"
-	"github.com/megaease/easegress/pkg/tracing"
 	"github.com/megaease/easegress/pkg/util/callbackreader"
 	"github.com/megaease/easegress/pkg/util/httpfilter"
 	"github.com/megaease/easegress/pkg/util/httpheader"
@@ -141,7 +139,7 @@ var httpstatResultPool = sync.Pool{
 	},
 }
 
-func (p *pool) handle(ctx context.HTTPContext, reqBody io.Reader, client *http.Client) string {
+func (p *pool) handle(ctx context.HTTPContext, reqBody io.Reader, client *Client) string {
 	// intMsg is converted to string in AddLazyTag for better performance,
 	// as it is not run when access logs are disabled.
 	addLazyTag := func(subPrefix, msg string, intMsg int) {
@@ -181,7 +179,7 @@ func (p *pool) handle(ctx context.HTTPContext, reqBody io.Reader, client *http.C
 		return resultInternalError
 	}
 
-	resp, span, err := p.doRequest(ctx, req, client)
+	resp, err := p.doRequest(ctx, req, client)
 	if err != nil {
 		// NOTE: May add option to cancel the tracing if failed here.
 		// ctx.Span().Cancel()
@@ -204,13 +202,11 @@ func (p *pool) handle(ctx context.HTTPContext, reqBody io.Reader, client *http.C
 	defer ctx.Unlock()
 	// NOTE: The code below can't use addTag and setStatusCode in case of deadlock.
 
-	respBody := p.statRequestResponse(ctx, req, resp, span)
-
+	respBody := p.statRequestResponse(ctx, req, resp)
 	if p.writeResponse {
 		ctx.Response().SetStatusCode(resp.StatusCode)
 		ctx.Response().Header().AddFromStd(resp.Header)
 		ctx.Response().SetBody(respBody)
-
 		return ""
 	}
 
@@ -235,7 +231,7 @@ func (p *pool) prepareRequest(
 	return p.newRequest(ctx, server, reqBody, requestPool, httpstatResultPool)
 }
 
-func (p *pool) doRequest(ctx context.HTTPContext, req *request, client *http.Client) (*http.Response, tracing.Span, error) {
+func (p *pool) doRequest(ctx context.HTTPContext, req *request, client *Client) (*http.Response, error) {
 	req.start()
 
 	spanName := p.spec.SpanName
@@ -243,14 +239,11 @@ func (p *pool) doRequest(ctx context.HTTPContext, req *request, client *http.Cli
 		spanName = req.server.URL
 	}
 
-	span := ctx.Span().NewChildWithStart(spanName, req.startTime())
-	span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.std.Header))
-
 	resp, err := fnSendRequest(req.std, client)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
-	return resp, span, nil
+	return resp, nil
 }
 
 var httpstatMetricPool = sync.Pool{
@@ -260,7 +253,7 @@ var httpstatMetricPool = sync.Pool{
 }
 
 func (p *pool) statRequestResponse(ctx context.HTTPContext,
-	req *request, resp *http.Response, span tracing.Span) io.Reader {
+	req *request, resp *http.Response) io.Reader {
 
 	var count int
 
@@ -269,7 +262,6 @@ func (p *pool) statRequestResponse(ctx context.HTTPContext,
 		count += n
 		if err == io.EOF {
 			req.finish()
-			span.Finish()
 		}
 
 		return p, n, err
@@ -278,7 +270,6 @@ func (p *pool) statRequestResponse(ctx context.HTTPContext,
 	ctx.OnFinish(func() {
 		if !p.writeResponse {
 			req.finish()
-			span.Finish()
 		}
 		duration := req.total()
 		ctx.AddLazyTag(func() string {
diff --git a/pkg/filter/proxy/proxy.go b/pkg/filter/proxy/proxy.go
index 5278f2ad..6879ef3f 100644
--- a/pkg/filter/proxy/proxy.go
+++ b/pkg/filter/proxy/proxy.go
@@ -25,12 +25,15 @@ import (
 	"net"
 	"net/http"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/megaease/easegress/pkg/context"
 	"github.com/megaease/easegress/pkg/logger"
 	"github.com/megaease/easegress/pkg/object/httppipeline"
+	"github.com/megaease/easegress/pkg/tracing"
 	"github.com/megaease/easegress/pkg/util/fallback"
+	zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
 )
 
 const (
@@ -54,7 +57,7 @@ func init() {
 	httppipeline.Register(&Proxy{})
 }
 
-var fnSendRequest = func(r *http.Request, client *http.Client) (*http.Response, error) {
+var fnSendRequest = func(r *http.Request, client *Client) (*http.Response, error) {
 	return client.Do(r)
 }
 
@@ -70,11 +73,18 @@ type (
 		candidatePools []*pool
 		mirrorPool     *pool
 
-		client *http.Client
+		client atomic.Value //*Client
 
 		compression *compression
 	}
 
+	// Client is a wrapper around http.Client.
+	Client struct {
+		client       *http.Client
+		zipkinClient *zipkinhttp.Client
+		tracing      *tracing.Tracing
+	}
+
 	// Spec describes the Proxy.
 	Spec struct {
 		Fallback            *FallbackSpec    `yaml:"fallback,omitempty" jsonschema:"omitempty"`
@@ -109,6 +119,24 @@ type (
 	}
 )
 
+// NewClient creates a wrapper around http.Client
+func NewClient(cl *http.Client, tr *tracing.Tracing) *Client {
+	var zClient *zipkinhttp.Client
+	if tr != nil && tr != tracing.NoopTracing {
+		tracer := tr.Tracer
+		zClient, _ = zipkinhttp.NewClient(tracer, zipkinhttp.WithClient(cl))
+	}
+	return &Client{client: cl, zipkinClient: zClient, tracing: tr}
+}
+
+// Do calls the correct http client
+func (c *Client) Do(r *http.Request) (*http.Response, error) {
+	if c.zipkinClient != nil {
+		return c.zipkinClient.DoWithAppSpan(r, r.URL.Path)
+	}
+	return c.client.Do(r)
+}
+
 // Validate validates Spec.
 func (s Spec) Validate() error {
 	// NOTE: The tag of v parent may be behind mainPool.
@@ -172,6 +200,7 @@ func (b *Proxy) Results() []string {
 // Init initializes Proxy.
 func (b *Proxy) Init(filterSpec *httppipeline.FilterSpec) {
 	b.filterSpec, b.spec = filterSpec, filterSpec.FilterSpec().(*Spec)
+
 	b.reload()
 }
 
@@ -240,7 +269,11 @@ func (b *Proxy) reload() {
 		b.compression = newCompression(b.spec.Compression)
 	}
 
-	b.client = &http.Client{
+	b.client.Store(NewClient(b.createHTTPClient() /*tracing=*/, nil))
+}
+
+func (b *Proxy) createHTTPClient() *http.Client {
+	return &http.Client{
 		// NOTE: Timeout could be no limit, real client or server could cancel it.
 		Timeout: 0,
 		Transport: &http.Transport{
@@ -315,6 +348,17 @@ func (b *Proxy) Handle(ctx context.HTTPContext) (result string) {
 	return ctx.CallNextHandler(result)
 }
 
+func (b *Proxy) updateAndGetClient(tracingInstance *tracing.Tracing) *Client {
+	client := b.client.Load().(*Client)
+	if client.tracing == tracingInstance {
+		return client
+	}
+	// tracingInstance is updated so recreate http.Client
+	newClient := NewClient(b.createHTTPClient(), tracingInstance)
+	b.client.Store(newClient)
+	return newClient
+}
+
 func (b *Proxy) handle(ctx context.HTTPContext) (result string) {
 	if b.mirrorPool != nil && b.mirrorPool.filter.Filter(ctx) {
 		primaryBody, secondaryBody := newPrimarySecondaryReader(ctx.Request().Body())
@@ -326,7 +370,8 @@ func (b *Proxy) handle(ctx context.HTTPContext) (result string) {
 
 		go func() {
 			defer wg.Done()
-			b.mirrorPool.handle(ctx, secondaryBody, b.client)
+			client := b.updateAndGetClient(ctx.Tracing())
+			b.mirrorPool.handle(ctx, secondaryBody, client)
 		}()
 	}
 
@@ -348,7 +393,8 @@ func (b *Proxy) handle(ctx context.HTTPContext) (result string) {
 		return ""
 	}
 
-	result = p.handle(ctx, ctx.Request().Body(), b.client)
+	client := b.updateAndGetClient(ctx.Tracing())
+	result = p.handle(ctx, ctx.Request().Body(), client)
 	if result != "" {
 		if b.fallbackForCodes(ctx) {
 			return resultFallback
diff --git a/pkg/object/easemonitormetrics/easemonitormetrics.go b/pkg/object/easemonitormetrics/easemonitormetrics.go
index 5c3fc0f5..597e7127 100644
--- a/pkg/object/easemonitormetrics/easemonitormetrics.go
+++ b/pkg/object/easemonitormetrics/easemonitormetrics.go
@@ -293,19 +293,16 @@ func (emm *EaseMonitorMetrics) record2Messages(record *statussynccontroller.Stat
 		}
 
 		switch status := status.ObjectStatus.(type) {
-		case *trafficcontroller.StatusInSameNamespace:
-			for name, server := range status.HTTPServers {
-				baseFields.Service = fmt.Sprintf("%s/%s", baseFields.Service, name)
-				reqs, codes := emm.httpServer2Metrics(baseFields, server.Status)
-				reqMetrics = append(reqMetrics, reqs...)
-				codeMetrics = append(codeMetrics, codes...)
-			}
-			for name, pipeline := range status.HTTPPipelines {
-				baseFields.Service = fmt.Sprintf("%s/%s", baseFields.Service, name)
-				reqs, codes := emm.httpPipeline2Metrics(baseFields, pipeline.Status)
-				reqMetrics = append(reqMetrics, reqs...)
-				codeMetrics = append(codeMetrics, codes...)
-			}
+		case *trafficcontroller.HTTPServerStatus:
+			baseFields.Service = fmt.Sprintf("%s/%s", baseFields.Service, status.Spec["name"])
+			reqs, codes := emm.httpServer2Metrics(baseFields, status.Status)
+			reqMetrics = append(reqMetrics, reqs...)
+			codeMetrics = append(codeMetrics, codes...)
+		case *trafficcontroller.HTTPPipelineStatus:
+			baseFields.Service = fmt.Sprintf("%s/%s", baseFields.Service, status.Spec["name"])
+			reqs, codes := emm.httpPipeline2Metrics(baseFields, status.Status)
+			reqMetrics = append(reqMetrics, reqs...)
+			codeMetrics = append(codeMetrics, codes...)
 		default:
 			continue
 		}
@@ -340,8 +337,8 @@ func (emm *EaseMonitorMetrics) record2Messages(record *statussynccontroller.Stat
 }
 
 func (emm *EaseMonitorMetrics) httpPipeline2Metrics(baseFields *GlobalFields, pipelineStatus *httppipeline.Status) (
-	reqMetrics []*RequestMetrics, codeMetrics []*StatusCodeMetrics) {
-
+	reqMetrics []*RequestMetrics, codeMetrics []*StatusCodeMetrics,
+) {
 	for filterName, filterStatus := range pipelineStatus.Filters {
 		proxyStatus, ok := filterStatus.(*proxy.Status)
 		if !ok {
@@ -380,8 +377,8 @@ func (emm *EaseMonitorMetrics) httpPipeline2Metrics(baseFields *GlobalFields, pi
 
 func (emm *EaseMonitorMetrics) httpServer2Metrics(
 	baseFields *GlobalFields, serverStatus *httpserver.Status) (
-	reqMetrics []*RequestMetrics, codeMetrics []*StatusCodeMetrics) {
-
+	reqMetrics []*RequestMetrics, codeMetrics []*StatusCodeMetrics,
+) {
 	if serverStatus.Status != nil {
 		baseFieldsServer := *baseFields
 		baseFieldsServer.Resource = "SERVER"
@@ -403,8 +400,8 @@ func (emm *EaseMonitorMetrics) httpServer2Metrics(
 }
 
 func (emm *EaseMonitorMetrics) httpStat2Metrics(baseFields *GlobalFields, s *httpstat.Status) (
-	*RequestMetrics, []*StatusCodeMetrics) {
-
+	*RequestMetrics, []*StatusCodeMetrics,
+) {
 	baseFields.Type = "eg-http-request"
 	rm := &RequestMetrics{
 		GlobalFields: *baseFields,
diff --git a/pkg/object/httpserver/mux.go b/pkg/object/httpserver/mux.go
index f872856e..0e5bc2ad 100644
--- a/pkg/object/httpserver/mux.go
+++ b/pkg/object/httpserver/mux.go
@@ -391,11 +391,9 @@ func (m *mux) ServeHTTP(stdw http.ResponseWriter, stdr *http.Request) {
 	}
 
 	rules := m.rules.Load().(*muxRules)
-
 	ctx := context.New(stdw, stdr, rules.tracer, rules.superSpec.Name())
 	defer ctx.Finish()
 	ctx.OnFinish(func() {
-		ctx.Span().Finish()
 		m.httpStat.Stat(ctx.StatMetric())
 		m.topN.Stat(ctx)
 	})
diff --git a/pkg/object/statussynccontroller/statussynccontroller.go b/pkg/object/statussynccontroller/statussynccontroller.go
index 5fe3894e..b84213bd 100644
--- a/pkg/object/statussynccontroller/statussynccontroller.go
+++ b/pkg/object/statussynccontroller/statussynccontroller.go
@@ -262,7 +262,8 @@ func (ssc *StatusSyncController) syncStatusToCluster(statuses map[string]string)
 	kvs := make(map[string]*string)
 	for k, value := range statuses {
 		key := ssc.superSpec.Super().Cluster().Layout().StatusObjectKey(k)
-		kvs[key] = &value
+		v := value
+		kvs[key] = &v
 		if len(kvs) >= ssc.statusUpdateMaxBatchSize {
 			err := ssc.superSpec.Super().Cluster().PutAndDeleteUnderLease(kvs)
 			if err != nil {
diff --git a/pkg/object/trafficcontroller/trafficcontroller.go b/pkg/object/trafficcontroller/trafficcontroller.go
index 80960210..912132b7 100644
--- a/pkg/object/trafficcontroller/trafficcontroller.go
+++ b/pkg/object/trafficcontroller/trafficcontroller.go
@@ -66,8 +66,7 @@ type (
 	WalkFunc = supervisor.WalkFunc
 
 	// Spec describes TrafficController.
-	Spec struct {
-	}
+	Spec struct{}
 
 	// Status is the status of namespaces
 	Status struct {
@@ -130,8 +129,8 @@ func (sisn *StatusInSameNamespace) ToSyncStatus() map[string]*supervisor.Status
 	for key, server := range sisn.HTTPServers {
 		objects[key] = server.toSyncStatus()
 	}
-	for key, server := range sisn.HTTPPipelines {
-		objects[key] = server.toSyncStatus()
+	for key, pipeline := range sisn.HTTPPipelines {
+		objects[key] = pipeline.toSyncStatus()
 	}
 	return objects
 }
@@ -175,8 +174,8 @@ func (tc *TrafficController) reload(previousGeneration *TrafficController) {
 
 // CreateHTTPServerForSpec creates HTTP server with a spec
 func (tc *TrafficController) CreateHTTPServerForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -186,8 +185,8 @@ func (tc *TrafficController) CreateHTTPServerForSpec(namespace string, superSpec
 
 // CreateHTTPServer creates HTTP server
 func (tc *TrafficController) CreateHTTPServer(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	if namespace == "" {
 		return nil, fmt.Errorf("empty namespace")
 	}
@@ -214,8 +213,8 @@ func (tc *TrafficController) CreateHTTPServer(namespace string, entity *supervis
 
 // UpdateHTTPServerForSpec updates HTTP server with a Spec
 func (tc *TrafficController) UpdateHTTPServerForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -225,8 +224,8 @@ func (tc *TrafficController) UpdateHTTPServerForSpec(namespace string, superSpec
 
 // UpdateHTTPServer updates HTTP server
 func (tc *TrafficController) UpdateHTTPServer(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	tc.mutex.Lock()
 	defer tc.mutex.Unlock()
 
@@ -252,8 +251,8 @@ func (tc *TrafficController) UpdateHTTPServer(namespace string, entity *supervis
 
 // ApplyHTTPServerForSpec applies HTTP servers with a Spec
 func (tc *TrafficController) ApplyHTTPServerForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -263,8 +262,8 @@ func (tc *TrafficController) ApplyHTTPServerForSpec(namespace string, superSpec
 
 // ApplyHTTPServer applies HTTP Server
 func (tc *TrafficController) ApplyHTTPServer(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	if namespace == "" {
 		return nil, fmt.Errorf("empty namespace")
 	}
@@ -409,8 +408,8 @@ func (tc *TrafficController) WalkHTTPPipelines(namespace string, walkFn WalkFunc
 
 // CreateHTTPPipelineForSpec creates a HTTP pipeline by a spec
 func (tc *TrafficController) CreateHTTPPipelineForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -420,8 +419,8 @@ func (tc *TrafficController) CreateHTTPPipelineForSpec(namespace string, superSp
 
 // CreateHTTPPipeline creates a HTTP pipeline
 func (tc *TrafficController) CreateHTTPPipeline(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	if namespace == "" {
 		return nil, fmt.Errorf("empty namespace")
 	}
@@ -448,8 +447,8 @@ func (tc *TrafficController) CreateHTTPPipeline(namespace string, entity *superv
 
 // UpdateHTTPPipelineForSpec updates the HTTP pipeline with a Spec
 func (tc *TrafficController) UpdateHTTPPipelineForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -459,8 +458,8 @@ func (tc *TrafficController) UpdateHTTPPipelineForSpec(namespace string, superSp
 
 // UpdateHTTPPipeline updates the HTTP pipeline
 func (tc *TrafficController) UpdateHTTPPipeline(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	tc.mutex.Lock()
 	defer tc.mutex.Unlock()
 
@@ -486,8 +485,8 @@ func (tc *TrafficController) UpdateHTTPPipeline(namespace string, entity *superv
 
 // ApplyHTTPPipelineForSpec applies the HTTP pipeline with a Spec
 func (tc *TrafficController) ApplyHTTPPipelineForSpec(namespace string, superSpec *supervisor.Spec) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	entity, err := tc.super.NewObjectEntityFromSpec(superSpec)
 	if err != nil {
 		return nil, err
@@ -497,8 +496,8 @@ func (tc *TrafficController) ApplyHTTPPipelineForSpec(namespace string, superSpe
 
 // ApplyHTTPPipeline applies the HTTP pipeline
 func (tc *TrafficController) ApplyHTTPPipeline(namespace string, entity *supervisor.ObjectEntity) (
-	*supervisor.ObjectEntity, error) {
-
+	*supervisor.ObjectEntity, error,
+) {
 	if namespace == "" {
 		return nil, fmt.Errorf("empty namespace")
 	}
diff --git a/pkg/tracing/span.go b/pkg/tracing/span.go
index 39aab78b..f21a9edd 100644
--- a/pkg/tracing/span.go
+++ b/pkg/tracing/span.go
@@ -21,20 +21,20 @@ import (
 	"sync"
 	"time"
 
-	opentracing "github.com/opentracing/opentracing-go"
+	zipkingo "github.com/openzipkin/zipkin-go"
+	zipkinmodel "github.com/openzipkin/zipkin-go/model"
 
 	"github.com/megaease/easegress/pkg/tracing/base"
-	"github.com/megaease/easegress/pkg/util/fasttime"
 )
 
 type (
 	// Span is the span of the Tracing.
 	Span interface {
 		// Tracer returns the Tracer that created this Span.
-		Tracer() opentracing.Tracer
+		Tracer() zipkingo.Tracer
 
 		// Context yields the SpanContext for this Span
-		Context() opentracing.SpanContext
+		Context() zipkinmodel.SpanContext
 
 		// Finish finishes the span.
 		Finish()
@@ -72,53 +72,16 @@ type (
 	span struct {
 		mutex    sync.Mutex
 		tracer   *Tracing
-		span     opentracing.Span
+		span     zipkingo.Span
 		children []*span
 	}
 )
 
-// NoopSpan does nothing.
-var NoopSpan = &span{
-	tracer: NoopTracing,
-	span:   NoopTracing.StartSpan(""), // will return opentracing.defaultNoopSpan
+func (s *span) Tracer() *zipkingo.Tracer {
+	return s.tracer.Tracer
 }
 
-// NewSpan creates a span.
-func NewSpan(tracer *Tracing, name string) Span {
-	if tracer.IsNoopTracer() {
-		return NoopSpan
-	}
-	return newSpanWithStart(tracer, name, fasttime.Now())
-}
-
-// NewSpanWithStart creates a span with specify start time.
-func NewSpanWithStart(tracer *Tracing, name string, startAt time.Time) Span {
-	if tracer.IsNoopTracer() {
-		return NoopSpan
-	}
-	return newSpanWithStart(tracer, name, startAt)
-}
-
-func newSpanWithStart(tracer *Tracing, name string, startAt time.Time) Span {
-	newSpan := tracer.StartSpan(name, opentracing.StartTime(startAt))
-	for tagKey, tagValue := range tracer.tags {
-		newSpan.SetTag(tagKey, tagValue)
-	}
-	return &span{
-		tracer: tracer,
-		span:   newSpan,
-	}
-}
-
-func (s *span) IsNoopSpan() bool {
-	return s == NoopSpan
-}
-
-func (s *span) Tracer() opentracing.Tracer {
-	return s.tracer
-}
-
-func (s *span) Context() opentracing.SpanContext {
+func (s *span) Context() zipkinmodel.SpanContext {
 	return s.span.Context()
 }
 
@@ -127,50 +90,8 @@ func (s *span) Finish() {
 }
 
 func (s *span) Cancel() {
-	s.span.SetTag(base.CancelTagKey, "yes")
+	s.span.Tag(base.CancelTagKey, "yes")
 	for _, child := range s.children {
 		child.Cancel()
 	}
 }
-
-func (s *span) NewChild(name string) Span {
-	if s.IsNoopSpan() {
-		return s
-	}
-	return s.newChildWithStart(name, fasttime.Now())
-}
-
-func (s *span) NewChildWithStart(name string, startAt time.Time) Span {
-	if s.IsNoopSpan() {
-		return s
-	}
-	return s.newChildWithStart(name, startAt)
-}
-
-func (s *span) newChildWithStart(name string, startAt time.Time) Span {
-	childSpan := s.tracer.StartSpan(name,
-		opentracing.ChildOf(s.span.Context()),
-		opentracing.StartTime(startAt))
-	child := &span{
-		tracer: s.tracer,
-		span:   childSpan,
-	}
-
-	s.mutex.Lock()
-	defer s.mutex.Unlock()
-	s.children = append(s.children, child)
-
-	return child
-}
-
-func (s *span) SetName(name string) {
-	s.span.SetOperationName(name)
-}
-
-func (s *span) LogKV(kv ...interface{}) {
-	s.span.LogKV(kv...)
-}
-
-func (s *span) SetTag(key string, value string) {
-	s.span.SetTag(key, value)
-}
diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go
index 6a3a64ba..ace66e6d 100644
--- a/pkg/tracing/tracing.go
+++ b/pkg/tracing/tracing.go
@@ -18,11 +18,12 @@
 package tracing
 
 import (
+	"context"
 	"io"
-
-	opentracing "github.com/opentracing/opentracing-go"
+	"time"
 
 	"github.com/megaease/easegress/pkg/tracing/zipkin"
+	zipkingo "github.com/openzipkin/zipkin-go"
 )
 
 type (
@@ -35,7 +36,7 @@ type (
 
 	// Tracing is the tracing.
 	Tracing struct {
-		opentracing.Tracer
+		Tracer *zipkingo.Tracer
 		tags   map[string]string
 		closer io.Closer
 	}
@@ -45,7 +46,7 @@ type (
 
 // NoopTracing is the tracing doing nothing.
 var NoopTracing = &Tracing{
-	Tracer: opentracing.NoopTracer{},
+	Tracer: zipkin.CreateNoopTracer(),
 	closer: nil,
 }
 
@@ -55,14 +56,13 @@ func New(spec *Spec) (*Tracing, error) {
 		return NoopTracing, nil
 	}
 
-	tracer, closer, err := zipkin.New(spec.ServiceName, spec.Zipkin)
+	tracer, closer, err := zipkin.New(spec.ServiceName, spec.Zipkin, spec.Tags)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Tracing{
 		Tracer: tracer,
-		tags:   spec.Tags,
 		closer: closer,
 	}, nil
 }
@@ -80,3 +80,9 @@ func (t *Tracing) Close() error {
 
 	return nil
 }
+
+// CreateSpanWithContext creates new span with given name and starttime and adds it to the context.
+func CreateSpanWithContext(ctx context.Context, tracing *Tracing, spanName string, startTime time.Time) context.Context {
+	span := tracing.Tracer.StartSpan(spanName, zipkingo.StartTime(startTime))
+	return zipkingo.NewContext(ctx, span)
+}
diff --git a/pkg/tracing/zipkin/zipkin.go b/pkg/tracing/zipkin/zipkin.go
index 8e160f6b..8413968a 100644
--- a/pkg/tracing/zipkin/zipkin.go
+++ b/pkg/tracing/zipkin/zipkin.go
@@ -20,8 +20,6 @@ package zipkin
 import (
 	"io"
 
-	opentracing "github.com/opentracing/opentracing-go"
-	zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing"
 	zipkingo "github.com/openzipkin/zipkin-go"
 	zipkingomodel "github.com/openzipkin/zipkin-go/model"
 	zipkingoreporter "github.com/openzipkin/zipkin-go/reporter"
@@ -46,6 +44,12 @@ type (
 	}
 )
 
+// CreateNoopTracer creates no-op tracer
+func CreateNoopTracer() *zipkingo.Tracer {
+	tracer, _ := zipkingo.NewTracer(nil)
+	return tracer
+}
+
 func (cp *cancellableReporter) Send(sm zipkingomodel.SpanModel) {
 	_, cancelled := sm.Tags[base.CancelTagKey]
 	if cancelled {
@@ -69,7 +73,7 @@ func (spec Spec) Validate() error {
 }
 
 // New creates zipkin tracer.
-func New(serviceName string, spec *Spec) (opentracing.Tracer, io.Closer, error) {
+func New(serviceName string, spec *Spec, tags map[string]string) (*zipkingo.Tracer, io.Closer, error) {
 	endpoint, err := zipkingo.NewEndpoint(serviceName, spec.Hostport)
 	if err != nil {
 		return nil, nil, err
@@ -88,10 +92,11 @@ func New(serviceName string, spec *Spec) (opentracing.Tracer, io.Closer, error)
 		zipkingo.WithSharedSpans(spec.SameSpan),
 		zipkingo.WithTraceID128Bit(spec.ID128Bit),
 		zipkingo.WithSampler(sampler),
+		zipkingo.WithTags(tags),
 	)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return zipkinot.Wrap(nativeTracer), reporter, nil
+	return nativeTracer, reporter, nil
 }
diff --git a/pkg/util/yamltool/yamltool.go b/pkg/util/yamltool/yamltool.go
index c34e14c3..b4f7e086 100644
--- a/pkg/util/yamltool/yamltool.go
+++ b/pkg/util/yamltool/yamltool.go
@@ -20,7 +20,7 @@ package yamltool
 import (
 	"fmt"
 
-	"gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
 )
 
 // Marshal wraps yaml.Marshal by panic instead of returning error.


Test patch:
diff --git a/pkg/context/contexttest/context.go b/pkg/context/contexttest/context.go
index 0b31ed36..ce19c964 100644
--- a/pkg/context/contexttest/context.go
+++ b/pkg/context/contexttest/context.go
@@ -33,7 +33,6 @@ type MockedHTTPContext struct {
 	finishFuncs              []func()
 	MockedLock               func()
 	MockedUnlock             func()
-	MockedSpan               func() tracing.Span
 	MockedRequest            MockedHTTPRequest
 	MockedResponse           MockedHTTPResponse
 	MockedDeadline           func() (time.Time, bool)
@@ -54,6 +53,7 @@ type MockedHTTPContext struct {
 	MockedSaveRspToTemplate  func(filterName string) error
 	MockedCallNextHandler    func(lastResult string) string
 	MockedSetHandlerCaller   func(caller context.HandlerCaller)
+	MockedTracing            func() *tracing.Tracing
 }
 
 // Protocol return protocol of MockedHTTPContext
@@ -75,14 +75,6 @@ func (c *MockedHTTPContext) Unlock() {
 	}
 }
 
-// Span mocks the Span function of HTTPContext
-func (c *MockedHTTPContext) Span() tracing.Span {
-	if c.MockedSpan != nil {
-		return c.MockedSpan()
-	}
-	return tracing.NewSpan(tracing.NoopTracing, "mocked")
-}
-
 // Request mocks the Request function of HTTPContext
 func (c *MockedHTTPContext) Request() context.HTTPRequest {
 	return &c.MockedRequest
@@ -242,6 +234,14 @@ func (c *MockedHTTPContext) CallNextHandler(lastResult string) string {
 // SetHandlerCaller mocks the SetHandlerCaller function of HTTPContext
 func (c *MockedHTTPContext) SetHandlerCaller(caller context.HandlerCaller) {
 	if c.MockedSetHandlerCaller != nil {
-		c.SetHandlerCaller(caller)
+		c.MockedSetHandlerCaller(caller)
+	}
+}
+
+// Tracing mocks the Tracing function of HTTPContext
+func (c *MockedHTTPContext) Tracing() *tracing.Tracing {
+	if c.MockedTracing != nil {
+		return c.MockedTracing()
 	}
+	return tracing.NoopTracing
 }
diff --git a/pkg/filter/proxy/proxy_test.go b/pkg/filter/proxy/proxy_test.go
index 429d0d86..107fd3b4 100644
--- a/pkg/filter/proxy/proxy_test.go
+++ b/pkg/filter/proxy/proxy_test.go
@@ -18,19 +18,27 @@
 package proxy
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"net/http"
+	"net/http/httptest"
 	"strings"
+	"sync"
 	"testing"
 	"time"
 
+	"github.com/megaease/easegress/pkg/cluster/clustertest"
 	"github.com/megaease/easegress/pkg/context/contexttest"
+	"github.com/megaease/easegress/pkg/logger"
 	"github.com/megaease/easegress/pkg/object/httppipeline"
+	"github.com/megaease/easegress/pkg/supervisor"
+	"github.com/megaease/easegress/pkg/tracing"
 	"github.com/megaease/easegress/pkg/util/httpfilter"
 	"github.com/megaease/easegress/pkg/util/httpheader"
 	"github.com/megaease/easegress/pkg/util/memorycache"
 	"github.com/megaease/easegress/pkg/util/yamltool"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestProxy(t *testing.T) {
@@ -122,7 +130,7 @@ failureCodes: [503, 504]
 		t.Error("fallback for 500 should be false")
 	}
 
-	fnSendRequest = func(r *http.Request, client *http.Client) (*http.Response, error) {
+	fnSendRequest = func(r *http.Request, client *Client) (*http.Response, error) {
 		return &http.Response{
 			Body: io.NopCloser(strings.NewReader("this is the body")),
 		}, nil
@@ -134,7 +142,7 @@ failureCodes: [503, 504]
 	}
 	ctx.Finish()
 
-	fnSendRequest = func(r *http.Request, client *http.Client) (*http.Response, error) {
+	fnSendRequest = func(r *http.Request, client *Client) (*http.Response, error) {
 		return nil, fmt.Errorf("mocked error")
 	}
 
@@ -247,3 +255,112 @@ func TestPoolSpecValidate(t *testing.T) {
 		t.Error("validate should succeed")
 	}
 }
+
+func createTracing(assert *assert.Assertions, url string) *tracing.Tracing {
+	if url == "" {
+		url = "http://localhost:9411"
+	}
+	spec := `
+serviceName: testService
+tags:
+    MyTagKey: X-value
+    SecondTag: 382
+zipkin:
+    hostport: 0.0.0.0:10087
+    serverURL: <URL>/api/v2/spans
+    sampleRate: 1
+    sameSpan: true
+    id128Bit: false
+`
+	spec = strings.Replace(spec, "<URL>", url, 1)
+	rawSpec := tracing.Spec{}
+	yamltool.Unmarshal([]byte(spec), &rawSpec)
+	tracer, err := tracing.New(&rawSpec)
+	assert.Nil(err)
+	return tracer
+}
+
+func TestProxyClient(t *testing.T) {
+	assert := assert.New(t)
+
+	client := &http.Client{}
+	pc := NewClient(client, nil)
+
+	wg := &sync.WaitGroup{}
+	wg.Add(2)
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		wg.Done()
+	}))
+	ctx := context.Background()
+	stdr, err := http.NewRequestWithContext(ctx, "GET", ts.URL, nil)
+	assert.Nil(err)
+	pc.Do(stdr)
+
+	// with tracing
+	tracer := createTracing(assert, "")
+	tracer.Tracer.SetNoop(true) // skip sending spans
+	pc = NewClient(client, tracer)
+	stdr, err = http.NewRequestWithContext(ctx, "GET", ts.URL, nil)
+	assert.Nil(err)
+	pc.Do(stdr)
+	wg.Wait()
+	tracer.Close()
+	ts.Close()
+}
+
+func TestHandleWithTracing(t *testing.T) {
+	logger.InitNop()
+	assert := assert.New(t)
+
+	//httppipeline.Register(&Proxy{})
+
+	const proxySpec = `
+name: proxy
+kind: Proxy
+mainPool:
+  servers:
+  - url: http://127.0.0.1:9095
+  loadBalance:
+    policy: roundRobin`
+
+	var mockMap sync.Map
+	clsMock := clustertest.NewMockedCluster()
+	superMock := supervisor.NewMock(nil, clsMock, mockMap, mockMap, nil, nil, false, nil, nil)
+
+	rawSpec := make(map[string]interface{})
+	yamltool.Unmarshal([]byte(proxySpec), &rawSpec)
+	spec, err := httppipeline.NewFilterSpec(rawSpec, superMock)
+	assert.Nil(err)
+	proxy := &Proxy{}
+	proxy.Init(spec)
+
+	ctx := &contexttest.MockedHTTPContext{}
+	ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
+		header := http.Header{}
+		return httpheader.New(header)
+	}
+	ctx.MockedTracing = func() *tracing.Tracing {
+		return tracing.NoopTracing
+	}
+	proxy.handle(ctx)
+	assert.Nil(proxy.client.Load().(*Client).zipkinClient)
+
+	// HTTPServer updates tracing
+	tracer := createTracing(assert, "")
+
+	ctx.MockedTracing = func() *tracing.Tracing {
+		return tracer
+	}
+
+	proxy.handle(ctx)
+	assert.NotNil(proxy.client.Load().(*Client).zipkinClient)
+
+	// HTTPServer removes tracing
+	ctx.MockedTracing = func() *tracing.Tracing {
+		return tracing.NoopTracing
+	}
+	proxy.handle(ctx)
+	assert.Nil(proxy.client.Load().(*Client).zipkinClient)
+
+	tracer.Close() // normally HTTPServer closes this
+}
diff --git a/pkg/filter/proxy/request_test.go b/pkg/filter/proxy/request_test.go
index 17fb1c20..d2623e6c 100644
--- a/pkg/filter/proxy/request_test.go
+++ b/pkg/filter/proxy/request_test.go
@@ -29,9 +29,11 @@ import (
 	"github.com/megaease/easegress/pkg/context/contexttest"
 	"github.com/megaease/easegress/pkg/util/fasttime"
 	"github.com/megaease/easegress/pkg/util/httpheader"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestRequest(t *testing.T) {
+	assert := assert.New(t)
 	ctx := &contexttest.MockedHTTPContext{}
 	ctx.MockedRequest.MockedPath = func() string {
 		return "/abc"
@@ -63,9 +65,7 @@ func TestRequest(t *testing.T) {
 
 	time.Sleep(time.Millisecond)
 	req.start()
-	if tm != req.startTime() {
-		t.Error("start time should not change")
-	}
+	assert.Equal(tm, req.startTime())
 
 	req.total()
 
@@ -75,14 +75,17 @@ func TestRequest(t *testing.T) {
 	time.Sleep(time.Millisecond)
 	req.finish()
 
-	if tm != req.endTime() {
-		t.Error("end time should not change")
-	}
+	assert.Equal(tm, req.endTime())
 
 	req.total()
-	if req.detail() == "" {
-		t.Error("detail should not be empty")
+	assert.NotEqual("", req.detail())
+
+	ctx.MockedRequest.MockedMethod = func() string {
+		return "ééé" // not tokenable, should fail
 	}
+	req, err := p.newRequest(ctx, &server, nil, requestPool, httpstatResultPool)
+	assert.Nil(req)
+	assert.NotNil(err)
 }
 
 func TestResultState(t *testing.T) {
@@ -129,3 +132,33 @@ func TestRequestStatus(t *testing.T) {
 	httpstatResultPool.Put(req.statResult)
 	requestPool.Put(req)
 }
+
+func TestAddB3PropagationHeaders(t *testing.T) {
+	assert := assert.New(t)
+	ctx := &contexttest.MockedHTTPContext{}
+	traceID := "463ac35c9f6413ad48485a3953bb6124"
+	spanID := "a2fb4a1d1a96d312"
+	ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
+		return httpheader.New(http.Header{
+			"X-B3-TraceId":      []string{traceID},
+			"X-B3-ParentSpanId": []string{"parentspanid"},
+			"X-B3-SpanId":       []string{spanID},
+			"X-B3-Sampled":      []string{"sampled"},
+		})
+	}
+
+	server := Server{
+		URL: "http://192.168.1.2",
+	}
+
+	p := pool{}
+	sr := strings.NewReader("this is the raw body")
+	req, _ := p.newRequest(ctx, &server, sr, requestPool, httpstatResultPool)
+	defer requestPool.Put(req) // recycle request
+
+	header := req.std.Header
+	assert.Equal(1, len(header["X-B3-TraceId"]))
+	assert.Equal(traceID, header["X-B3-TraceId"][0])
+	assert.Equal(1, len(header["X-B3-Sampled"]))
+	assert.Equal("sampled", header["X-B3-Sampled"][0])
+}
diff --git a/pkg/object/httpserver/httpserver_test.go b/pkg/object/httpserver/httpserver_test.go
index a159566a..125df621 100644
--- a/pkg/object/httpserver/httpserver_test.go
+++ b/pkg/object/httpserver/httpserver_test.go
@@ -372,7 +372,7 @@ func TestStartTwoServerInSamePort(t *testing.T) {
 	superSpecYaml := `
 name: http-server-test
 kind: HTTPServer
-port: 10080
+port: 10082
 cacheSize: 200
 rules:
   - paths:
@@ -391,12 +391,10 @@ rules:
 	httpServer2 := HTTPServer{}
 	httpServer2.Init(superSpec2, mux2)
 
-	_, err1 := http.Get("http://127.0.0.1:10080/api")
 	httpServer1.Close()
 	httpServer2.Close()
-	_, err2 := http.Get("http://127.0.0.1:10080/api")
-	assert.Nil(err1)
-	assert.NotNil(err2)
+	_, err = http.Get("http://127.0.0.1:10082/api")
+	assert.NotNil(err)
 }
 
 func TestMatchPath(t *testing.T) {