diff --git a/pkg/orm/filter.go b/pkg/orm/filter.go index 9676e4af..d04b8c42 100644 --- a/pkg/orm/filter.go +++ b/pkg/orm/filter.go @@ -18,8 +18,12 @@ import ( "context" ) +// FilterChain is used to build a Filter +// don't forget to call next(...) inside your Filter type FilterChain func(next Filter) Filter +// Filter's behavior is a little big strang. +// it's only be called when users call methods of Ormer type Filter func(ctx context.Context, inv *Invocation) var globalFilterChains = make([]FilterChain, 0, 4) diff --git a/pkg/orm/filter/opentracing/filter.go b/pkg/orm/filter/opentracing/filter.go new file mode 100644 index 00000000..a55ae6d2 --- /dev/null +++ b/pkg/orm/filter/opentracing/filter.go @@ -0,0 +1,59 @@ +// 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" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/pkg/orm" +) + +// FilterChainBuilder provides an extension point +// this Filter's behavior looks a little bit strange +// for example: +// if we want to trace QuerySetter +// actually we trace invoking "QueryTable" and "QueryTableWithCtx" +type FilterChainBuilder struct { + // CustomSpanFunc users are able to custom their span + CustomSpanFunc func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) +} + +func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { + return func(ctx context.Context, inv *orm.Invocation) { + operationName := builder.operationName(ctx, inv) + span, spanCtx := opentracing.StartSpanFromContext(ctx, operationName) + defer span.Finish() + + next(spanCtx, inv) + span.SetTag("Method", inv.Method) + span.SetTag("Table", inv.GetTableName()) + span.SetTag("InsideTx", inv.InsideTx) + span.SetTag("TxName", spanCtx.Value(orm.TxNameKey)) + + if builder.CustomSpanFunc != nil { + builder.CustomSpanFunc(span, spanCtx, inv) + } + + } +} + +func (builder *FilterChainBuilder) operationName(ctx context.Context, inv *orm.Invocation) string { + if n, ok := ctx.Value(orm.TxNameKey).(string); ok { + return inv.Method + "#" + n + } + return inv.Method + "#" + inv.GetTableName() +} diff --git a/pkg/orm/filter/opentracing/filter_test.go b/pkg/orm/filter/opentracing/filter_test.go new file mode 100644 index 00000000..1428df8a --- /dev/null +++ b/pkg/orm/filter/opentracing/filter_test.go @@ -0,0 +1,43 @@ +// 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" + "testing" + "time" + + "github.com/opentracing/opentracing-go" + + "github.com/astaxie/beego/pkg/orm" +) + +func TestFilterChainBuilder_FilterChain(t *testing.T) { + next := func(ctx context.Context, inv *orm.Invocation) { + inv.TxName = "Hello" + } + + builder := &FilterChainBuilder{ + CustomSpanFunc: func(span opentracing.Span, ctx context.Context, inv *orm.Invocation) { + span.SetTag("hello", "hell") + }, + } + + inv := &orm.Invocation{ + Method: "Hello", + TxStartTime: time.Now(), + } + builder.FilterChain(next)(context.Background(), inv) +} \ No newline at end of file diff --git a/pkg/orm/filter/prometheus/filter.go b/pkg/orm/filter/prometheus/filter.go index 9f177deb..33fdf78f 100644 --- a/pkg/orm/filter/prometheus/filter.go +++ b/pkg/orm/filter/prometheus/filter.go @@ -29,6 +29,10 @@ import ( // FilterChainBuilder is an extension point, // when we want to support some configuration, // please use this structure +// this Filter's behavior looks a little bit strange +// for example: +// if we want to records the metrics of QuerySetter +// actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx" type FilterChainBuilder struct { summaryVec prometheus.ObserverVec } diff --git a/pkg/web/filter/opentracing/filter.go b/pkg/web/filter/opentracing/filter.go index 8e332c7d..82d0f719 100644 --- a/pkg/web/filter/opentracing/filter.go +++ b/pkg/web/filter/opentracing/filter.go @@ -30,22 +30,14 @@ type FilterChainBuilder struct { func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.FilterFunc { return func(ctx *context.Context) { - span := opentracing.SpanFromContext(ctx.Request.Context()) - spanCtx := ctx.Request.Context() - if span == nil { - 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 - route, found := beego.BeeApp.Handlers.FindRouter(ctx) - if found { - operationName = route.GetPattern() - } - span, spanCtx = opentracing.StartSpanFromContext(spanCtx, operationName) - newReq := ctx.Request.Clone(spanCtx) - ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) - } + operationName := builder.operationName(ctx) + span, spanCtx := opentracing.StartSpanFromContext(ctx.Request.Context(), operationName) defer span.Finish() + + newReq := ctx.Request.Clone(spanCtx) + ctx.Reset(ctx.ResponseWriter.ResponseWriter, newReq) + next(ctx) // if you think we need to do more things, feel free to create an issue to tell us span.SetTag("status", ctx.Output.Status) @@ -56,3 +48,14 @@ func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.Filt } } } + +func (builder *FilterChainBuilder) operationName(ctx *context.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 + route, found := beego.BeeApp.Handlers.FindRouter(ctx) + if found { + operationName = route.GetPattern() + } + return operationName +}