Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:HTTP 路由及二次开发调试

Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:HTTP 路由及二次开发调试

路由相关的具体实现:

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go  

API v1 的具体路由注册实现:

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L669-722
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L354-430
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L61-138
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L427-481
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L301-358
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L474-540
https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#L371-388
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L536-609
https://github.com/prometheus/prometheus/blob/v3.4.0/storage/remote/read_handler.go#L61-115
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L605-672
https://github.com/prometheus/prometheus/blob/v3.4.0/storage/remote/write_handler.go#L621-660
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L180-254

查询处理器实现(web/api/v1):

https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go 

联邦查询处理器(web):

https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go#L51-106
https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go#L103-179
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L301-358
https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#321-388
https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go#L0-55
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L180-254
https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L427-481
https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#438-511
https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go#L199-258
https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#179-235
https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#264-325
https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go#L253-313

        在高并发监控场景中,HTTP 路由选择器的分发效率直接决定了 Prometheus Web 服务的响应能力。当每秒处理数千甚至数万请求时,路由匹配的速度将成为系统性能的关键瓶颈之一。

        本文将从源码出发,完整拆解 Prometheus 3.4.0 的 HTTP 路由架构,重点分析 GET/POST 方法的前缀树实现,并探讨其高性能设计的核心逻辑。

一、分层设计的核心逻辑

Prometheus 的 HTTP 路由系统采用 "主路由 + 子路由" 的分层架构,通过职责拆分实现高内聚低耦合。这种设计既保证了静态资源、Web UI 与 API 接口的隔离,又通过中间件机制实现了功能的复用。

1.1、核心组件与依赖

Prometheus 并未使用第三方路由库,而是基于自定义的 topicroutetopic 包实现路由功能,核心依赖如下:

      • route.Router:路由核心结构体,维护路径与处理器的映射关系
      • 中间件机制:通过 WithInstrumentation 注入监控、日志、路径重写等通用逻辑
      • http.ServeMux:基础路由分发器,用于挂载主路由与 API 子路由
      • 处理器函数:每个路由对应独立的业务处理函数(如查询、联邦、版本查询等)

1.2、初始化流程 —— 从创建到挂载

路由系统的初始化在 web.New() 函数(https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go)中完成,核心步骤分为三步:

1.2.1、主路由创建(初始化带监控中间件的路由,用于处理静态资源和页面请求)

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L311

router := route.New().
    WithInstrumentation(m.instrumentHandler).  // 注入请求监控
    WithInstrumentation(setPathWithPrefix(""))  // 路径前缀处理 

1.2.2、API v1 子路由创建(独立创建 API 路由,避免与主路由逻辑混淆)

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L690

av1 := route.New().
    WithInstrumentation(h.metrics.instrumentHandlerWithPrefix("/api/v1")).  // API 专属监控
    WithInstrumentation(setPathWithPrefix(apiPath + "/v1"))  // API 路径前缀
h.apiV1.Register(av1)  // 注册 API 端点

1.2.3、路由挂载(将主路由和 API 子路由挂载到基础 ServeMux)

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L682

mux := http.NewServeMux()
mux.Handle("/", h.router)  // 主路由挂载到根路径
mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1))  // API 子路由挂载

二、按功能划分的路由注册机制

Prometheus 按 "功能模块" 划分路由,每个模块对应独立的注册逻辑。

通过源码分析,可将路由分为四大类:静态资源路由Web UI 路由API v1 路由联邦查询路由

2.1、主路由注册(静态资源和页面路由)

主路由(router)的注册逻辑全部包含在 https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go 的 New 函数中,该函数在初始化 Hander 结构体时,同步完成了处理静态资源、Web UI 入口、健康检查、生命周期等路由的注册。

核心路由及对应源代码如下:

路由路径请求方法处理器函数 / 核心逻辑功能描述源码文件及行号(v3.4.0)
topic/topic GET 重定向到首页(根据配置指向 topic/querytopic/topic/graphtopic/topic/agenttopic) 根路径统一跳转,适配新 / 旧 UI 及 Agent 模式 web/web.go L415-L417
topic/graphtopic GET 重定向到 topic/querytopic(仅非旧 UI 模式,topic!o.UseOldUItopic 为 true 时生效) 新 UI 下统一查询入口,替代旧 topic/graphtopic 页面 web/web.go L419-L422
topic/classic/static/*filepathtopic GET 静态文件服务(topicserver.StaticFileServer(ui.Assets)topic),重写路径为 topic/static/*filepathtopic 兼容旧控制台模板依赖的静态资源(如 JS/CSS) web/web.go L424-L430
topic/versiontopic GET topich.versiontopic(返回 JSON 格式的版本信息) 暴露 Prometheus 版本、分支、编译时间等元数据 web/web.go L432
topic/metricstopic GET topicpromhttp.Handler().ServeHTTPtopic 暴露 Prometheus 自身的监控指标(如请求量、耗时) web/web.go L433
topic/consoles/*filepathtopic GET topicreadyf(h.consoles)topic(需服务就绪,返回控制台模板页面) 提供自定义控制台模板的访问入口(如自定义监控面板) web/web.go L440
topic/favicon.svgtopic GET 静态文件服务(topicui.Assetstopic),路径重写为 topicreactAssetsRoot + "/favicon.svg"topic 提供浏览器标签页图标 web/web.go L474-L486(同 topic/favicon.icotopic/topic/manifest.jsontopic)
topic/favicon.icotopic GET 同上(静态文件服务,适配不同浏览器图标需求) 兼容旧浏览器的图标访问需求 web/web.go L474-L486
topic/manifest.jsontopic GET 同上(静态文件服务,返回 PWA 应用清单) 支持将 Web UI 作为渐进式 Web 应用(PWA)添加到桌面 / 手机 web/web.go L474-L486
topic/assets/*filepathtopic GET 静态文件服务(topicui.Assetstopic),路径重写为 topicreactAssetsRoot + "/assets/*filepath"topic 加载新 UI(Mantine 框架)的静态资源(如组件样式、图片) web/web.go L488-L494(非旧 UI 模式)
topic/static/*filepathtopic GET 静态文件服务(topicui.Assetstopic),路径重写为 topicreactAssetsRoot + "/static/*filepath"topic 加载旧 UI(React 旧框架)的静态资源(仅 topico.UseOldUItopic 为 true 时生效) web/web.go L488-L494
topic/user/*filepathtopic GET topicroute.FileServe(o.UserAssetsPath)topic(用户自定义静态资源服务) 提供用户本地自定义静态资源的访问入口(如自定义图片、模板) web/web.go L496-L498
topic/-/quittopic POST/PUT topich.quittopic(生命周期开启时)/ 403 响应(生命周期关闭时) 触发 Prometheus 服务优雅退出 web/web.go L500-L514
topic/-/quittopic GET 返回 405 响应(提示 “仅支持 POST/PUT 方法”) 限制非法请求方法,保证接口安全性 web/web.go L516-L519
topic/-/reloadtopic POST/PUT topich.reloadtopic(生命周期开启时)/ 403 响应(生命周期关闭时) 触发 Prometheus 配置重载(如刷新采集规则、告警规则) web/web.go L500-L514
topic/-/reloadtopic GET 返回 405 响应(提示 “仅支持 POST/PUT 方法”) 限制非法请求方法,保证接口安全性 web/web.go L521-L523
topic/debug/*subpathtopic GET/POST topicserveDebugtopic(内部转发到 pprof 调试接口) 提供 Go 程序标准调试接口(如 topic/debug/pprof/profiletopic 采集性能样本) web/web.go L524-L525
topic/-/healthytopic GET 返回 200 状态码 + “XXX is Healthy” 文本 健康检查接口(判断服务是否启动,不依赖业务就绪) web/web.go L548-L551
topic/-/healthytopic HEAD 返回 200 状态码(无响应体) 轻量健康检查,适配监控系统 HEAD 请求优化 web/web.go L552-L555
topic/-/readytopic GET topicreadyf(...)topic(服务就绪时返回 200,否则返回 503) 就绪检查接口(判断服务是否完成初始化,可处理业务请求) web/web.go L557-L560
topic/-/readytopic HEAD topicreadyf(...)topic(同上,无响应体) 轻量就绪检查,适配监控系统 HEAD 请求优化 web/web.go L561-L564

以下是对该 Newtopic 函数(Prometheus v3.4.0 web 模块路由初始化核心逻辑)的逐行注释,重点标注路由注册、配置依赖、处理器绑定等关键逻辑:

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L305

// New 初始化一个新的 web Handler(核心功能:初始化 Handler 结构体 + 注册所有 web 路由)
func New(logger *slog.Logger, o *Options) *Handler {
	// 日志器容错:若传入 logger 为 nil,使用空日志器(避免空指针)
	if logger == nil {
		logger = promslog.NewNopLogger()
	}

	// 初始化监控指标:基于传入的注册器(Registerer)创建 metrics 实例,用于路由请求的指标统计
	m := newMetrics(o.Registerer)
	// 初始化路由管理器:
	// 1. 绑定指标监控中间件(m.instrumentHandler,统计路由请求耗时、成功/失败数等)
	// 2. 绑定路径前缀设置中间件(setPathWithPrefix(""),初始化路径前缀为空)
	router := route.New().
		WithInstrumentation(m.instrumentHandler).
		WithInstrumentation(setPathWithPrefix(""))

	// 获取当前工作目录(CWD):用于后续记录服务运行路径,失败时记录错误信息
	cwd, err := os.Getwd()
	if err != nil {
		cwd = "<error retrieving current working directory>"
	}

	// 初始化 Handler 核心结构体:聚合所有依赖(日志、监控、路由、配置、存储等)
	h := &Handler{
		logger: logger,                  // 日志器
		gatherer: o.Gatherer,            // 指标采集器(用于暴露 /metrics)
		metrics:  m,                     // 自定义监控指标实例
		router:      router,             // 路由管理器
		quitCh:      make(chan struct{}),// 服务退出信号通道
		reloadCh:    make(chan chan error),// 配置重载信号通道(带错误返回)
		options:     o,                  // 传入的 web 配置选项
		versionInfo: o.Version,          // 服务版本信息
		birth:       time.Now().UTC(),   // 服务启动时间(UTC 时区)
		cwd:         cwd,                // 服务运行目录
		flagsMap:    o.Flags,            // 命令行参数映射

		// 核心业务依赖注入(Prometheus 核心功能模块)
		context:         o.Context,         // 根上下文(用于服务生命周期管理)
		scrapeManager:   o.ScrapeManager,   // 采集管理器(管理目标采集任务)
		ruleManager:     o.RuleManager,     // 规则管理器(管理告警/记录规则)
		queryEngine:     o.QueryEngine,     // 查询引擎(处理 PromQL 查询)
		lookbackDelta:   o.LookbackDelta,   // 查询回溯时间窗口
		storage:         o.Storage,         // 主存储(长期指标存储)
		localStorage:    o.LocalStorage,    // 本地存储(短期/临时指标)
		exemplarStorage: o.ExemplarStorage, // 样本存储(关联指标的原始样本)
		notifier:        o.Notifier,        // 告警通知器(发送告警到 Alertmanager)

		now: model.Now, // 时间获取函数(默认当前时间,便于测试 mock)
	}
	// 设置服务初始状态为「未就绪」(NotReady),就绪后通过其他逻辑更新
	h.SetReady(NotReady)

	// 初始化 API v1 所需的「资源检索器工厂函数」:
	// 作用是为 API 提供统一的资源访问入口,解耦 API 与具体模块的依赖
	factorySPr := func(_ context.Context) api_v1.ScrapePoolsRetriever { return h.scrapeManager } // 采集池检索器(API 查采集池)
	factoryTr := func(_ context.Context) api_v1.TargetRetriever { return h.scrapeManager }     // 采集目标检索器(API 查采集目标)
	factoryAr := func(_ context.Context) api_v1.AlertmanagerRetriever { return h.notifier }    // Alertmanager 检索器(API 查告警接收器)
	FactoryRr := func(_ context.Context) api_v1.RulesRetriever { return h.ruleManager }        // 规则检索器(API 查告警/记录规则)

	// 初始化 Appendable 存储:仅当启用远程写入接收(RemoteWriteReceiver)或 OTLP 写入接收时,使用主存储
	var app storage.Appendable
	if o.EnableRemoteWriteReceiver || o.EnableOTLPWriteReceiver {
		app = h.storage
	}

	// 初始化 API v1 实例:绑定所有核心依赖,为后续路由提供 API 能力
	h.apiV1 = api_v1.NewAPI(
		h.queryEngine,          // 查询引擎
		h.storage,              // 主存储
		app,                    // 可追加存储(远程写入用)
		h.exemplarStorage,      // 样本存储
		factorySPr,             // 采集池检索器工厂
		factoryTr,              // 采集目标检索器工厂
		factoryAr,              // Alertmanager 检索器工厂
		// 配置获取函数(带读锁,保证配置读取线程安全)
		func() config.Config {
			h.mtx.RLock()         // 加读锁(防止配置更新时并发读取)
			defer h.mtx.RUnlock() // 释放读锁
			return *h.config
		},
		o.Flags,                // 命令行参数
		// API 全局 URL 配置(用于生成正确的外部链接)
		api_v1.GlobalURLOptions{
			ListenAddress: o.ListenAddresses[0], // 监听地址(第一个)
			Host:          o.ExternalURL.Host,   // 外部访问 Host
			Scheme:        o.ExternalURL.Scheme, // 外部访问协议(http/https)
		},
		h.testReady,            // 就绪状态检查函数(API 就绪性校验)
		h.options.LocalStorage, // 本地存储
		h.options.TSDBDir,      // TSDB 存储目录
		h.options.EnableAdminAPI, // 是否启用 Admin API(如删除指标)
		logger,                 // 日志器
		FactoryRr,              // 规则检索器工厂
		h.options.RemoteReadSampleLimit,    // 远程读取样本数限制
		h.options.RemoteReadConcurrencyLimit, // 远程读取并发数限制
		h.options.RemoteReadBytesInFrame,   // 远程读取单帧字节数限制
		h.options.IsAgent,       // 是否为 Agent 模式(区别于 Server 模式)
		h.options.CORSOrigin,    // CORS 跨域配置
		h.runtimeInfo,           // 运行时信息(如内存、CPU 占用)
		h.versionInfo,           // 版本信息
		h.options.NotificationsGetter, // 告警通知查询器(API 查告警历史)
		h.options.NotificationsSub,    // 告警通知订阅器(API 订阅告警)
		o.Gatherer,              // 指标采集器
		o.Registerer,            // 指标注册器
		nil,                     // 预留参数(暂无使用)
		o.EnableRemoteWriteReceiver, // 是否启用远程写入接收
		o.AcceptRemoteWriteProtoMsgs, // 是否接受 Protobuf 格式的远程写入
		o.EnableOTLPWriteReceiver,    // 是否启用 OTLP 写入接收
		o.ConvertOTLPDelta,           // 是否将 OTLP Delta 指标转为 Cumulative
		o.NativeOTLPDeltaIngestion,   // 是否原生支持 OTLP Delta 指标摄入
		o.CTZeroIngestionEnabled,     // 是否启用 CT Zero 指标摄入
	)

	// 路由前缀配置:若配置了非根路径前缀(RoutePrefix != "/")
	if o.RoutePrefix != "/" {
		// 根路径重定向:访问 "/" 时,重定向到配置的前缀路径(避免 404)
		router.Get("/", func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, o.RoutePrefix, http.StatusFound)
		})
		// 为所有后续注册的路由添加前缀(批量生效)
		router = router.WithPrefix(o.RoutePrefix)
	}

	// 首页路径配置:根据模式选择默认首页
	homePage := "/query"          // 默认首页:新 UI 的查询页面
	if o.UseOldUI {
		homePage = "/graph"       // 旧 UI 模式:首页为 /graph
	}
	if o.IsAgent {
		homePage = "/agent"       // Agent 模式:首页为 /agent
	}

	// 就绪状态检查函数:复用 Handler 的 testReady 方法(判断服务是否就绪)
	readyf := h.testReady

	// 根路径重定向:访问路由前缀根路径(如 /prometheus/)时,重定向到配置的首页
	router.Get("/", func(w http.ResponseWriter, r *http.Request) {
		// 拼接外部 URL 路径 + 首页路径(保证重定向地址正确)
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, homePage), http.StatusFound)
	})

	// 旧 UI 兼容:非旧 UI 模式下,访问 /graph 重定向到 /query(统一新 UI 入口)
	if !o.UseOldUI {
		router.Get("/graph", func(w http.ResponseWriter, r *http.Request) {
			// 保留原查询参数(RawQuery),避免参数丢失
			http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/query?"+r.URL.RawQuery), http.StatusFound)
		})
	}

	// React 前端资源根路径配置:根据 UI 模式选择资源目录
	reactAssetsRoot := "/static/mantine-ui" // 新 UI:Mantine 组件库资源目录
	if h.options.UseOldUI {
		reactAssetsRoot = "/static/react-app" // 旧 UI:旧 React 应用资源目录
	}

	// 旧控制台兼容路由:为 /classic/static/*filepath 提供静态资源服务
	// 用途:旧控制台库(如 console_libraries/prom.lib)依赖该路径下的资源
	router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
		// 路径重写:将 /classic/static/xxx 转为 /static/xxx(匹配 ui.Assets 中的资源路径)
		r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
		// 使用静态文件服务:从 ui.Assets(嵌入式资源)中读取文件
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})

	// 版本信息路由:GET /version,调用 h.version 处理器返回版本信息
	router.Get("/version", h.version)
	// 自身监控指标路由:GET /metrics,使用 promhttp 提供标准 Prometheus 指标暴露
	router.Get("/metrics", promhttp.Handler().ServeHTTP)

	// 联邦查询路由:GET /federate
	// 1. 就绪校验:通过 readyf 中间件,未就绪时返回 503
	// 2. 压缩处理:使用 httputil.CompressionHandler 压缩响应(减少网络传输)
	// 3. 处理器:调用 h.federation 处理联邦查询请求
	router.Get("/federate", readyf(httputil.CompressionHandler{
		Handler: http.HandlerFunc(h.federation),
	}.ServeHTTP))

	// 控制台页面路由:GET /consoles/*filepath
	// 1. 就绪校验:未就绪时拒绝访问
	// 2. 处理器:调用 h.consoles 加载控制台页面资源(支持自定义控制台)
	router.Get("/consoles/*filepath", readyf(h.consoles))

	// React 应用服务函数:动态生成 React 首页 HTML(替换占位符为实际配置)
	serveReactApp := func(w http.ResponseWriter, _ *http.Request) {
		// 拼接 React 首页 HTML 路径(从嵌入式资源中读取)
		indexPath := reactAssetsRoot + "/index.html"
		f, err := ui.Assets.Open(indexPath)
		if err != nil {
			// 资源读取失败:返回 500 并提示错误
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error opening React index.html: %v", err)
			return
		}
		// 延迟关闭文件句柄(避免资源泄漏)
		defer func() { _ = f.Close() }()
		// 读取 HTML 文件内容
		idx, err := io.ReadAll(f)
		if err != nil {
			// 内容读取失败:返回 500 并提示错误
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error reading React index.html: %v", err)
			return
		}
		// 替换 HTML 中的占位符为实际配置(让前端获取后端配置)
		replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath())) // 控制台链接
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle)) // 页面标题
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("AGENT_MODE_PLACEHOLDER"), []byte(strconv.FormatBool(h.options.IsAgent))) // 是否为 Agent 模式
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("READY_PLACEHOLDER"), []byte(strconv.FormatBool(h.isReady()))) // 服务就绪状态
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("LOOKBACKDELTA_PLACEHOLDER"), []byte(model.Duration(h.options.LookbackDelta).String())) // 查询回溯窗口
		// 返回替换后的 HTML 内容(前端渲染入口)
		w.Write(replacedIdx)
	}

	// React 路由路径配置:根据 UI 模式选择前端路由列表
	reactRouterPaths := newUIReactRouterPaths       // 新 UI 前端路由(如 /query、/alerts)
	reactRouterServerPaths := newUIReactRouterServerPaths // 新 UI Server 模式路由
	if h.options.UseOldUI {
		reactRouterPaths = oldUIReactRouterPaths       // 旧 UI 前端路由
		reactRouterServerPaths = oldUIReactRouterServerPaths // 旧 UI Server 模式路由
	}

	// 注册 React 前端路由:为所有前端路由绑定 serveReactApp 处理器(前端路由复用首页 HTML)
	for _, p := range reactRouterPaths {
		router.Get(p, serveReactApp)
	}

	//  Agent 模式额外路由:注册 Agent 模式专属的前端路由
	if h.options.IsAgent {
		for _, p := range reactRouterAgentPaths {
			router.Get(p, serveReactApp)
		}
	} else {
		//  Server 模式额外路由:注册 Server 模式专属的前端路由
		for _, p := range reactRouterServerPaths {
			router.Get(p, serveReactApp)
		}
	}

	// 根路径静态资源路由:为浏览器图标、应用清单提供服务(需根路径访问,如 /favicon.ico)
	for _, p := range []string{"/favicon.svg", "/favicon.ico", "/manifest.json"} {
		// 拼接资源在嵌入式资产中的实际路径
		assetPath := reactAssetsRoot + p
		router.Get(p, func(w http.ResponseWriter, r *http.Request) {
			// 路径重写:将根路径资源请求转为嵌入式资产中的路径
			r.URL.Path = assetPath
			fs := server.StaticFileServer(ui.Assets)
			fs.ServeHTTP(w, r)
		})
	}

	// React 静态资源目录配置:根据 UI 模式选择资源目录
	reactStaticAssetsDir := "/assets"  // 新 UI:静态资源目录(如 /assets/css、/assets/js)
	if h.options.UseOldUI {
		reactStaticAssetsDir = "/static" // 旧 UI:静态资源目录(如 /static/css)
	}
	// React 静态资源路由:为 /assets/* 或 /static/* 提供静态资源服务
	router.Get(reactStaticAssetsDir+"/*filepath", func(w http.ResponseWriter, r *http.Request) {
		// 路径重写:拼接资产根路径 + 静态资源目录 + 文件路径(匹配嵌入式资源)
		r.URL.Path = path.Join(reactAssetsRoot+reactStaticAssetsDir, route.Param(r.Context(), "filepath"))
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})

	// 用户自定义静态资源路由:若配置了 UserAssetsPath(用户本地资源目录)
	if o.UserAssetsPath != "" {
		// 注册 /user/*filepath 路由,从用户指定目录加载静态资源
		router.Get("/user/*filepath", route.FileServe(o.UserAssetsPath))
	}

	// 生命周期 API 配置:若启用生命周期管理(EnableLifecycle = true)
	if o.EnableLifecycle {
		// 服务退出路由:POST/PUT /-/quit,调用 h.quit 触发服务退出
		router.Post("/-/quit", h.quit)
		router.Put("/-/quit", h.quit)
		// 配置重载路由:POST/PUT /-/reload,调用 h.reload 触发配置重载
		router.Post("/-/reload", h.reload

2.2、API v1 路由注册(监控核心接口)

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L359

API v1 路由不包含在主路由中,而是通过独立的 topicapi_v1.APItopictopic(https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L62topictopic)topic 结构体注册,最终挂载到主 topicServeMuxtopic 上。整个流程分为两步:

2.2.1、初始化 API v1 实例

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L359

在 New 函数中,通过 api_v1.NewAPI 初始化 API 处理实例,传入查询引擎、存储、配置等核心依赖,为路由注册做准备。

h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, app, h.exemplarStorage, factorySPr, factoryTr, factoryAr,
    func() config.Config { /* 配置获取逻辑 */ },
    o.Flags,
    api_v1.GlobalURLOptions{ /* URL 配置 */ },
    // 其他依赖参数...
)

2.2.2、注册并挂载 API v1 路由

https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L682-695

  • 第 690-692 行:创建 API v1 专属路由 av1,并添加监控中间件
  • 第 693 行:调用 h.apiV1.Register(av1) 注册所有 API 端点
  • 第 695 行:通过 http.StripPrefix 将 API v1 路由挂载到主 ServeMux
  • 第 374-389 行:在 web/api/v1/api.go 中注册核心监控接口,包括 /query、/query_range、/labels 等

 在 Handler.Run 函数中,创建 API v1 专属路由(av1 https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L690),调用 h.apiV1.Register(av1) 注册所有 API 端点(https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#L374-389),最后通过 http.StripPrefix 挂载到主 ServeMux(https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L690)

# https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L690
mux := http.NewServeMux() mux.Handle("/", h.router) apiPath := "/api" if h.options.RoutePrefix != "/" { apiPath = h.options.RoutePrefix + apiPath h.logger.Info("Router prefix", "prefix", h.options.RoutePrefix) } av1 := route.New(). WithInstrumentation(h.metrics.instrumentHandlerWithPrefix("/api/v1")). WithInstrumentation(setPathWithPrefix(apiPath + "/v1")) h.apiV1.Register(av1) mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1))
        • 注册核心端点:包含 /query//query_range//labels 等监控接口,逻辑在 https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go 的 Register 函数(https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go#L339)中(如 L371-L388 注册 /query/,/query_range)。
        • 挂载代码:
          // 源码位置:web/web.go L611-L620
          av1 := route.New().
              WithInstrumentation(h.metrics.instrumentHandlerWithPrefix("/api/v1")).
              WithInstrumentation(setPathWithPrefix(apiPath + "/v1"))
          h.apiV1.Register(av1)
          mux.Handle(apiPath+"/v1/", http.StripPrefix(apiPath+"/v1", av1))

2.3、联邦查询路由(跨实例数据聚合)

联邦查询路由属于主路由的一部分,并非独立模块,其注册和处理逻辑如下:

  • 路由注册:在 New 函数中,通过 router.Get("/federate", ...) 注册,处理器为 h.federation(https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go#L440-442)。
  • 核心逻辑:h.federation 函数(定义在 https://github.com/prometheus/prometheus/blob/v3.4.0/web/federate.go L51-L106)负责解析 match[] 参数、查询本地存储数据,并通过 expfmt 格式返回聚合结果,支持跨 Prometheus 实例的数据拉取。

三、路由注册与源码映射表

        该表基于 https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go  https://github.com/prometheus/prometheus/blob/v3.4.0/web/api/v1/api.go 核心源码,按 "主路由(静态 / 基础功能)" "API v1 路由(监控核心)" "联邦查询路由" 三大类划分,明确每个路由的路径、方法、处理器及精确源码位置,确保与实际代码完全对齐。

3.1、主路由注册,请参考本文 `主路由注册(静态资源和页面路由)`

3.2、API v1 路由:监控核心功能路由

API v1 路由通过 api_v1.API 结构体独立注册,在 web/handler.go 的 Run 函数中挂载到主 ServeMux,负责处理所有监控核心操作(如查询、标签管理、元数据获取)。

路由路径请求方法处理器函数(topicapi_v1.APItopic 内方法)功能描述源码文件及行号(v3.4.0)
topic/querytopic GET/POST topicapi.querytopic 即时查询(获取单个时间点的监控指标数据,支持 PromQL) web/api/v1/api.go L374-L375
topic/query_rangetopic GET/POST topicapi.queryRangetopic 范围查询(获取指定时间段内的监控指标数据,支持 PromQL) web/api/v1/api.go L376-L377
topic/query_exemplarstopic GET/POST topicapi.queryExemplarstopic 样本查询(获取监控指标对应的 exemplar 数据,用于追踪链路关联) web/api/v1/api.go L378-L379
topic/format_querytopic GET/POST topicapi.formatQuerytopic 查询语句格式化(美化 PromQL 语句,便于阅读和调试) web/api/v1/api.go L381-L382
topic/parse_querytopic GET/POST topicapi.parseQuerytopic 查询语句解析(校验 PromQL 语法正确性,返回解析结果) web/api/v1/api.go L383-L384
topic/labelstopic GET/POST topicapi.labelNamestopic 标签名查询(获取所有监控指标的标签键(label key)列表) web/api/v1/api.go L386-L387
topic/label/:name/valuestopic GET/POST topicapi.labelValuestopic 标签值查询(获取指定标签键对应的所有标签值(label value)列表) web/api/v1/api.go L388
topic/seriestopic GET/POST topicapi.seriestopic 时间序列查询(根据标签筛选条件,获取匹配的时间序列列表) web/api/v1/api.go L390-L391(源码中紧随 topic/label/:name/valuestopic 注册)
topic/metadatatopic GET/POST topicapi.metadatatopic 指标元数据查询(获取监控指标的类型、帮助信息、标签等元数据) web/api/v1/api.go L393-L394

3.2、联邦查询路由——跨实例数据聚合路由

联邦查询路由属于主路由的子集,专门用于跨 Prometheus 实例聚合数据,注册和处理逻辑独立于普通 API。

路由路径请求方法处理器函数 / 核心逻辑功能描述源码文件及行号(v3.4.0)
topic/federatetopic GET topicreadyf(httputil.CompressionHandler{ Handler: http.HandlerFunc(h.federation) })topic 联邦查询入口(接收 topicmatch[]topic 参数,返回符合条件的监控指标,支持跨实例聚合) 注册逻辑:web/web.go L435-L438;处理逻辑:web/federate.go L51-L106

四、Prometheus 3.4.0 二次开发时路由调试

在实际部署或二次开发中,路由匹配异常(如请求 404、接口无响应)是常见问题。本文从“日志定位”、“有效性验证”、“问题排查”三个核心场景出发,提供可直接操作的步骤,帮助快速定位并解决路由相关问题。

4.1、如何通过日志定位路由匹配过程 

Prometheus 默认不输出路由匹配细节,需通过 调整日志级别 和 启用请求追踪日志,完整记录请求从接收、路由匹配到处理器调用的全流程。

路由匹配的核心日志(如路径解析、中间件执行)属于 topicdebugtopic 级别,需通过启动参数或配置文件开启(推荐,临时生效)

./prometheus --log.level=debug --config.file=prometheus.yml
      • --log.level=debug:将全局日志级别设为 debug,路由相关日志(如 route 包的匹配日志)会被输出。

通过配置文件开启(长期生效)

# 在 prometheus.yml 中添加日志配置(需 Prometheus 3.0+ 支持):

global:
  scrape_interval: 15s
log:
  level: debug  # 全局日志级别
  format: logfmt  # 日志格式,logfmt 或 json(json 便于机器解析)  

开启 topicdebugtopic 日志后,可通过关键字过滤路由相关日志,核心日志类型及含义如下:

日志关键字日志含义示例日志(简化)
topicroute matchedtopic 路由匹配成功,包含请求方法、路径、匹配到的处理器名称 topiclevel=debug ts=2024-05-20T10:30:00Z caller=route/route.go:123 msg="route matched" method=GET path=/api/v1/query handler=api_v1.querytopic
topicroute not foundtopic 路由匹配失败,请求路径或方法不存在 topiclevel=debug ts=2024-05-20T10:31:00Z caller=route/route.go:135 msg="route not found" method=POST path=/api/v1/unknowntopic
topicinstrumentHandlertopic 中间件执行日志,包含请求耗时、处理器名称(监控中间件输出) topiclevel=debug ts=2024-05-20T10:32:00Z caller=web/metrics.go:45 msg="instrumentHandler" handler=api_v1.query duration=120mstopic
topichttp server: servingtopic HTTP 服务器接收请求的初始日志,包含客户端 IP、请求方法、路径 topiclevel=debug ts=2024-05-20T10:29:00Z caller=web/handler.go:680 msg="http server: serving" client=192.168.1.100:54321 method=GET path=/metricstopic

4.2、启用请求追踪,关联全链路日志

若需追踪单个请求的完整链路(从接收→路由匹配→中间件→处理器→响应),可通过 HTTP 请求头传递追踪 ID,并在日志中关联该 ID:

4.2.1、客户端发送请求时添加追踪头(如 topicX-Trace-IDtopic)

curl -H "X-Trace-ID: abc123" http://localhost:9090/api/v1/query?query=up

4.2.2、修改 Prometheus 日志配置(需二次开发或使用自定义中间件)

在 withStackTracer 中间件(https://github.com/prometheus/prometheus/blob/v3.4.0/web/web.go L178-L195)中添加追踪头日志,示例修改:

func withStackTracer(h http.Handler, l *slog.Logger) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 提取追踪头
        traceID := r.Header.Get("X-Trace-ID")
        if traceID == "" {
            traceID = uuid.NewString() // 无追踪头时生成随机 ID
        }
        // 日志中添加 traceID 字段
        l = l.With("traceID", traceID)
        defer func() {
            if err := recover(); err != nil {
                // 异常日志关联 traceID
                l.Error("panic while serving request", "client", r.RemoteAddr, "url", r.URL, "err", err, "stack", buf)
            }
        }()
        // 请求处理日志关联 traceID
        l.Debug("serving request", "method", r.Method, "path", r.URL.Path, "traceID", traceID)
        h.ServeHTTP(w, r)
    })
}
posted @ 2025-10-24 14:42  左扬  阅读(2)  评论(0)    收藏  举报