github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/prometheus/promhttp/http.go (about)

     1  // Copyright 2016 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  // Package promhttp provides tooling around HTTP servers and clients.
    15  //
    16  // First, the package allows the creation of http.Handler instances to expose
    17  // Prometheus metrics via HTTP. promhttp.Handler acts on the
    18  // prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a
    19  // custom registry or anything that implements the Gatherer interface. It also
    20  // allows the creation of handlers that act differently on errors or allow to
    21  // log errors.
    22  //
    23  // Second, the package provides tooling to instrument instances of http.Handler
    24  // via middleware. Middleware wrappers follow the naming scheme
    25  // InstrumentHandlerX, where X describes the intended use of the middleware.
    26  // See each function's doc comment for specific details.
    27  //
    28  // Finally, the package allows for an http.RoundTripper to be instrumented via
    29  // middleware. Middleware wrappers follow the naming scheme
    30  // InstrumentRoundTripperX, where X describes the intended use of the
    31  // middleware. See each function's doc comment for specific details.
    32  package promhttp
    33  
    34  import (
    35  	"compress/gzip"
    36  	"fmt"
    37  	"io"
    38  	"strings"
    39  	"sync"
    40  	"time"
    41  
    42  	http "github.com/hxx258456/ccgo/gmhttp"
    43  	"github.com/hxx258456/ccgo/prometheus/common/expfmt"
    44  
    45  	"github.com/prometheus/client_golang/prometheus"
    46  )
    47  
    48  const (
    49  	contentTypeHeader     = "Content-Type"
    50  	contentEncodingHeader = "Content-Encoding"
    51  	acceptEncodingHeader  = "Accept-Encoding"
    52  )
    53  
    54  var gzipPool = sync.Pool{
    55  	New: func() interface{} {
    56  		return gzip.NewWriter(nil)
    57  	},
    58  }
    59  
    60  // Handler returns an http.Handler for the prometheus.DefaultGatherer, using
    61  // default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
    62  // no error logging, and it applies compression if requested by the client.
    63  //
    64  // The returned http.Handler is already instrumented using the
    65  // InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
    66  // create multiple http.Handlers by separate calls of the Handler function, the
    67  // metrics used for instrumentation will be shared between them, providing
    68  // global scrape counts.
    69  //
    70  // This function is meant to cover the bulk of basic use cases. If you are doing
    71  // anything that requires more customization (including using a non-default
    72  // Gatherer, different instrumentation, and non-default HandlerOpts), use the
    73  // HandlerFor function. See there for details.
    74  func Handler() http.Handler {
    75  	return InstrumentMetricHandler(
    76  		prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
    77  	)
    78  }
    79  
    80  // HandlerFor returns an uninstrumented http.Handler for the provided
    81  // Gatherer. The behavior of the Handler is defined by the provided
    82  // HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
    83  // Gatherers, with non-default HandlerOpts, and/or with custom (or no)
    84  // instrumentation. Use the InstrumentMetricHandler function to apply the same
    85  // kind of instrumentation as it is used by the Handler function.
    86  func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
    87  	var (
    88  		inFlightSem chan struct{}
    89  		errCnt      = prometheus.NewCounterVec(
    90  			prometheus.CounterOpts{
    91  				Name: "promhttp_metric_handler_errors_total",
    92  				Help: "Total number of internal errors encountered by the promhttp metric handler.",
    93  			},
    94  			[]string{"cause"},
    95  		)
    96  	)
    97  
    98  	if opts.MaxRequestsInFlight > 0 {
    99  		inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
   100  	}
   101  	if opts.Registry != nil {
   102  		// Initialize all possibilities that can occur below.
   103  		errCnt.WithLabelValues("gathering")
   104  		errCnt.WithLabelValues("encoding")
   105  		if err := opts.Registry.Register(errCnt); err != nil {
   106  			if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
   107  				errCnt = are.ExistingCollector.(*prometheus.CounterVec)
   108  			} else {
   109  				panic(err)
   110  			}
   111  		}
   112  	}
   113  
   114  	h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
   115  		if inFlightSem != nil {
   116  			select {
   117  			case inFlightSem <- struct{}{}: // All good, carry on.
   118  				defer func() { <-inFlightSem }()
   119  			default:
   120  				http.Error(rsp, fmt.Sprintf(
   121  					"Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
   122  				), http.StatusServiceUnavailable)
   123  				return
   124  			}
   125  		}
   126  		mfs, err := reg.Gather()
   127  		if err != nil {
   128  			if opts.ErrorLog != nil {
   129  				opts.ErrorLog.Println("error gathering metrics:", err)
   130  			}
   131  			errCnt.WithLabelValues("gathering").Inc()
   132  			switch opts.ErrorHandling {
   133  			case PanicOnError:
   134  				panic(err)
   135  			case ContinueOnError:
   136  				if len(mfs) == 0 {
   137  					// Still report the error if no metrics have been gathered.
   138  					httpError(rsp, err)
   139  					return
   140  				}
   141  			case HTTPErrorOnError:
   142  				httpError(rsp, err)
   143  				return
   144  			}
   145  		}
   146  
   147  		var contentType expfmt.Format
   148  		if opts.EnableOpenMetrics {
   149  			contentType = expfmt.NegotiateIncludingOpenMetrics(req.Header)
   150  		} else {
   151  			contentType = expfmt.Negotiate(req.Header)
   152  		}
   153  		header := rsp.Header()
   154  		header.Set(contentTypeHeader, string(contentType))
   155  
   156  		w := io.Writer(rsp)
   157  		if !opts.DisableCompression && gzipAccepted(req.Header) {
   158  			header.Set(contentEncodingHeader, "gzip")
   159  			gz := gzipPool.Get().(*gzip.Writer)
   160  			defer gzipPool.Put(gz)
   161  
   162  			gz.Reset(w)
   163  			defer gz.Close()
   164  
   165  			w = gz
   166  		}
   167  
   168  		enc := expfmt.NewEncoder(w, contentType)
   169  
   170  		// handleError handles the error according to opts.ErrorHandling
   171  		// and returns true if we have to abort after the handling.
   172  		handleError := func(err error) bool {
   173  			if err == nil {
   174  				return false
   175  			}
   176  			if opts.ErrorLog != nil {
   177  				opts.ErrorLog.Println("error encoding and sending metric family:", err)
   178  			}
   179  			errCnt.WithLabelValues("encoding").Inc()
   180  			switch opts.ErrorHandling {
   181  			case PanicOnError:
   182  				panic(err)
   183  			case HTTPErrorOnError:
   184  				// We cannot really send an HTTP error at this
   185  				// point because we most likely have written
   186  				// something to rsp already. But at least we can
   187  				// stop sending.
   188  				return true
   189  			}
   190  			// Do nothing in all other cases, including ContinueOnError.
   191  			return false
   192  		}
   193  
   194  		for _, mf := range mfs {
   195  			if handleError(enc.Encode(mf)) {
   196  				return
   197  			}
   198  		}
   199  		if closer, ok := enc.(expfmt.Closer); ok {
   200  			// This in particular takes care of the final "# EOF\n" line for OpenMetrics.
   201  			if handleError(closer.Close()) {
   202  				return
   203  			}
   204  		}
   205  	})
   206  
   207  	if opts.Timeout <= 0 {
   208  		return h
   209  	}
   210  	return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
   211  		"Exceeded configured timeout of %v.\n",
   212  		opts.Timeout,
   213  	))
   214  }
   215  
   216  // InstrumentMetricHandler is usually used with an http.Handler returned by the
   217  // HandlerFor function. It instruments the provided http.Handler with two
   218  // metrics: A counter vector "promhttp_metric_handler_requests_total" to count
   219  // scrapes partitioned by HTTP status code, and a gauge
   220  // "promhttp_metric_handler_requests_in_flight" to track the number of
   221  // simultaneous scrapes. This function idempotently registers collectors for
   222  // both metrics with the provided Registerer. It panics if the registration
   223  // fails. The provided metrics are useful to see how many scrapes hit the
   224  // monitored target (which could be from different Prometheus servers or other
   225  // scrapers), and how often they overlap (which would result in more than one
   226  // scrape in flight at the same time). Note that the scrapes-in-flight gauge
   227  // will contain the scrape by which it is exposed, while the scrape counter will
   228  // only get incremented after the scrape is complete (as only then the status
   229  // code is known). For tracking scrape durations, use the
   230  // "scrape_duration_seconds" gauge created by the Prometheus server upon each
   231  // scrape.
   232  func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
   233  	cnt := prometheus.NewCounterVec(
   234  		prometheus.CounterOpts{
   235  			Name: "promhttp_metric_handler_requests_total",
   236  			Help: "Total number of scrapes by HTTP status code.",
   237  		},
   238  		[]string{"code"},
   239  	)
   240  	// Initialize the most likely HTTP status codes.
   241  	cnt.WithLabelValues("200")
   242  	cnt.WithLabelValues("500")
   243  	cnt.WithLabelValues("503")
   244  	if err := reg.Register(cnt); err != nil {
   245  		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
   246  			cnt = are.ExistingCollector.(*prometheus.CounterVec)
   247  		} else {
   248  			panic(err)
   249  		}
   250  	}
   251  
   252  	gge := prometheus.NewGauge(prometheus.GaugeOpts{
   253  		Name: "promhttp_metric_handler_requests_in_flight",
   254  		Help: "Current number of scrapes being served.",
   255  	})
   256  	if err := reg.Register(gge); err != nil {
   257  		if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
   258  			gge = are.ExistingCollector.(prometheus.Gauge)
   259  		} else {
   260  			panic(err)
   261  		}
   262  	}
   263  
   264  	return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
   265  }
   266  
   267  // HandlerErrorHandling defines how a Handler serving metrics will handle
   268  // errors.
   269  type HandlerErrorHandling int
   270  
   271  // These constants cause handlers serving metrics to behave as described if
   272  // errors are encountered.
   273  const (
   274  	// Serve an HTTP status code 500 upon the first error
   275  	// encountered. Report the error message in the body. Note that HTTP
   276  	// errors cannot be served anymore once the beginning of a regular
   277  	// payload has been sent. Thus, in the (unlikely) case that encoding the
   278  	// payload into the negotiated wire format fails, serving the response
   279  	// will simply be aborted. Set an ErrorLog in HandlerOpts to detect
   280  	// those errors.
   281  	HTTPErrorOnError HandlerErrorHandling = iota
   282  	// Ignore errors and try to serve as many metrics as possible.  However,
   283  	// if no metrics can be served, serve an HTTP status code 500 and the
   284  	// last error message in the body. Only use this in deliberate "best
   285  	// effort" metrics collection scenarios. In this case, it is highly
   286  	// recommended to provide other means of detecting errors: By setting an
   287  	// ErrorLog in HandlerOpts, the errors are logged. By providing a
   288  	// Registry in HandlerOpts, the exposed metrics include an error counter
   289  	// "promhttp_metric_handler_errors_total", which can be used for
   290  	// alerts.
   291  	ContinueOnError
   292  	// Panic upon the first error encountered (useful for "crash only" apps).
   293  	PanicOnError
   294  )
   295  
   296  // Logger is the minimal interface HandlerOpts needs for logging. Note that
   297  // log.Logger from the standard library implements this interface, and it is
   298  // easy to implement by custom loggers, if they don't do so already anyway.
   299  type Logger interface {
   300  	Println(v ...interface{})
   301  }
   302  
   303  // HandlerOpts specifies options how to serve metrics via an http.Handler. The
   304  // zero value of HandlerOpts is a reasonable default.
   305  type HandlerOpts struct {
   306  	// ErrorLog specifies an optional Logger for errors collecting and
   307  	// serving metrics. If nil, errors are not logged at all. Note that the
   308  	// type of a reported error is often prometheus.MultiError, which
   309  	// formats into a multi-line error string. If you want to avoid the
   310  	// latter, create a Logger implementation that detects a
   311  	// prometheus.MultiError and formats the contained errors into one line.
   312  	ErrorLog Logger
   313  	// ErrorHandling defines how errors are handled. Note that errors are
   314  	// logged regardless of the configured ErrorHandling provided ErrorLog
   315  	// is not nil.
   316  	ErrorHandling HandlerErrorHandling
   317  	// If Registry is not nil, it is used to register a metric
   318  	// "promhttp_metric_handler_errors_total", partitioned by "cause". A
   319  	// failed registration causes a panic. Note that this error counter is
   320  	// different from the instrumentation you get from the various
   321  	// InstrumentHandler... helpers. It counts errors that don't necessarily
   322  	// result in a non-2xx HTTP status code. There are two typical cases:
   323  	// (1) Encoding errors that only happen after streaming of the HTTP body
   324  	// has already started (and the status code 200 has been sent). This
   325  	// should only happen with custom collectors. (2) Collection errors with
   326  	// no effect on the HTTP status code because ErrorHandling is set to
   327  	// ContinueOnError.
   328  	Registry prometheus.Registerer
   329  	// If DisableCompression is true, the handler will never compress the
   330  	// response, even if requested by the client.
   331  	DisableCompression bool
   332  	// The number of concurrent HTTP requests is limited to
   333  	// MaxRequestsInFlight. Additional requests are responded to with 503
   334  	// Service Unavailable and a suitable message in the body. If
   335  	// MaxRequestsInFlight is 0 or negative, no limit is applied.
   336  	MaxRequestsInFlight int
   337  	// If handling a request takes longer than Timeout, it is responded to
   338  	// with 503 ServiceUnavailable and a suitable Message. No timeout is
   339  	// applied if Timeout is 0 or negative. Note that with the current
   340  	// implementation, reaching the timeout simply ends the HTTP requests as
   341  	// described above (and even that only if sending of the body hasn't
   342  	// started yet), while the bulk work of gathering all the metrics keeps
   343  	// running in the background (with the eventual result to be thrown
   344  	// away). Until the implementation is improved, it is recommended to
   345  	// implement a separate timeout in potentially slow Collectors.
   346  	Timeout time.Duration
   347  	// If true, the experimental OpenMetrics encoding is added to the
   348  	// possible options during content negotiation. Note that Prometheus
   349  	// 2.5.0+ will negotiate OpenMetrics as first priority. OpenMetrics is
   350  	// the only way to transmit exemplars. However, the move to OpenMetrics
   351  	// is not completely transparent. Most notably, the values of "quantile"
   352  	// labels of Summaries and "le" labels of Histograms are formatted with
   353  	// a trailing ".0" if they would otherwise look like integer numbers
   354  	// (which changes the identity of the resulting series on the Prometheus
   355  	// server).
   356  	EnableOpenMetrics bool
   357  }
   358  
   359  // gzipAccepted returns whether the client will accept gzip-encoded content.
   360  func gzipAccepted(header http.Header) bool {
   361  	a := header.Get(acceptEncodingHeader)
   362  	parts := strings.Split(a, ",")
   363  	for _, part := range parts {
   364  		part = strings.TrimSpace(part)
   365  		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
   366  			return true
   367  		}
   368  	}
   369  	return false
   370  }
   371  
   372  // httpError removes any content-encoding header and then calls http.Error with
   373  // the provided error and http.StatusInternalServerError. Error contents is
   374  // supposed to be uncompressed plain text. Same as with a plain http.Error, this
   375  // must not be called if the header or any payload has already been sent.
   376  func httpError(rsp http.ResponseWriter, err error) {
   377  	rsp.Header().Del(contentEncodingHeader)
   378  	http.Error(
   379  		rsp,
   380  		"An error has occurred while serving metrics:\n\n"+err.Error(),
   381  		http.StatusInternalServerError,
   382  	)
   383  }