func()

in receiver/prometheusreceiver/metrics_receiver.go [234:386]


func (r *pReceiver) initAPIServer(ctx context.Context, host component.Host) error {
	r.settings.Logger.Info("Starting Prometheus API server")

	// If allowed CORS origins are provided in the receiver config, combine them into a single regex since the Prometheus API server requires this format.
	var corsOriginRegexp *grafanaRegexp.Regexp
	if r.cfg.APIServer.ServerConfig.CORS != nil && len(r.cfg.APIServer.ServerConfig.CORS.AllowedOrigins) > 0 {
		var combinedOriginsBuilder strings.Builder
		combinedOriginsBuilder.WriteString(r.cfg.APIServer.ServerConfig.CORS.AllowedOrigins[0])
		for _, origin := range r.cfg.APIServer.ServerConfig.CORS.AllowedOrigins[1:] {
			combinedOriginsBuilder.WriteString("|")
			combinedOriginsBuilder.WriteString(origin)
		}
		combinedRegexp, err := grafanaRegexp.Compile(combinedOriginsBuilder.String())
		if err != nil {
			return fmt.Errorf("failed to compile combined CORS allowed origins into regex: %s", err.Error())
		}
		corsOriginRegexp = combinedRegexp
	}

	// If read timeout is not set in the receiver config, use the default Prometheus value.
	readTimeout := r.cfg.APIServer.ServerConfig.ReadTimeout
	if readTimeout == 0 {
		readTimeout = time.Duration(readTimeoutMinutes) * time.Minute
	}

	o := &web.Options{
		ScrapeManager:   r.scrapeManager,
		Context:         ctx,
		ListenAddresses: []string{r.cfg.APIServer.ServerConfig.Endpoint},
		ExternalURL: &url.URL{
			Scheme: "http",
			Host:   r.cfg.APIServer.ServerConfig.Endpoint,
			Path:   "",
		},
		RoutePrefix:    "/",
		ReadTimeout:    readTimeout,
		PageTitle:      "Prometheus Receiver",
		Flags:          make(map[string]string),
		MaxConnections: maxConnections,
		IsAgent:        true,
		Registerer:     r.registerer,
		Gatherer:       r.registry,
		CORSOrigin:     corsOriginRegexp,
	}

	// Creates the API object in the same way as the Prometheus web package: https://github.com/prometheus/prometheus/blob/6150e1ca0ede508e56414363cc9062ef522db518/web/web.go#L314-L354
	// Anything not defined by the options above will be nil, such as o.QueryEngine, o.Storage, etc. IsAgent=true, so these being nil is expected by Prometheus.
	factorySPr := func(_ context.Context) api_v1.ScrapePoolsRetriever { return o.ScrapeManager }
	factoryTr := func(_ context.Context) api_v1.TargetRetriever { return o.ScrapeManager }
	factoryAr := func(_ context.Context) api_v1.AlertmanagerRetriever { return nil }
	factoryRr := func(_ context.Context) api_v1.RulesRetriever { return nil }
	var app storage.Appendable
	logger := promslog.NewNopLogger()

	apiV1 := api_v1.NewAPI(o.QueryEngine, o.Storage, app, o.ExemplarStorage, factorySPr, factoryTr, factoryAr,

		// This ensures that any changes to the config made, even by the target allocator, are reflected in the API.
		func() promconfig.Config {
			return *(*promconfig.Config)(r.cfg.PrometheusConfig)
		},
		o.Flags, // nil
		api_v1.GlobalURLOptions{
			ListenAddress: o.ListenAddresses[0],
			Host:          o.ExternalURL.Host,
			Scheme:        o.ExternalURL.Scheme,
		},
		func(f http.HandlerFunc) http.HandlerFunc {
			return func(w http.ResponseWriter, r *http.Request) {
				f(w, r)
			}
		},
		o.LocalStorage,   // nil
		o.TSDBDir,        // nil
		o.EnableAdminAPI, // nil
		logger,
		factoryRr,
		o.RemoteReadSampleLimit,      // nil
		o.RemoteReadConcurrencyLimit, // nil
		o.RemoteReadBytesInFrame,     // nil
		o.IsAgent,
		o.CORSOrigin,
		func() (api_v1.RuntimeInfo, error) {
			status := api_v1.RuntimeInfo{
				GoroutineCount: runtime.NumGoroutine(),
				GOMAXPROCS:     runtime.GOMAXPROCS(0),
				GOMEMLIMIT:     debug.SetMemoryLimit(-1),
				GOGC:           os.Getenv("GOGC"),
				GODEBUG:        os.Getenv("GODEBUG"),
			}

			return status, nil
		},
		&web.PrometheusVersion{
			Version:   version.Version,
			Revision:  version.Revision,
			Branch:    version.Branch,
			BuildUser: version.BuildUser,
			BuildDate: version.BuildDate,
			GoVersion: version.GoVersion,
		},
		o.NotificationsGetter,
		o.NotificationsSub,
		o.Gatherer,
		o.Registerer,
		nil,
		o.EnableRemoteWriteReceiver,
		o.AcceptRemoteWriteProtoMsgs,
		o.EnableOTLPWriteReceiver,
	)

	// Create listener and monitor with conntrack in the same way as the Prometheus web package: https://github.com/prometheus/prometheus/blob/6150e1ca0ede508e56414363cc9062ef522db518/web/web.go#L564-L579
	listener, err := r.cfg.APIServer.ServerConfig.ToListener(ctx)
	if err != nil {
		return fmt.Errorf("failed to create listener: %s", err.Error())
	}
	listener = netutil.LimitListener(listener, o.MaxConnections)
	listener = conntrack.NewListener(listener,
		conntrack.TrackWithName("http"),
		conntrack.TrackWithTracing())

	// Run the API server in the same way as the Prometheus web package: https://github.com/prometheus/prometheus/blob/6150e1ca0ede508e56414363cc9062ef522db518/web/web.go#L582-L630
	mux := http.NewServeMux()
	promHandler := promhttp.HandlerFor(o.Gatherer, promhttp.HandlerOpts{Registry: o.Registerer})
	mux.Handle("/metrics", promHandler)

	// This is the path the web package uses, but the router above with no prefix can also be Registered by apiV1 instead.
	apiPath := "/api"
	if o.RoutePrefix != "/" {
		apiPath = o.RoutePrefix + apiPath
		logger.Info("Router prefix", "prefix", o.RoutePrefix)
	}
	av1 := route.New().
		WithInstrumentation(setPathWithPrefix(apiPath + "/v1"))
	apiV1.Register(av1)
	mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1))

	spanNameFormatter := otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
		return fmt.Sprintf("%s %s", r.Method, r.URL.Path)
	})
	r.apiServer, err = r.cfg.APIServer.ServerConfig.ToServer(ctx, host, r.settings.TelemetrySettings, otelhttp.NewHandler(mux, "", spanNameFormatter))
	if err != nil {
		return err
	}
	webconfig := ""

	go func() {
		if err := toolkit_web.Serve(listener, r.apiServer, &toolkit_web.FlagConfig{WebConfigFile: &webconfig}, logger); err != nil {
			r.settings.Logger.Error("API server failed", zap.Error(err))
		}
	}()

	return nil
}