diff --git a/CHANGELOG.md b/CHANGELOG.md index 9235ec94..6041f5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,6 @@ - Feature issue #4402 finish router get example. [4416](https://github.com/beego/beego/pull/4416) - Implement context.Context support and deprecate `QueryM2MWithCtx` and `QueryTableWithCtx` [4424](https://github.com/beego/beego/pull/4424) - Finish timeout option for tasks #4441 [4441](https://github.com/beego/beego/pull/4441) -- Error Module brief design & using httplib module to validate this design. [4453](https://github.com/beego/beego/pull/4453) \ No newline at end of file +- Error Module brief design & using httplib module to validate this design. [4453](https://github.com/beego/beego/pull/4453) +- Fix 4444: panic when 404 not found. [4446](https://github.com/beego/beego/pull/4446) +- Fix 4435: fix panic when controller dir not found. [4452](https://github.com/beego/beego/pull/4452) \ No newline at end of file diff --git a/client/httplib/filter/prometheus/filter.go b/client/httplib/filter/prometheus/filter.go index 3d5acf12..5761eb7e 100644 --- a/client/httplib/filter/prometheus/filter.go +++ b/client/httplib/filter/prometheus/filter.go @@ -18,6 +18,7 @@ import ( "context" "net/http" "strconv" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -26,24 +27,30 @@ import ( ) type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec AppName string ServerName string RunMode string } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter { - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "remote_http_request", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for remote http requests", - }, []string{"proto", "scheme", "method", "host", "path", "status", "isError"}) + initSummaryVec.Do(func() { + summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "remote_http_request", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for remote http requests", + }, []string{"proto", "scheme", "method", "host", "path", "status", "isError"}) + + prometheus.MustRegister(summaryVec) + }) return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) { startTime := time.Now() @@ -72,6 +79,6 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time dur := int(endTime.Sub(startTime) / time.Millisecond) - builder.summaryVec.WithLabelValues(proto, scheme, method, host, path, + summaryVec.WithLabelValues(proto, scheme, method, host, path, strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur)) } diff --git a/client/orm/filter/prometheus/filter.go b/client/orm/filter/prometheus/filter.go index e68e9670..270deaf6 100644 --- a/client/orm/filter/prometheus/filter.go +++ b/client/orm/filter/prometheus/filter.go @@ -18,6 +18,7 @@ import ( "context" "strconv" "strings" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -33,24 +34,29 @@ import ( // if we want to records the metrics of QuerySetter // actually we only records metrics of invoking "QueryTable" type FilterChainBuilder struct { - summaryVec prometheus.ObserverVec AppName string ServerName string RunMode string } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter { - builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Name: "beego", - Subsystem: "orm_operation", - ConstLabels: map[string]string{ - "server": builder.ServerName, - "env": builder.RunMode, - "appname": builder.AppName, - }, - Help: "The statics info for orm operation", - }, []string{"method", "name", "insideTx", "txName"}) + initSummaryVec.Do(func() { + summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "beego", + Subsystem: "orm_operation", + ConstLabels: map[string]string{ + "server": builder.ServerName, + "env": builder.RunMode, + "appname": builder.AppName, + }, + Help: "The statics info for orm operation", + }, []string{"method", "name", "insideTx", "txName"}) + prometheus.MustRegister(summaryVec) + }) return func(ctx context.Context, inv *orm.Invocation) []interface{} { startTime := time.Now() @@ -74,12 +80,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati builder.reportTxn(ctx, inv) return } - builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), + summaryVec.WithLabelValues(inv.Method, inv.GetTableName(), strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) { dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond - builder.summaryVec.WithLabelValues(inv.Method, inv.TxName, + summaryVec.WithLabelValues(inv.Method, inv.TxName, strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur)) } diff --git a/client/orm/filter/prometheus/filter_test.go b/client/orm/filter/prometheus/filter_test.go index 92316557..a25515a7 100644 --- a/client/orm/filter/prometheus/filter_test.go +++ b/client/orm/filter/prometheus/filter_test.go @@ -32,7 +32,7 @@ func TestFilterChainBuilder_FilterChain1(t *testing.T) { builder := &FilterChainBuilder{} filter := builder.FilterChain(next) - assert.NotNil(t, builder.summaryVec) + assert.NotNil(t, summaryVec) assert.NotNil(t, filter) inv := &orm.Invocation{} diff --git a/server/web/admin.go b/server/web/admin.go index 89c9ddb9..3f665b09 100644 --- a/server/web/admin.go +++ b/server/web/admin.go @@ -108,8 +108,11 @@ func registerAdmin() error { c := &adminController{ servers: make([]*HttpServer, 0, 2), } + + // copy config to avoid conflict + adminCfg := *BConfig beeAdminApp = &adminApp{ - HttpServer: NewHttpServerWithCfg(BConfig), + HttpServer: NewHttpServerWithCfg(&adminCfg), } // keep in mind that all data should be html escaped to avoid XSS attack beeAdminApp.Router("/", c, "get:AdminIndex") diff --git a/server/web/filter/prometheus/filter.go b/server/web/filter/prometheus/filter.go index 59a673ac..520170e0 100644 --- a/server/web/filter/prometheus/filter.go +++ b/server/web/filter/prometheus/filter.go @@ -17,23 +17,49 @@ package prometheus import ( "strconv" "strings" + "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/beego/beego/v2" + "github.com/beego/beego/v2/core/logs" "github.com/beego/beego/v2/server/web" "github.com/beego/beego/v2/server/web/context" ) +const unknownRouterPattern = "UnknownRouterPattern" + // FilterChainBuilder is an extension point, // when we want to support some configuration, // please use this structure type FilterChainBuilder struct { } +var summaryVec prometheus.ObserverVec +var initSummaryVec sync.Once + // FilterChain returns a FilterFunc. The filter will records some metrics func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFunc { + + initSummaryVec.Do(func() { + summaryVec = builder.buildVec() + err := prometheus.Register(summaryVec) + if _, ok := err.(*prometheus.AlreadyRegisteredError); err != nil && !ok { + logs.Error("web module register prometheus vector failed, %+v", err) + } + registerBuildInfo() + }) + + return func(ctx *context.Context) { + startTime := time.Now() + next(ctx) + endTime := time.Now() + go report(endTime.Sub(startTime), ctx, summaryVec) + } +} + +func (builder *FilterChainBuilder) buildVec() *prometheus.SummaryVec { summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Name: "beego", Subsystem: "http_request", @@ -44,17 +70,7 @@ func (builder *FilterChainBuilder) FilterChain(next web.FilterFunc) web.FilterFu }, Help: "The statics info for http request", }, []string{"pattern", "method", "status"}) - - prometheus.MustRegister(summaryVec) - - registerBuildInfo() - - return func(ctx *context.Context) { - startTime := time.Now() - next(ctx) - endTime := time.Now() - go report(endTime.Sub(startTime), ctx, summaryVec) - } + return summaryVec } func registerBuildInfo() { @@ -75,13 +91,17 @@ func registerBuildInfo() { }, }, []string{}) - prometheus.MustRegister(buildInfo) + _ = prometheus.Register(buildInfo) buildInfo.WithLabelValues().Set(1) } -func report(dur time.Duration, ctx *context.Context, vec *prometheus.SummaryVec) { +func report(dur time.Duration, ctx *context.Context, vec prometheus.ObserverVec) { status := ctx.Output.Status - ptn := ctx.Input.GetData("RouterPattern").(string) + ptnItf := ctx.Input.GetData("RouterPattern") + ptn := unknownRouterPattern + if ptnItf != nil { + ptn = ptnItf.(string) + } ms := dur / time.Millisecond vec.WithLabelValues(ptn, ctx.Input.Method(), strconv.Itoa(status)).Observe(float64(ms)) } diff --git a/server/web/filter/prometheus/filter_test.go b/server/web/filter/prometheus/filter_test.go index be008d41..7d88d843 100644 --- a/server/web/filter/prometheus/filter_test.go +++ b/server/web/filter/prometheus/filter_test.go @@ -40,3 +40,17 @@ func TestFilterChain(t *testing.T) { assert.True(t, ctx.Input.GetData("invocation").(bool)) time.Sleep(1 * time.Second) } + +func TestFilterChainBuilder_report(t *testing.T) { + + ctx := context.NewContext() + r, _ := http.NewRequest("GET", "/prometheus/user", nil) + w := httptest.NewRecorder() + ctx.Reset(w, r) + fb := &FilterChainBuilder{} + // without router info + report(time.Second, ctx, fb.buildVec()) + + ctx.Input.SetData("RouterPattern", "my-route") + report(time.Second, ctx, fb.buildVec()) +} \ No newline at end of file diff --git a/server/web/hooks.go b/server/web/hooks.go index ae32b9f8..f91bec1e 100644 --- a/server/web/hooks.go +++ b/server/web/hooks.go @@ -6,6 +6,8 @@ import ( "net/http" "path/filepath" + "github.com/coreos/etcd/pkg/fileutil" + "github.com/beego/beego/v2/core/logs" "github.com/beego/beego/v2/server/web/context" "github.com/beego/beego/v2/server/web/session" @@ -99,7 +101,12 @@ func registerGzip() error { func registerCommentRouter() error { if BConfig.RunMode == DEV { - if err := parserPkg(filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath)); err != nil { + ctrlDir := filepath.Join(WorkPath, BConfig.WebConfig.CommentRouterPath) + if !fileutil.Exist(ctrlDir) { + logs.Warn("controller package not found, won't generate router: ", ctrlDir) + return nil + } + if err := parserPkg(ctrlDir); err != nil { return err } }