1
0
mirror of https://github.com/astaxie/beego.git synced 2025-06-24 22:30:17 +00:00

Support prometheus and opentracing filter

This commit is contained in:
Ming Deng
2020-08-04 22:16:55 +08:00
parent ae8461f95d
commit 6c6cf91741
8 changed files with 206 additions and 58 deletions

16
pkg/web/doc.go Normal file

@ -0,0 +1,16 @@
// 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.
// we will move all web related codes here
package web

@ -0,0 +1,56 @@
// 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 (
"github.com/opentracing/opentracing-go"
beego "github.com/astaxie/beego/pkg"
"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)
}
func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.FilterFunc {
// TODO, if we support multiple servers, this need to be changed
cr := beego.BeeApp.Handlers
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.
route, found := cr.FindRouter(ctx)
if found {
operationName = route.GetPattern()
}
span, spanCtx = opentracing.StartSpanFromContext(spanCtx, operationName)
}
defer span.Finish()
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)
span.SetTag("method", ctx.Input.Method())
span.SetTag("route", ctx.Input.GetData("RouterPattern"))
if builder.CustomSpanFunc != nil {
builder.CustomSpanFunc(span, ctx)
}
}
}

@ -0,0 +1,47 @@
// 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 (
"net/http"
"net/http/httptest"
"testing"
"github.com/opentracing/opentracing-go"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/pkg/context"
)
func TestFilterChainBuilder_FilterChain(t *testing.T) {
builder := &FilterChainBuilder{
CustomSpanFunc: func(span opentracing.Span, ctx *context.Context) {
span.SetTag("aa", "bbb")
},
}
ctx := context.NewContext()
r, _ := http.NewRequest("GET", "/prometheus/user", nil)
w := httptest.NewRecorder()
ctx.Reset(w, r)
ctx.Input.SetData("RouterPattern", "my-route")
filterFunc := builder.FilterChain(func(ctx *context.Context) {
ctx.Input.SetData("opentracing", true)
})
filterFunc(ctx)
assert.True(t, ctx.Input.GetData("opentracing").(bool))
}

@ -1,4 +1,4 @@
// Copyright 2020 astaxie
// 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.
@ -12,22 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package metric
package prometheus
import (
"net/http"
"reflect"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/astaxie/beego/pkg"
"github.com/astaxie/beego/pkg/logs"
beego "github.com/astaxie/beego/pkg"
"github.com/astaxie/beego/pkg/context"
)
func PrometheusMiddleWare(next http.Handler) http.Handler {
// FilterChainBuilder is an extension point,
// when we want to support some configuration,
// please use this structure
type FilterChainBuilder struct {
}
// FilterChain returns a FilterFunc. The filter will records some metrics
func (builder *FilterChainBuilder) FilterChain(next beego.FilterFunc) beego.FilterFunc {
summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "http_request",
@ -43,12 +48,12 @@ func PrometheusMiddleWare(next http.Handler) http.Handler {
registerBuildInfo()
return http.HandlerFunc(func(writer http.ResponseWriter, q *http.Request) {
start := time.Now()
next.ServeHTTP(writer, q)
end := time.Now()
go report(end.Sub(start), writer, q, summaryVec)
})
return func(ctx *context.Context) {
startTime := time.Now()
next(ctx)
endTime := time.Now()
go report(endTime.Sub(startTime), ctx, summaryVec)
}
}
func registerBuildInfo() {
@ -73,27 +78,9 @@ func registerBuildInfo() {
buildInfo.WithLabelValues().Set(1)
}
func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) {
ctrl := beego.BeeApp.Handlers
ctx := ctrl.GetContext()
ctx.Reset(writer, q)
defer ctrl.GiveBackContext(ctx)
// We cannot read the status code from q.Response.StatusCode
// since the http server does not set q.Response. So q.Response is nil
// Thus, we use reflection to read the status from writer whose concrete type is http.response
responseVal := reflect.ValueOf(writer).Elem()
field := responseVal.FieldByName("status")
status := -1
if field.IsValid() && field.Kind() == reflect.Int {
status = int(field.Int())
}
ptn := "UNKNOWN"
if rt, found := ctrl.FindRouter(ctx); found {
ptn = rt.GetPattern()
} else {
logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String())
}
func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) {
status := ctx.Output.Status
ptn := ctx.Input.GetData("RouterPattern").(string)
ms := dur / time.Millisecond
vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms))
vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms))
}

@ -1,4 +1,4 @@
// Copyright 2020 astaxie
// 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.
@ -12,31 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package metric
package prometheus
import (
"net/http"
"net/url"
"net/http/httptest"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/astaxie/beego/pkg/context"
)
func TestPrometheusMiddleWare(t *testing.T) {
middleware := PrometheusMiddleWare(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
writer := &context.Response{}
request := &http.Request{
URL: &url.URL{
Host: "localhost",
RawPath: "/a/b/c",
},
Method: "POST",
}
vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status", "duration"})
func TestFilterChain(t *testing.T) {
filter := (&FilterChainBuilder{}).FilterChain(func(ctx *context.Context) {
// do nothing
ctx.Input.SetData("invocation", true)
})
report(time.Second, writer, request, vec)
middleware.ServeHTTP(writer, request)
ctx := context.NewContext()
r, _ := http.NewRequest("GET", "/prometheus/user", nil)
w := httptest.NewRecorder()
ctx.Reset(w, r)
ctx.Input.SetData("RouterPattern", "my-route")
filter(ctx)
assert.True(t, ctx.Input.GetData("invocation").(bool))
}