diff --git a/pkg/httplib/filter/opentracing/filter.go b/pkg/httplib/filter/opentracing/filter.go new file mode 100644 index 00000000..5f409c63 --- /dev/null +++ b/pkg/httplib/filter/opentracing/filter.go @@ -0,0 +1,77 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opentracing + +import ( + "context" + "net/http" + "strconv" + + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" + + "github.com/astaxie/beego/pkg/httplib" +) + +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, + req *httplib.BeegoHTTPRequest, resp *http.Response, err error) +} + +func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { + + return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + + method := req.GetRequest().Method + host := req.GetRequest().URL.Host + path := req.GetRequest().URL.Path + + proto := req.GetRequest().Proto + + scheme := req.GetRequest().URL.Scheme + + operationName := host + path + "#" + method + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + + inject := opentracingKit.ContextToHTTP(opentracing.GlobalTracer(), logKit.NewNopLogger()) + inject(spanCtx, req.GetRequest()) + resp, err := next(spanCtx, req) + + if resp != nil { + span.SetTag("status", strconv.Itoa(resp.StatusCode)) + } + + span.SetTag("method", method) + span.SetTag("host", host) + span.SetTag("path", path) + span.SetTag("proto", proto) + span.SetTag("scheme", scheme) + + span.LogFields(log.String("url", req.GetRequest().URL.String())) + + if err != nil { + span.LogFields(log.String("error", err.Error())) + } + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, ctx, req, resp, err) + } + return resp, err + } +} diff --git a/pkg/httplib/filter/opentracing/filter_test.go b/pkg/httplib/filter/opentracing/filter_test.go new file mode 100644 index 00000000..aa687541 --- /dev/null +++ b/pkg/httplib/filter/opentracing/filter_test.go @@ -0,0 +1,42 @@ +// Copyright 2020 beego +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package opentracing + +import ( + "context" + "errors" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/astaxie/beego/pkg/httplib" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { + time.Sleep(100 * time.Millisecond) + return &http.Response{ + StatusCode: 404, + }, errors.New("hello") + } + builder := &FilterChainBuilder{} + filter := builder.FilterChain(next) + req := httplib.Get("https://github.com/notifications?query=repo%3Aastaxie%2Fbeego") + resp, err := filter(context.Background(), req) + assert.NotNil(t, resp) + assert.NotNil(t, err) +} diff --git a/pkg/web/filter/opentracing/filter.go b/pkg/web/filter/opentracing/filter.go index 82d0f719..822d5e4d 100644 --- a/pkg/web/filter/opentracing/filter.go +++ b/pkg/web/filter/opentracing/filter.go @@ -15,24 +15,39 @@ package opentracing import ( + "context" + + logKit "github.com/go-kit/kit/log" + opentracingKit "github.com/go-kit/kit/tracing/opentracing" "github.com/opentracing/opentracing-go" beego "github.com/astaxie/beego/pkg" - "github.com/astaxie/beego/pkg/context" + beegoCtx "github.com/astaxie/beego/pkg/context" ) // FilterChainBuilder provides an extension point that we can support more configurations if necessary type FilterChainBuilder struct { // CustomSpanFunc makes users to custom the span. - CustomSpanFunc func(span opentracing.Span, ctx *context.Context) + CustomSpanFunc func(span opentracing.Span, ctx *beegoCtx.Context) } func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.FilterFunc { - return func(ctx *context.Context) { + return func(ctx *beegoCtx.Context) { + var ( + spanCtx context.Context + span opentracing.Span + ) operationName := builder.operationName(ctx) - span, spanCtx := opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) + if preSpan := opentracing.SpanFromContext(ctx.Request.Context()); preSpan == nil { + inject := opentracingKit.HTTPToContext(opentracing.GlobalTracer(), operationName, logKit.NewNopLogger()) + spanCtx = inject(ctx.Request.Context(), ctx.Request) + span = opentracing.SpanFromContext(spanCtx) + } else { + span, spanCtx = opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) + } + defer span.Finish() newReq := ctx.Request.Clone(spanCtx) @@ -49,7 +64,7 @@ func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.Filt } } -func (builder *FilterChainBuilder) operationName(ctx *context.Context) string { +func (builder *FilterChainBuilder) operationName(ctx *beegoCtx.Context) string { operationName := ctx.Input.URL() // it means that there is not any span, so we create a span as the root span. // TODO, if we support multiple servers, this need to be changed