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

go 复制代码
	http.HandleFunc(path.Join(*routePrefix, "/probe"), func(w http.ResponseWriter, r *http.Request) {
		sc.Lock()
		conf := sc.C
		sc.Unlock()
		prober.Handler(w, r, conf, logger, rh, *timeoutOffset, nil, moduleUnknownCounter, allowedLevel)
	})

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

目录

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

函数签名

go 复制代码
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

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

2. 获取探针模块名称

go 复制代码
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. 解析超时设置

go 复制代码
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)

go 复制代码
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 指标

go 复制代码
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)

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

7. 获取探针类型和处理

go 复制代码
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 探测)

go 复制代码
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. 设置日志级别

go 复制代码
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. 开始探测

go 复制代码
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. 记录结果

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

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

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

13. 返回 Prometheus 格式的指标

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

总结

它通过解析请求参数来执行指定类型的探测(如 HTTP、TCP 探测),并生成相应的 Prometheus 指标。返回的指标可以被 Prometheus 服务器抓取并进行监控。此外,代码还处理了探测过程中的日志记录和调试输出。

prober(ctx, target, module, registry, slLogger)是整个执行探测的核心部分,下一篇将重点分析此函数

相关推荐
Aress"21 分钟前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy10 小时前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克10 小时前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨11 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆12 小时前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂1 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T2 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20252 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz2 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频
安和昂2 天前
【iOS】SDWebImage源码学习
学习·ios