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
}