【Blackbox Exporter】prober.Handler源码详细分析

devtools/2025/1/8 2:33:30/
	http.HandleFunc(path.Join(*routePrefix, "/probe"), func(w http.ResponseWriter, r *http.Request) {sc.Lock()conf := sc.Csc.Unlock()prober.Handler(w, r, conf, logger, rh, *timeoutOffset, nil, moduleUnknownCounter, allowedLevel)})

在这里插入图片描述

我们了解到blackbox_exporter中都是通过请求/probe来进行端口探测的,那么今天我们来详尽的分析prober.Handler相关源码。

目录

      • 函数签名
      • 1. 获取 URL 查询参数 `params`
      • 2. 获取探针模块名称
      • 3. 解析超时设置
      • 4. 创建上下文(Context)
      • 5. 创建 Prometheus 指标
      • 6. 获取目标(target)
      • 7. 获取探针类型和处理
      • 8. 设置 `hostname`(针对 HTTP 或 TCP 探测)
      • 9. 设置日志级别
      • 10. 开始探测
      • 11. 记录结果
      • 12. 返回调试输出(如果启用)
      • 13. 返回 Prometheus 格式的指标
      • 总结

函数签名

func Handler(w http.ResponseWriter, r *http.Request, c *config.Config, logger *slog.Logger, rh *ResultHistory, timeoutOffset float64, params url.Values, moduleUnknownCounter prometheus.Counter, logLevelProber *promslog.AllowedLevel)
  • w http.ResponseWriter:HTTP 响应对象,用于向客户端发送响应。
  • r *http.Request:HTTP 请求对象,包含请求信息。
  • c *config.Config:包含配置的对象,包含了可用的探针模块等配置。
  • logger *slog.Logger:日志记录器,用于输出日志。
  • rh *ResultHistory:用于记录结果历史的对象。
  • timeoutOffset float64:超时偏移量,可能用于调整默认的超时设置。
  • params url.Values:URL 查询参数,通常包含了目标和其他探测信息。
  • moduleUnknownCounter prometheus.Counter:Prometheus 计数器,用于统计未知模块的次数。
  • logLevelProber *promslog.AllowedLevel:日志级别,控制探测日志的详细程度。

1. 获取 URL 查询参数 params

if params == nil {params = r.URL.Query()
}
  • 如果 params 参数为空(即传入的 URL 查询参数为空),则使用 HTTP 请求的查询参数 r.URL.Query()

2. 获取探针模块名称

moduleName := params.Get("module")
if moduleName == "" {moduleName = "http_2xx"
}
module, ok := c.Modules[moduleName]
if !ok {http.Error(w, fmt.Sprintf("Unknown module %q", moduleName), http.StatusBadRequest)logger.Debug("Unknown module", "module", moduleName)if moduleUnknownCounter != nil {moduleUnknownCounter.Add(1)}return
}
  • 获取 URL 查询参数中的 module 参数。如果没有传递 module 参数,默认设置为 http_2xx
  • 通过 moduleName 从配置 c.Modules 中获取对应的模块配置。如果模块不存在,返回 HTTP 错误 400 BadRequest

3. 解析超时设置

timeoutSeconds, err := getTimeout(r, module, timeoutOffset)
if err != nil {http.Error(w, fmt.Sprintf("Failed to parse timeout from Prometheus header: %s", err), http.StatusInternalServerError)return
}
  • 调用 getTimeout 函数从请求头或模块配置中解析超时设置,超时偏移量会影响最终的超时值。
  • 如果解析超时出错,则返回 500 InternalServerError

4. 创建上下文(Context)

ctx, cancel := context.WithTimeout(r.Context(), time.Duration(timeoutSeconds*float64(time.Second)))
defer cancel()
r = r.WithContext(ctx)
  • 使用 context.WithTimeout 创建一个带有超时设置的上下文 ctx,并将其与请求 r 关联。超时会在 timeoutSeconds 秒后触发。

5. 创建 Prometheus 指标

probeSuccessGauge := prometheus.NewGauge(prometheus.GaugeOpts{Name: "probe_success",Help: "Displays whether or not the probe was a success",
})
probeDurationGauge := prometheus.NewGauge(prometheus.GaugeOpts{Name: "probe_duration_seconds",Help: "Returns how long the probe took to complete in seconds",
})
  • 创建两个 Prometheus Gauge 类型的指标:
    • probe_success: 表示探测是否成功(1:成功,0:失败)。
    • probe_duration_seconds: 表示探测完成的时长(单位:秒)。

6. 获取目标(target)

target := params.Get("target")
if target == "" {http.Error(w, "Target parameter is missing", http.StatusBadRequest)return
}
  • 获取 URL 查询参数中的 target 参数,表示需要探测的目标地址。如果没有提供目标地址,则返回 400 BadRequest 错误。

7. 获取探针类型和处理

prober, ok := Probers[module.Prober]
if !ok {http.Error(w, fmt.Sprintf("Unknown prober %q", module.Prober), http.StatusBadRequest)return
}
  • 根据 module.Prober 获取对应的探针。如果探针类型不存在,则返回 400 BadRequest 错误。

8. 设置 hostname(针对 HTTP 或 TCP 探测)

hostname := params.Get("hostname")
if module.Prober == "http" && hostname != "" {err = setHTTPHost(hostname, &module)if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}
}if module.Prober == "tcp" && hostname != "" {if module.TCP.TLSConfig.ServerName == "" {module.TCP.TLSConfig.ServerName = hostname}
}
  • 如果是 http 探针并且 hostname 不为空,则调用 setHTTPHost 设置 HTTP 请求的 Host 头。
  • 如果是 tcp 探针并且 hostname 不为空,则设置 TLS 配置中的 ServerName

9. 设置日志级别

if logLevelProber == nil {logLevelProber = &promslog.AllowedLevel{}
}
if logLevelProber.String() == "" {_ = logLevelProber.Set("info")
}
sl := newScrapeLogger(logger, moduleName, target, logLevelProber)
slLogger := slog.New(sl)
  • 如果没有提供 logLevelProber,则使用默认的日志级别 “info”。
  • 创建一个新的日志记录器 slLogger,用于记录探测过程中的信息。

10. 开始探测

slLogger.Info("Beginning probe", "probe", module.Prober, "timeout_seconds", timeoutSeconds)start := time.Now()
registry := prometheus.NewRegistry()
registry.MustRegister(probeSuccessGauge)
registry.MustRegister(probeDurationGauge)
success := prober(ctx, target, module, registry, slLogger)
duration := time.Since(start).Seconds()
probeDurationGauge.Set(duration)
if success {probeSuccessGauge.Set(1)slLogger.Info("Probe succeeded", "duration_seconds", duration)
} else {slLogger.Error("Probe failed", "duration_seconds", duration)
}
  • 记录日志开始探测。
  • 使用 prober(即相应的探针函数)开始实际的探测操作,并记录探测的持续时间。
  • 根据探测结果,设置 probe_successprobe_duration_seconds 指标。

11. 记录结果

debugOutput := DebugOutput(&module, &sl.buffer, registry)
rh.Add(moduleName, target, debugOutput, success)
  • 调用 DebugOutput 函数生成调试输出,并将结果添加到 ResultHistory(用于记录历史结果)。

12. 返回调试输出(如果启用)

if r.URL.Query().Get("debug") == "true" {w.Header().Set("Content-Type", "text/plain")w.Write([]byte(debugOutput))return
}
  • 如果查询参数 debug=true,则返回调试输出。

13. 返回 Prometheus 格式的指标

h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
  • 创建 Prometheus 格式的 HTTP 处理程序,并返回探测结果的指标数据。

总结

它通过解析请求参数来执行指定类型的探测(如 HTTP、TCP 探测),并生成相应的 Prometheus 指标。返回的指标可以被 Prometheus 服务器抓取并进行监控。此外,代码还处理了探测过程中的日志记录和调试输出。
prober(ctx, target, module, registry, slLogger)是整个执行探测的核心部分,下一篇将重点分析此函数


http://www.ppmy.cn/devtools/148789.html

相关文章

在调用 borrowObject 方法时,Apache Commons Pool 会根据连接池的配置触发一系列相关的方法

在调用 borrowObject 方法时,Apache Commons Pool 会根据连接池的配置触发一系列相关的方法 1. GrpcChannel 的概念 GrpcChannel 是 gRPC 客户端与服务器之间通信的核心组件。它是基于 HTTP/2 的连接,支持多路复用,即通过单个通道可以发送多…

Leetcode 3409. Longest Subsequence With Decreasing Adjacent Difference

Leetcode 3409. Longest Subsequence With Decreasing Adjacent Difference 1. 解题思路2. 代码实现 题目链接:3409. Longest Subsequence With Decreasing Adjacent Difference 1. 解题思路 这一题做的很失败,虽然只是一个medium的题目,但…

掌控ctf-2月赛

没事干 随便刷刷题 1伪协议读取系统进程 源码 <?php highlight_file(__FILE__); require_once flag.php; if(isset($_GET[file])) {require_once $_GET[file]; } 伪协议读取flag.php&#xff0c;/proc/self指向当前进程的 exp ?filephp://filter/readconvert.base64…

初始值变量类型

状态名同步位置初始值变量类型不支持的UL刷新注意事项State父组件必填Object、classstring、number、boolean、enum类型&#xff0c;以及这些类型的数组。支持Date类型。对象的对象数组属性更新数组对象的属性更新 State装饰的变量必须初始化&#xff0c;否则编译期会报错。Sta…

PADS Layout 如何快速高效的学习,学习的重点,难点,目标

以最短的时间去操作PADS的流程,走线需要注意哪些规则? 布局需要注意哪些规范和规则要求? 开始布第一块的高速板子,肯定是有大把的问题的,这些问题就是在严格的行业规则和规范下面是不符合要求的。 我们一定要把PADS Layout的速度练习起来,到后面的话,就会越来越快,对…

3.1 vue基础1

template 和 render()函数 buildTemplate > render() js <template> <div class"data1">{{data1}}</div> </template> // 更改 render() { return ( <div class"data1">{ data1 }</div> ) } // 优化能力 - domdi…

Django AWS负载均衡器管理系统

在本文中,我们将详细介绍如何构建一个Django应用来管理和同步AWS负载均衡器信息。这个系统将允许我们从AWS获取负载均衡器的数据,并在本地数据库中进行存储和管理。 项目概述 我们将创建一个名为aws_ops的Django项目,其中包含一个名为loadbalancer的应用。这个应用将负责处…

Selenium 八大元素定位方法及场景扩展

Selenium 提供了八种常见的元素定位方法&#xff0c;用于在网页中准确找到需要操作的元素。以下是详细介绍以及一些特殊场景的解决方案。 1. ID 定位 用法&#xff1a; 通过元素的唯一 id 属性进行定位。 element driver.find_element(By.ID, "element_id")使用场…