github.com/astaxie/beego@v1.12.3/metric/prometheus.go (about)

     1  // Copyright 2020 astaxie
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metric
    16  
    17  import (
    18  	"net/http"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/prometheus/client_golang/prometheus"
    25  
    26  	"github.com/astaxie/beego"
    27  	"github.com/astaxie/beego/logs"
    28  )
    29  
    30  func PrometheusMiddleWare(next http.Handler) http.Handler {
    31  	summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{
    32  		Name:      "beego",
    33  		Subsystem: "http_request",
    34  		ConstLabels: map[string]string{
    35  			"server":  beego.BConfig.ServerName,
    36  			"env":     beego.BConfig.RunMode,
    37  			"appname": beego.BConfig.AppName,
    38  		},
    39  		Help: "The statics info for http request",
    40  	}, []string{"pattern", "method", "status", "duration"})
    41  
    42  	prometheus.MustRegister(summaryVec)
    43  
    44  	registerBuildInfo()
    45  
    46  	return http.HandlerFunc(func(writer http.ResponseWriter, q *http.Request) {
    47  		start := time.Now()
    48  		next.ServeHTTP(writer, q)
    49  		end := time.Now()
    50  		go report(end.Sub(start), writer, q, summaryVec)
    51  	})
    52  }
    53  
    54  func registerBuildInfo() {
    55  	buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{
    56  		Name:      "beego",
    57  		Subsystem: "build_info",
    58  		Help:      "The building information",
    59  		ConstLabels: map[string]string{
    60  			"appname":        beego.BConfig.AppName,
    61  			"build_version":  beego.BuildVersion,
    62  			"build_revision": beego.BuildGitRevision,
    63  			"build_status":   beego.BuildStatus,
    64  			"build_tag":      beego.BuildTag,
    65  			"build_time":     strings.Replace(beego.BuildTime, "--", " ", 1),
    66  			"go_version":     beego.GoVersion,
    67  			"git_branch":     beego.GitBranch,
    68  			"start_time":     time.Now().Format("2006-01-02 15:04:05"),
    69  		},
    70  	}, []string{})
    71  
    72  	prometheus.MustRegister(buildInfo)
    73  	buildInfo.WithLabelValues().Set(1)
    74  }
    75  
    76  func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) {
    77  	ctrl := beego.BeeApp.Handlers
    78  	ctx := ctrl.GetContext()
    79  	ctx.Reset(writer, q)
    80  	defer ctrl.GiveBackContext(ctx)
    81  
    82  	// We cannot read the status code from q.Response.StatusCode
    83  	// since the http server does not set q.Response. So q.Response is nil
    84  	// Thus, we use reflection to read the status from writer whose concrete type is http.response
    85  	responseVal := reflect.ValueOf(writer).Elem()
    86  	field := responseVal.FieldByName("status")
    87  	status := -1
    88  	if field.IsValid() && field.Kind() == reflect.Int {
    89  		status = int(field.Int())
    90  	}
    91  	ptn := "UNKNOWN"
    92  	if rt, found := ctrl.FindRouter(ctx); found {
    93  		ptn = rt.GetPattern()
    94  	} else {
    95  		logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String())
    96  	}
    97  	ms := dur / time.Millisecond
    98  	vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status), strconv.Itoa(int(ms))).Observe(float64(ms))
    99  }