指纹文件编写指南

log-redfish-lts · 让任意 Redfish 路径只靠配置即可采集数据

一、指纹是什么、为什么要有它

不同厂商、不同型号的 BMC(基板管理控制器)暴露传感器数据的 接口路径和 JSON 结构千差万别

如果把"取哪个路径、取哪个字段"写死在代码里,每换一台机器就要改代码、重新发版。指纹机制的做法是:

把"路径 + 字段映射"全部抽到 JSON 文件里,代码只负责"读 JSON → 请求路径 → 按映射提取 → 输出 CSV"。 换机器时,只需新增/修改一条指纹,零代码改动
指纹文件 JSON 程序自动探测匹配 按 fields 提取 过滤无效值 CSV 输出

二、指纹文件整体结构

指纹文件默认为 uri_fingerprints.json(可在 config.ini 里改 fingerprint_file),是一个 JSON 数组,每个元素是一条指纹:

[
  {
    "id": 1,                          // 整数,全局唯一,用于 fingerprint_id 指定或日志引用
    "type": "redfish_temp_power_speed_duty", // 字符串,人类可读的类型名,仅用于日志展示
    "Uris": {                          // 对象:键=URI路径,值=该路径的字段配置
      "/redfish/v1/Chassis/1/ThresholdSensors": {
        "fields": [ /* 见第三章 */ ]
      },
      "/redfish/v1/Chassis/1/Oem/Public/Thermal": {
        "fields": [ /* 一条指纹可同时从多个 URI 聚合数据 */ ]
      }
    }
  },
  { "id": 2, "type": "...", "Uris": {/* ... */} }
]
一条指纹 = 一种机型/一种接口形态。一条指纹下可以有多个 URI,程序会把多个 URI 提取到的字段 合并到一个 CSV 行里。这样当一台机器的传感器分散在多个接口时,也能一次采全。

三、fields 的三种写法(核心)

每个 URI 下 "fields" 是数组,数组里每个对象描述"一个要提取的量"。根据 path 里通配符的不同,分三种模式:

模式 A:数组模式 [*] —— 传感器是一个数组

当接口返回形如 "Sensors": [ {...}, {...} ] 的数组时使用。程序会遍历数组每个元素,从中各取一个 name 和若干 value。

fields 配置典型响应提取结果
[
  {"name":"name",  "path":"Sensors[*].Name"},
  {"name":"value", "path":"Sensors[*].ReadingValue"}
]
{
  "Sensors": [
    {"Name":"CPU0_Temp","ReadingValue":45},
    {"Name":"CPU1_Temp","ReadingValue":47}
  ]
}
{
  "CPU0_Temp": 45,
  "CPU1_Temp": 47
}

规则:同一 [*] 前缀的字段会按 name 配对。name 字段的值作为结果键名;其余字段作为值。若只有一个 value 字段且名为 value,结果键就是 name 本身;否则键名为 传感器名_字段名(见下文命名规则)。

需 v2 引擎 支持嵌套根路径:path 中 [*] 之前可以是多层路径,例如 Oem.Public.Fans[*].FanName。旧版引擎只支持顶层键(如 Sensors[*].Name)。

模式 B:字典模式 {*} —— 传感器是一个字典

当接口返回形如 "data": { "CPU0": {...}, "CPU1": {...} } 的对象时使用。程序遍历字典每个键值对。

fields 配置典型响应提取结果
[
  {"name":"name",  "path":"data.{*}.@key"},
  {"name":"value", "path":"data.{*}.Value"}
]
{
  "data": {
    "CPU0": {"Value":45},
    "CPU1": {"Value":47}
  }
}
{
  "CPU0": 45,
  "CPU1": 47
}

关键点@key 是一个特殊后缀,表示"用字典的键本身作为传感器名"。如果不写 @key,则需要再配一个 name 字段指向某个属性来取名字。

需 v2 引擎 name/value 都支持多层嵌套后缀,例如 data.{*}.Sensor.Value(取 data 下每个对象的 Sensor.Value)。旧版只支持一层。

模式 C:固定字段模式 无通配符 —— 提取单个标量

当只需要从响应里取某个固定位置的值(比如设备型号、序列号、单一读数)时使用。path 直接是嵌套路径,不含 [*]{*}

fields 配置典型响应提取结果
[
  {"name":"model", "path":"Oem.DeviceInfo.Model"},
  {"name":"sn",    "path":"SerialNumber"}
]
{
  "SerialNumber":"ABC123",
  "Oem":{"DeviceInfo":{"Model":"X2000"}}
}
{
  "model": "X2000",
  "sn": "ABC123"
}
需 v2 引擎 固定字段模式是 v2 新增,旧版引擎会忽略所有不含通配符的 path。
三种模式可混用:同一个 fields 数组里可以同时出现三种模式的字段,程序会分别处理再合并到同一行结果。

四、path 语法速查

写法含义示例
Key取对象下某键SerialNumber
a.b.c多层嵌套(点分隔)Oem.DeviceInfo.Model
[*]遍历数组每个元素Sensors[*].Name
{*}遍历字典每个键值对data.{*}.Value
@key(仅字典模式 name)用字典键本身作传感器名data.{*}.@key
数字(在路径中)数组索引(0 起)Fans.0.Name 取第 1 个
大小写敏感:path 里的键名必须和响应里的 JSON 键完全一致(包括大小写)。Namename。 建议先用 curl -k -H "X-Auth-Token: xxx" https://<ip>/ 路径 看真实响应再写 path。

五、结果键名是怎么生成的

提取出来的数据最终拼成一行 CSV,列名(键名)的生成规则:

情况键名示例
只有一个 value 字段,且其 name"value" 直接用传感器名 CPU0_Temp
有多个值字段(如 rpm + duty),或 value 的 name 不叫 value 传感器名_字段name Fan0_rpmFan0_duty
字典模式用 @key 字典键(若含 / 取最后一段) root/sensor/CPU0CPU0
固定字段模式 field 的 name model
设计意图:传感器通常只有一个读数,所以"单 value + name=value"是常见用法,让列名干净; 风扇这种同时有转速和占空比的,才用 name_rpm / name_duty 区分。

六、程序怎么知道用哪条指纹(探测机制)

程序启动后调用 probe() 匹配指纹,有两种触发方式:

1. 自动探测(推荐)

config.ini 里 不写 fingerprint_id(或注释掉)。程序会:

  1. 按指纹文件里的顺序,逐条指纹尝试;
  2. 对每条指纹,逐个请求它所有的 URI
  3. 只要任一 URI 请求成功且 fields 能提取到非空数据,就认定这条指纹匹配,停止遍历。
这正是"任意路径都能采集"的关键:你在指纹文件里新加一条、新配一个路径,程序自动就能识别,不需要改任何代码或配置。

2. 指定 id(强制)

config.ini 里写 fingerprint_id = 2。程序直接用 id=2 的指纹,但仍会验证它的所有 URI

需 v2 引擎 旧版探测只验证每条指纹的第一个 URI,且只看 HTTP 有没有响应、不看字段能否提取,会出现"路径变了但还匹配到旧指纹"的假匹配。v2 改为"实际提取到数据才算匹配"。
为什么不自动回退:当你显式写了 fingerprint_id,是在声明"这台机器就用这个指纹"。若验证失败通常是配置错误,直接报错+诊断信息比悄悄换一条指纹更安全。

七、采集后会自动过滤的值

提取出来的原始结果,还会过一道清洗,以下值会被整列丢弃(不出现在 CSV 里):

无效值含义
65535 / 65535.0传感器故障或未接入时的占位标记
null / None字段缺失
空字符串 ""空读数
"NA"(不区分大小写)Not Available
注意是"整列丢"不是"填空":如果某次 CPU0_Temp=65535,那么这一行 CSV 里就没有 CPU0_Temp 这一列。 因为表头在初次采集时确定,之后每次按表头输出,缺失的列会是空。这是为了避免把 65535 当真实温度画进图表。

八、编写注意事项

① id 必须唯一且为整数:两条指纹用了同一个 id,自动探测没影响,但用 fingerprint_id 指定时只会命中第一条。
② 每条 fields 必须有 name 字段:数组/字典模式里,如果没有 name 来源(name 字段或 @key),那个传感器会被跳过。 固定字段模式则用 field 自己的 name 当列名。
③ path 大小写要和响应完全一致。最容易踩的坑:Name 写成 name 导致提取为空。
④ 数组模式要确保根是数组Sensors[*].Name 要求 Sensors 的值是 [...]。 如果实际是 {"0":{...}} 这种"伪数组",改用字典模式 Sensors.{*}.Name
⑤ @odata.id 不能当数据:Redfish 响应里很多对象有 @odata.id 指向另一个 URL,它本身不是读数。要取的数据应在叶子节点。
⑥ 一条指纹可聚合多个 URI:如果温度在 A 接口、风扇在 B 接口,把它们都写进同一条指纹的 Uris,程序会合并输出。 但要确保这些 URI 在同一台机器上都可用,否则其中某个失败只是少几列,不影响其它(见 collect_once 的诊断)。
⑦ URI 开头的斜杠:写成 /redfish/v1/...,程序内部会拼到 host 上。不要写完整域名。
⑧ 调试技巧:日志目录 logs/ 下会记录每个 URI 的采集诊断(成功提取几个字段、响应空、字段不匹配等)。 配不通时先看日志,再 curl 对比真实结构。

九、一个完整的多机型指纹文件示例

[
  {
    "id": 1,
    "type": "redfish_standard",
    "Uris": {
      "/redfish/v1/Chassis/1/ThresholdSensors": {
        "fields": [
          {"name":"name",  "path":"Sensors[*].Name"},
          {"name":"value", "path":"Sensors[*].ReadingValue"}
        ]
      },
      "/redfish/v1/Chassis/1/Oem/Public/Thermal": {
        "fields": [
          {"name":"name", "path":"Fans[*].Name"},
          {"name":"duty","path":"Fans[*].SpeedRatio"}
        ]
      }
    }
  },
  {
    "id": 2,
    "type": "oembmc_dict_style",
    "Uris": {
      "/sensors/allinfo": {
        "fields": [
          {"name":"name",  "path":"{*}.11"},
          {"name":"value", "path":"{*}.1"}
        ]
      }
    }
  },
  {
    "id": 3,
    "type": "kunlun_webui",
    "Uris": {
      "/kunlun/webui/sensor": {
        "fields": [
          {"name":"name",  "path":"data.{*}.@key"},
          {"name":"value", "path":"data.{*}.Value"}
        ]
      }
    }
  },
  {
    "id": 4,
    "type": "new_machine_with_nested_paths",
    "Uris": {
      "/redfish/v1/Chassis/1/Thermal": {
        "fields": [
          {"name":"name", "path":"Oem.Public.Fans[*].FanName"},
          {"name":"rpm",  "path":"Oem.Public.Fans[*].ReadingRPM"}
        ]
      },
      "/redfish/v1/Chassis/1": {
        "fields": [
          {"name":"model", "path":"Oem.DeviceInfo.Model"},
          {"name":"sn",    "path":"SerialNumber"}
        ]
      }
    }
  }
]
id=4 这条同时演示了 数组嵌套根 Oem.Public.Fans[*].FanName字典多层后缀固定字段 Oem.DeviceInfo.Model 三种 v2 能力的混合用法。

十、常见问题排查表

现象可能原因排查
未匹配到任何指纹所有指纹的 URI 在该机器都不可用 / fields 都提取为空看日志诊断;curl 各 URI;核对 path 大小写
匹配成功但 CSV 某些列空那次该传感器值=65535/NA 被过滤,或该 URI 偶发失败正常;持续空则检查该 URI 稳定性
提取结果为空(字段不匹配)path 写错 / 键名大小写不符 / 数据结构变了curl 看真实响应,逐段对照 path
数组模式提取为空根不是数组(是字典),应改用 {*}检查响应里该键的值是 [...] 还是 {...}
嵌套路径提取不到(v2 特性)代码还是旧版引擎确认 jsonpath.py 已升级(含 fixed_fields、_get_nested_value 导航)
固定字段模式不生效同上,旧版引擎忽略无通配符 path升级到 v2 引擎
指定 fingerprint_id 报错退出该指纹所有 URI 都提取不到数据看报错诊断信息;机器可能不支持该指纹
会话过期反复重登token 超时程序已自动重登,无需处理;若频繁可缩短 interval