1. 概述
1.1 文档目的
本文档旨在指导社区开发者基于 BMC 可观测框架(OpenTelemetry),为新增硬件部件或软件功能开发完整的可观测能力,包括跟踪(Traces)、指标(Metrics)和日志(Logs)的采集、上报与验证
1.2 前置条件
- BMC 固件已升级至支持可观测框架的版本(libmc4lua ≥ 1.80.5 支持跟踪与指标数据采集接口,libmc4lua ≥ 1.90.21 支持日志上报和调试日志能力归一)
- 已准备可观测后端(至少包含
otelcol,建议包含 Zipkin/Prometheus/Elasticsearch/Grafana)
1.3 关键术语表
| 术语 | 说明 |
|---|---|
| Trace | 一次业务请求形成的完整调用链,由多个 Span 组成 |
| Span | Trace 中的最小工作单元,包含名称、时间、状态、属性和事件 |
| Meter | Metrics 入口对象,用于创建各类 Instrument |
| Instrument | 指标采集工具,如 Counter/UpDownCounter/Histogram |
| LogRecord | 日志记录单元,包含级别、内容、属性及可选 Trace 关联字段 |
| Attribute | 键值对元数据,用于对 Trace/Metric/Log 数据打标签 |
| opentelemetry-collector-contrib | 接收并转发可观测数据的中转组件(otelcol) |
| 可视化后端 | Zipkin、Prometheus、Elasticsearch、Grafana 等查询与展示系统 |
2. 背景介绍
2.1 概念介绍
2.1.1 可观测
可观测是基于openTelemetry规范构建的可视化能力,主要围绕系统中的可观测数据(包括指标、跟踪和日志),提供采集、上报以及与多种可视化工具集成的能力,帮助用户更好地监控系统状态、预测运行趋势、分析和定位系统故障
2.1.2 跟踪(Traces)
跟踪是一组事件,这些事件是由单个逻辑操作触发的,通过应用程序的各个组件处理后最终整合在一起。跟踪可能包含跨越进程、网络和安全边界的事件,因此也被称为分布式跟踪,当有人按下按钮在网站上启动一个操作时,就可能会启动分布式跟踪
具体来说,跟踪可以被认为是跨度(Span)的有向无环图 (DAG),其中跨度之间的边定义为父子关系。例如,以下是一个由6个跨度组成的跟踪示例:
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `child` of Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F]使用时间轴来显示跟踪轨迹会更容易理解,如下图所示:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··]2.1.3 指标(Metrics)
指标是对系统行为、性能或状态的量化测量,可以是任何可测量的数值,例如CPU使用率、内存占用率、请求延迟、错误率等。记录测量值主要涉及计量器(Meter)、仪表(Instrument)和测量值(Measurement):
- 计量器(Meter):主要负责创建和管理仪表(Instrument)
- 仪表(Instrument):具有名称、类型、描述和单位等标识,主要用于捕捉和记录测量值
- 测量值(Measurement):单次记录的原始值,包含键值对属性标签,用于标记和区分不同场景下的测量值
+------------------+
| MeterProvider | +-----------------+ +--------------+
| Meter A | Measurements... | | Metrics... | |
| Instrument X +-----------------> In-memory state +-------------> MetricReader |
| Instrument Y | | | | |
| Meter B | +-----------------+ +--------------+
| Instrument Z |
| ... | +-----------------+ +--------------+
| ... | Measurements... | | Metrics... | |
| ... +-----------------> In-memory state +-------------> MetricReader |
| ... | | | | |
| ... | +-----------------+ +--------------+
+------------------+2.1.4 日志(Logs)
日志用于记录应用程序的事件和状态信息,方便调试和故障排查
- OTel Logging API:原生日志接口(Logger/LoggerProvider)
- 上下文注入:自动添加 TraceID/SpanID 实现日志-追踪关联 核心组件和数据流向如下
+----------------------------------------------------------------+
| Application |
| |
| +----------------------+ +-------------------+ |
| | OTel Logging API | | OTel Context | |
| | (Logger/Logger- +-------> Propagator | |
| | Provider/LogRecord) | | (TraceID/SpanID) | |
| +----------+-----------+ +---------+---------+ |
| | | |
| | Inject Context | |
| +<----------------------------+ |
| | |
| +----------v-----------+ |
| | OTel Logs SDK | |
| | +-----------------+ | |
| | | LogRecord | | +-------------------+ |
| | | Processor +---------> Resource | |
| | | (Batch/Simple) | | | (service.name, | |
| | +--------+--------+ | | host.name, etc.) | |
| | | | +---------+---------+ |
| | +--------v--------+ | | |
| | | LogRecord | | Add Resource | |
| | | Exporter +<------------------+ |
| | | (OTLP/Console) | | |
| | +-----------------+ | |
| +----------+-----------+ |
+----------------------------------------------------------------+2.2 数据模型
2.2.1 跟踪
跟踪的数据模型
┌───────────────────────────────┐
│ TracerProvider │
├───────────────────────────────┤
│ + getTracer(name, version) │
└──────────────┬────────────────┘
│ creates
▼
┌───────────────────────────────┐
│ Tracer │
├───────────────────────────────┤
│ + startSpan(name, options) │
└──────────────┬────────────────┘
│ creates
▼
┌───────────────────────────────┐ has ┌──────────────┐
│ Span │◆──▶ │ SpanContext │
├───────────────────────────────┤ ├──────────────┤
│ - name: string │ │ - traceId │
│ - startTime: timestamp │ │ - spanId │
│ - endTime: timestamp │ │ - traceFlags │
│ - parentSpanId: String │ │ - isRemote │
├───────────────────────────────┤ └──────────────┘
│ + setAttribute(k,v) │ contains ┌─────────────┐
│ + addEvent(name, attributes) │───────▶ │ Attribute │
│ + recordException(exception) │ ├─────────────┤
│ + end() │ │ - key │
└──────────────┬────────────────┘ │ - value │
│ contains └─────────────┘
▼ ▲
┌─────────────┐ contains │
│ Event │──────────────────────────┘
├─────────────┤
│ - name │
│ - timestamp │
└─────────────┘跨度的组成
- 操作名称
- 开始和结束时间戳
- 属性:键值对列表
- 事件:零个或多个事件的集合,每个事件本身都是一个元组(时间戳、名称、属性)。名称必须是字符串
- 父级跨度标识符
- 链接:指向零个或多个因果相关的跨度的链接(通过这些相关的跨度上下文)
- 上下文:引用跨度所需的跨度上下文(Span Context)信息
跨度的名称
跨度的名称简洁地标识了跨度所代表的工作,例如RPC方法名称、函数名称或更大计算中的子任务或阶段的名称
跨度的类型
- SERVER 表示跨度涵盖了服务端处理的远程请求,而客户端等待响应
- CLIENT 表示跨度描述了一个远程服务的请求,客户端等待响应。当 CLIENT 跨度的上下文被传播时,CLIENT 跨度通常会成为远程 SERVER 跨度的父级
- PRODUCER 表示跨度描述了本地或远程操作的启动或调度。这个启动跨度通常在相关的 CONSUMER 跨度结束之前结束,甚至可能在 CONSUMER 跨度开始之前结束。在具有批处理的消息传递场景中,跟踪单个消息需要为每条消息创建一个新的 PRODUCER 跨度
- CONSUMER 表示跨度代表由生产者启动的操作的处理,生产者不等待结果
- INTERNAL 默认值。表示跨度代表应用程序内的内部操作,而不是具有远程父级或子级的操作
2.2.2 指标
指标的数据模型
Metric
+------------+
|name |
|description |
|unit | +------------------------------------+
|data |---> |Gauge, Counter, Histogram, ... |
+------------+ +------------------------------------+
Data [One of Gauge, Counter, Histogram, ...]
+-----------+
|... | // Metadata about the Data.
|points |--+
+-----------+ |
| +---------------------------+
| |DataPoint 1 |
v |+------+------+ +------+ |
+-----+ ||label |label |...|label | |
| 1 |-->||value1|value2|...|valueN| |
+-----+ |+------+------+ +------+ |
| . | |+-----+ |
| . | ||value| |
| . | |+-----+ |
| . | +---------------------------+
| . | .
| . | .
| . | .
| . | +---------------------------+
| . | |DataPoint M |
+-----+ |+------+------+ +------+ |
| M |-->||label |label |...|label | |
+-----+ ||value1|value2|...|valueN| |
|+------+------+ +------+ |
|+-----+ |
||value| |
|+-----+ |
+---------------------------+指标的组成
- 指标由元数据和数据组成
- 元数据部分包含多个属性:
- 指标名称
- 属性(维度,也被称为标签)
- 值类型(整数、浮点等)
- 计量单位
- 数据是计数器、仪表、直方图等类型之一
- 数据点包含时间戳、属性和值
指标的名称
- 不能为空字符串
- 不区分大小写的ASCII字符串
- 第一个字符必须是字母。后续字符必须是字母数字字符、“_”、“.”、“-”和“/”
- 最大长度为 255 个字符
指标的名称约定
- limit - 一种测量某种已知总量的恒定值的工具应称为entity.limit。例如,system.memory.limit 表示系统内存的总量
- usage - 一种测量已知总量(有限的)中已使用量的工具应称为 entity.usage。例如,system.memory.usage 带有属性 state = used | cached | free |…表示每种状态下的内存量。在适当的情况下,所有属性值的总使用量应等于总量
- utilization - 一种测量使用量相对于其限制的比例工具应称为 entity.utilization。例如,system.memory.utilization 表示正在使用的内存比例
- time - 一种测量时间流逝的工具应称为 entity.time。例如,system.cpu.time 带有属性 state = idle | user | system |…时间测量不一定是墙钟时间,可能小于或大于测量之间的实际墙钟时间
- io - 一种测量双向数据流的工具应称为 entity.io,并具有方向属性。例如,system.network.io
2.2.3 日志
日志的数据模型
Logger
+------------------+
| name |
| version | +------------------------------------+
| ... |---> |LogRecord 1 |
+------------------+ |+----------------+----------------+|
|| Timestamp | timestamp ||
|+----------------+----------------+|
|| SeverityNumber | severity_num ||
|+----------------+----------------+|
|| SeverityText | severity_text ||
|+----------------+----------------+|
|| Body | body ||
|+----------------+----------------+|
|| Attributes | +-----------+ ||
|| | |key: value | ||
|| | |... | ||
|| | +-----------+ ||
|+----------------+----------------+|
|| TraceId | (optional) ||
|| SpanId | (optional) ||
|+----------------+----------------+|
+------------------------------------+
.
.
.
+------------------------------------+
|LogRecord N |
| ... |
+------------------------------------+日志的组成
- 定义了一个与供应商无关的日志数据模型(Log Record),包含核心字段如时间戳、严重级别、消息体、Trace ID、Span ID、资源属性(服务名、实例ID等)、属性(键值对)
2.2.4 属性
属性在跟踪、指标和日志中都有使用。属性是一个键值对,必须具有以下要求:
- 属性键必须是一个非空的字符串。键的大小写敏感,大小写不同的键被视为不同的键
- 属性值可以是:
- 一个原始类型:字符串、布尔值、双精度浮点数(IEEE 754-1985)或带符号的 64 位整数
- 一个原始类型值的数组。数组必须是同质的,即它不能包含不同类型的值
- 属性的最大个数为128
属性的名称
以otel.开头的属性名称属于OpenTelemetry规范保留定义
属性的名称应遵循以下规则:
- 名称应为小写
- 使用命名空间,并使用点字符分隔命名空间。例如,service.version 表示服务版本,其中 service 是命名空间,version 是该命名空间中的一个属性。命名空间可以嵌套。例如,telemetry.sdk 是顶级 telemetry 命名空间中的一个命名空间,而 telemetry.sdk.name 是 telemetry.sdk 命名空间中的一个属性。在合适的情况下使用命名空间(和点分隔符)
- 对于名称中每个多词的点分隔组件,使用下划线分隔单词(即使用 snake_case)。例如,http.response.status_code 表示 http 命名空间中的状态码。仅在点(命名空间)的使用没有意义或改变名称的语义意义时使用下划线。例如,使用 rate_limiting 而不是 rate.limiting
- 属性、事件、指标和其他名称应具有描述性和明确性。在引入描述对象某个属性的名称时,包含属性名称。例如,使用 file.owner.name 而不是 file.owner,以及 system.network.packet.dropped 而不是 system.network.dropped。避免引入在不同约定或工具中使用时含义不同的名称和命名空间。例如,使用 security_rule 而不是 rule
- 在不影响清晰度的情况下使用更短的名称。在多词组件中删除不必要的命名空间组件或单词。例如,vcs.change.id 与 vcs.repository.change.id 一样精确地描述拉取请求 ID,使用 vcs.change.id 而不是 vcs.repository.change.id
2.3 可观测架构
2.3.1 可观测数据流图
- 应用组件通过应用框架基于openTelemetry SDK封装的跟踪、指标、日志接口采集应用可观测数据
- 应用框架通过可观测组件openTelemetry SDK采样器、处理器和导出器处理可观测数据,并上报到可观测组件fluent-bit服务端
- 可观测组件fluent-bit服务端根据可观测配置,转发和上报可观测数据到openUBMC外收集器及可视化平台
3. 应用接口
3.1 跟踪
本模块用于Trace相关能力接口的lua层隔离,以实现Lua层、封装层、开源引入层,Lua层与封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响
注意:当前业务通过调用libmc4lua提供的lua封装接口使用trace能力,libmc4lua弱依赖opentelemetry库,因此本地构建无法根据依赖拉取组件,目前本地自验只能通过手动依赖trace库来使libmc4lua依赖到,否则只能调用到libmc4lua打桩的空接口
3.1.1 tracer创建
tracer是Trace能力构建中最初一环,业务需要持有tracer用于创建span,利用span的记录存储上报能力构建跨服务跟踪链路,目前组件初始化过程中自动以组件名作为tracer name创建tracer并存储,组件后续调用get_tracer()将直接返回tracer,组件也可以通过传入参数name新建tracer
-- 方法原型
local tracer = trace.get_tracer(name, version, url)
-- 使用示例
local trace = require 'telemetry.trace'
local name = ...
local trace_custom = trace.get_tracer(name, '1.0.0')
local trace_default = tracer.get_tracer()req:
name: 必选参数,string类型,tracer名称,一般使用库名称. e.g."hwdiscovery"version: 可选参数,string类型,库版本. e.g."1.0.0"url: 可选参数,string类型,指向语义约定文档,用于属性命名规范. e.g."https://xxx/"
rsp:
tracer:userdata类型,由业务持有并调用start_span进行span创建,然后进行后续操作msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
3.1.2 span创建
span是跟踪链路中的最小功能单元,生命周期一般为单个函数或代码块,业务在需要跟踪的位置创建span,并通过span提供的能力进行数据跟踪,在生命周期结束后进行上报,后台根据上报span间的关联将不同服务上报的span整合为一条完整的跟踪链路
-- 方法原型
local child_span = tracer:start_span(name, attribute, options)
local child_span_opt = trace.start_span(name, attribute, options)
-- 使用示例
function get_object()
local root_span = tracer:start_span("get_object", {"level": "notice", "opcode": 1})
local parent_span_context = root_span:get_context()
local child_span = tracer:start_span("get_object", {"level": "notice", "opcode": 1}, {parent = parent_span_context})
-- 支持不通过tracer直接创建span,trace会自动获取当前组件名与组件版本创建tracer
local child_span_opt = trace.start_span("get_object", {"level": "notice", "opcode": 1}, {parent = parent_span_context})
endreq:
name: 必选参数,string类型,span的名称,无需唯一性,span创建时会分配span_id作为唯一性标识,业务不感知. e.g. "get_object"attribute: 必选参数,字典类型,键必须为string,值只支持string、bool、int、double类型,span的属性,由用户根据需要使用,后续跟随span数据导出,可用于链路标记、数据筛选等. e.g.{"level": "notice", "opcode": 1}options: 可选参数,字典类型,代表span创建时的可选配置项。可选项有:parent: table类型,只能使用由span:get_context()导出的spancontext数据,传入时创建为child span,继承上级的trace_id, 不传入时创建为root span,作为整条跟踪链路的起始force_sample: bool类型,作为组件自定义采样标记,在业务需要强制跟踪采样时传入true
rsp:
- span:
userdata类型,由业务持有并调用相关方法完成跟踪链路的构建,Lua层未引用到封装层或创建失败会返回一个noop span,进行的任何操作都不会生效,数据也不会上报 - msg:
string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
3.1.3span操作
set_attribute 设置span属性
span属性后续跟随span数据导出,可用于链路标记,数据筛选等
-- 方法原型
span:set_attribute(key, value)
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
span:set_attribute("level", "notice")
local err_msg = span:set_attribute(key, value)
local err_msg then
log:error("%s", err_msg)
end
endreq:
key: 必选参数,string类型,属性名称,若已存在该属性名,则该操作会修改原属性. e.g."level"value: 必选参数,支持string、bool、int、double类型,对应属性名的属性值
rsp:
msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
add_link 添加关联span
在span之间建立非父子关系的关联,一般为异步场景使用
-- 方法原型
span:add_link(span_context, attribute)
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
span:add_link(span_context, {"level": "notice", "opcode": 1})
local err_msg = span:add_link(span_context, attribute)
local err_msg then
log:error("%s", err_msg)
end
endreq:
span_context: 必选参数,字典类型,只能使用由span:get_context()导出的spancontext数据attribute: 可选参数,字典类型,键必须为string,值只支持string、bool、int、double类型,span的属性,添加links时的补充属性,由用户根据需要使用,后续跟随span数据作为links字段的额外字段导出. e.g.{"level": "notice", "opcode": 1}
rsp:
msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
add_event 添加span事件
在span中记录事件,类似日志记录操作,是用于可视化的主要部分
-- 方法原型
span:add_event(name, attribute)
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
span:add_event(string.format("get object failed: %s", ret), {"level": "notice", "opcode": 1})
local err_msg = span:add_event(name, attribute)
local err_msg then
log:error("%s", err_msg)
end
endreq:
name: 必选参数,string类型,事件描述信息,即日志记录内容. e.g."get object failed"attribute: 可选参数,字典类型,键必须为string,值只支持string、bool、int、double类型,添加event时的补充属性,添加event时的补充属性,由用户根据需要使用,后续跟随span数据作为event字段的额外字段导出. e.g.{"level": "notice", "opcode": 1}
rsp:
msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
set_status 设置当前span状态
设置当前span状态,默认状态为unset,支持unset、ok、error三种状态设置,span状态可用于尾部采样、可视化筛选等
-- 方法原型
span:set_status(status, description)
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
span:set_status("ok", "get object successed")
local err_msg = span:set_status(status, description)
local err_msg then
log:error("%s", err_msg)
end
endreq:
status: 必选参数,string类型,span状态,仅可设置description. e.g."ok"description: 必选参数,string类型,状态补充描述. e.g."get object successed"
rsp:
msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
get_context 获取当前span的上下文信息
获取当前span的上下文信息,用于后续span进行link或child span创建
-- 方法原型
local context = span:get_context()
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
local context = span:get_context()
local context, err_msg = span:get_context()
local err_msg then
log:error("%s", err_msg)
end
endrsp:
context:span的上下文信息,字典类型,组成字段为trace_id,span_id,trace_state,is_remoting,trace_flags, 业务不感知具体信息,仅用来传递,禁止修改context内容,否则会导致下级span链接失败msg:string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回
is_recording 检查当前span是否可操作
采样策略制定、发生错误等都会导致创建span失败而返回no operation span,当业务需要记录的内容需要通过耗时或资源占用等方式获取,可以通过该方法进行判断是否需要收集记录信息
-- 方法原型
local is_recording = span:is_recording()
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
if span:is_recording() then
span:add_event()
span:set_status()
end
endrsp:
is_recording:bool类型,反映当前span是否可操作,发生错误时则返回false
finish 结束当前span
结束当前span,自动触发存储或上报等操作
-- 方法原型
span:finish()
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
span:add_event()
span:set_status()
span:finish()
endpcall 执行方法并捕获执行状态并记录到span中进行上报
类似lua的pcall方法,保护调用之外,同时捕获函数内部的执行结果,刷新span状态为"ok"或"error"并上报, 并且会自动结束此次跟踪
-- 方法原型
span:pcall(cb, ...)
-- 使用示例
function get_object()
local span = trace.start_span(name, attribute)
local function record(time, msg)
if type(msg) ~= "string" then
error("msg must be string")
end
print(time, msg)
end
local ok, ret = span:pcall(record, time, msg)
endreq:
function: 必选参数,pcall进行保护调用的函数. e.g.function();param: 可选不定参数,保护调用函数的参数
rsp:
status:bool类型,表示函数是否成功执行(true表示成功,false表示失败)。result: 函数的返回值(如果成功)或错误信息(如果失败)
3.2 指标
本模块用于metrics相关能力接口的Lua层隔离,以实现Lua层、封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响
核心概念简述
MeterProvider:整个Metrics API的入口点,负责创建和管理所有Meter实例Meter:由MeterProvider创建,负责生成具体的指标工具Instrument。根据业务需要,通过CreateXxxInstrument()创建不同类型Instrument:由Meter创建,直接负责采集和上报具体的测量数据
层级结构示例:
+-- MeterProvider()
|
+-- Meter(name='test1', version='1.0.0', schema url='https://test//test')
| |
| +-- Instrument<Counter>(name='counter_test', description='test counter', unit='kb')
| |
| +-- instrument...
|
+-- Meter(name='test2', version='1.1.0', schema url='https://test//test')
|
+-- Instrument<ObservableCounter>(name='observable_counter_test', description='test observable counter', unit='kb')
|
+-- Instruments...3.2.1 meter创建
meter是metrics能力构建中最初的一环,业务需要持有meter根据业务创建不同类型的instrument来记录数据。组件通过调用get_meter()来创建meter
-- 方法原型
local meter = metrics.get_meter(name, version, schema_url)
-- 使用示例
local metrics = require 'telemetry.metrics'
local meter_custom = metrics.get_meter(name)req:
name: 必选参数,string类型,meter名称version: 可选参数, 版本号schema_url: 可选参数, 关联的语义约定的URL
3.2.2 instrument创建
instrument是负责采集和上报具体的测量数据的载体,业务根据需要选择创建不同类型的instrument
NOTE
仪器命名呈现在最终可视化系统中,直接体现了数据上报的易读性,需要着重审视命名合理性
-- 方法原型
meter:create_counter(name, description, unit)
meter:create_updowncounter(name, description, unit)
meter:create_observable_counter(name, description, unit)
meter:create_observable_updowncounter(name, description, unit)
meter:create_observable_gauge(name, description, unit)
-- 使用示例
local counter = meter:create_counter("counter_data", "count some data for test", "kb")create_counter:创建一个单调递增的计数器countercreate_updowncounter:创建一个可递增递减的计数器countercreate_observable_counter:创建一个异步的单调递增的计数器observable_countercreate_observable_updowncounter:创建一个异步的可递增递减的计数器observable_updowncountercreate_observable_gauge:创建一个异步的瞬时仪表observable_gauge
req:
name: 必选参数,string类型,instrument名称description: 可选参数,instrument的相关描述信息unit: 可选参数, 采集数据的单位,e.g."kb"
3.2.3 instrument方法调用
同步仪器
同步仪器包括counter、updowncounter、histogram,通过显式调用直接记录指标
-- 创建 Meter
local meter = metrics.get_meter()
-- 创建 Counter
local counter = meter:create_counter("my_counter")
-- 增加Counter计数器的值
counter:add(10)-- 创建 Meter
local meter = metrics.get_meter()
-- 创建 UpDownCounter
local updowncounter = meter:create_updowncounter("my_updowncounter")
-- 增加或减少UpDownCounter的值
updowncounter:add(5) -- 增加
updowncounter:add(-3) -- 减少
-- 使用 Attribute 给指标添加维度示例
flash_io_counter:add(data_size, {file_type="log", mc_name="xx"})
flash_io_counter:add(data_size, {file_type="persistence", mc_name="xx"})-- 创建 Meter
local meter = metrics.get_meter()
-- 创建 Histogram
local histogram = meter:create_histogram("my_histogram")
-- 记录Histogram的值
histogram:record(5)
-- 使用 Attribute 给指标添加维度示例
record:record(data, {mc_name="xx"})在实际业务的度量采样中,添加标签可以从多个维度去分析同一个指标,可视化系统支持通过标签筛选和聚合指标。例如:
otelcol_flash_io_total{file_type = "log"}可查看指标在写入类型为日志的flash写入量数值变化sum(otelcol_flash_io_total{file_type = "persistence"})可查看指标在写入类型为持久化的flash写入总量的数值变化
异步仪器
异步仪器包括observable_counter,observable_updowncounter,observable_gauge,也称为观察仪器,则是通过注册回调函数来采集数据。这些回调函数会在收集指标时被调用,这些数据需要定期轮询获取
-- 创建 Meter
local meter = metrics.get_meter()
-- 创建ObservableCounter
local observable_counter = meter:create_observable_counter("my_observable_counter")
-- 注册回调函数
observable_counter:add_callback(function()
return 30
end)3.3 日志
本模块用于logs相关能力接口的Lua层隔离,以实现Lua层、封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响
日志接口与调试日志接口已归一,内部通过键值对列表和参数列表方式区分上报日志和调试日志
3.3.1 日志记录
| 接口 | 功能 |
|---|---|
| log:debug(body, attrs) | 上报debug级别日志 |
| log:info(body, attrs) | 上报info级别日志 |
| log:notice(body, attrs) | 上报notice级别日志 |
| log:warn(body, attrs) | 上报warn级别日志 |
| log:error(body, attrs) | 上报error级别日志 |
-- 记录debug日志
log:debug('my debug log')
-- 记录debug日志,和相关属性信息
log:debug('my debug log', {system_id=1, mc_name="xx"})
-- 其他级别日志使用方法同上4. 可观测后端环境搭建指导
可观测后端环境一共需要五个,包括:
- opentelemetry-collector-contrib(用于接收bmc发送的可观测数据,再将可观测数据分别发送给zipkin、prometheus和elasticsearch后端接收,实现类似中转站的能力)
- zipkin(用于接收可观测traces数据)
- prometheus(用于接收可观测metics数据)
- elasticsearch(用于接收可观测logs数据)
- grafana(统一的可视化后端,可直接通过grafana对接zipkin、prometheus和elasticsearch,用于查看上报的可观测数据)
个人使用可以通过下载对应软件的windows包进行搭建,linux或其他操作系统仅软件包形式有差异,配置方法均相同
4.1 opentelemetry-collector-contrib 搭建
opentelemetry-collector-contrib(简称otelcol),可从otelcol下载地址进行下载。BMC与otelcol之间需要支持TLS加密,从而防止可观测数据泄漏,因此在otelcol环境搭建之前需要先准备证书
4.1.1 证书制作方法
安装XCA
XCA是一款开源的x509证书生成工具,可用来生成BMC与otelcol之间认证的TLS证书,可从XCA安装包下载地址进行下载
说明:如果遇到XCA 2.9.0下载过慢,可选择XCA 2.8.0或更低版本进行下载
新建数据库
点击左上角文件->新建数据库->设置数据库密码
制作CA证书
(1)创建证书
(2)来源
一定要点击应用模板扩展信息!
(3)主题
(4)扩展
证书的有效期要早于服务端以及客户端的系统时间!建议有效期的起始时间设置早一点
创建完成后应有一CA证书
制作SSL证书
(1)创建证书
(2)来源
(3)主题
(4)扩展
证书的有效期要早于服务端以及客户端的系统时间!建议有效期的起始时间设置早一点
创建完成后应有一CA证书签发的ssl证书
导出证书
(1)导出CA证书 选中CA证书->导出
(2)导出SSL证书 选中SSL证书->导出
(3)导出SSL私钥
最终得到的证书和密钥说明
1)可观测CA证书,用于导入openUBMC,或者用于开启双向认证后配置otelcol的client_ca_file参数
observability_CA.crt2)可观测openTelemetry Collector Contrib证书,用于配置otelcol的cert_file参数
observability.otelcol.crt3)可观测openTelemetry Collector Contrib私钥,用于配置otelcol的key_file参数
observability.otelcol.key.pem4.1.2 配置opentelemetry-collector-contrib
完成证书制作之后,就可以进行部署otelcol了,下载的安装包中没有配置文件(otelcol-config.yaml),需要手动创建otelcol-config.yaml这个配置文件,然后配置如下:
receivers:
otlp:
protocols:
http:
endpoint: "0.0.0.0:4318" # OTLP over HTTP,4318端口为bmc与otelcol通信端口,可自行更改为其他端口使用
max_request_body_size: 67108864 # 64MB
tls:
# client_ca_file: observability_CA.crt # 用于双向认证的CA证书,bmc未开启双向认证不要设置该属性
cert_file: observability.otelcol.crt # otelcol证书
key_file: observability.otelcol.key.pem # otelcol密钥
processors:
# 批处理器 - 控制数据分批
batch:
send_batch_size: 1024
send_batch_max_size: 2048
timeout: 1s
# 内存限制器
memory_limiter:
limit_mib: 512
spike_limit_mib: 128
check_interval: 5s
exporters:
debug:
verbosity: detailed
prometheus:
endpoint: "127.0.0.1:49091" # prometheus对外端口
namespace: "otelcol"
elasticsearch:
endpoint: "https://127.0.0.1:9200" # elasticsearch默认端口,不要修改
user: "elastic"
password: "xxxxxx" # elasticsearch初始启动过程可获取默认密码,或通过自行重置获取密码
tls:
ca_file: /home/elasticsearch/elasticsearch-9.1.0/config/certs/http_ca.crt
zipkin:
endpoint: "http://127.0.0.1:9411/api/v2/spans" # 9411为zipkin默认端口,无需修改
service:
pipelines:
metrics:
receivers: [otlp]
exporters: [debug, prometheus]
logs:
receivers: [otlp]
exporters: [debug, elasticsearch]
traces:
receivers: [otlp]
exporters: [debug, zipkin]BMC默认开启单向认证,因此otelcol必须要配置cert_file属性和key_file属性;如果开启双向认证,那么需要配置client_ca_file属性
启动otelcol命令,--config参数后填otelcol-config.yaml文件路径:
otelcol-contrib --config otelcol-config.yaml特别提醒: 如果启动otelcol时提示启动失败,原因为某个端口号被占用,需要将占用了该端口号的程序关闭才能使otelcol启动成功
4.2 zipkin后端搭建
Zipkin用于接收opentelemetry-collector-contrib发送的Trace类型数据,并进行可视化呈现,可通过zipkin下载地址进行下载
由于zipkin安装依赖环境上的java版本,因此部署环境java版本较低的话需要进行升级,通过openjdk下载地址下载后安装
Zipkin默认不需要额外的配置文件,只要环境上java版本满足zipkin要求,可直接执行下列命令启动:
java -jar /zipkin_path/zipkin-server-3.5.1-exec.jar如果下载了对应版本的openJDK,则执行时指定JDK路径即可
/jdk_path/jdk-17.0.1/Home/bin/java -jar /zipkin_path/zipkin-server-3.5.1-exec.jar4.3 prometheus后端搭建
prometheus用于接收opentelemetry-collector-contrib发送的Metric类型数据,并进行可视化呈现,可通过prometheus下载地址进行下载,prometheus启动依赖配置文件(prometheus.yml),配置示例如下:
- scrape_interval为Prometheus抓取上报数据的周期,可按需调整
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["127.0.0.1:49091"] # 监听49091端口,用于接收otelcol发送的Metric数据,需要与otelcol中配置的prometheus端口一致
# The label name is added as a label `label_name=<label_value>` to any timeseries scraped from this config.
labels:
app: "prometheus"启动prometheus,通过49092端口对外提供web服务
./prometheus --web.enable-otlp-receiver --web.listen-address=0.0.0.0:49092 --config.file=prometheus.yml4.4 elasticsearch后端搭建
elasticsearch用于接收opentelemetry-collector-contrib发送的Log类型数据,可通过elasticsearch下载地址进行下载,elasticsearch需要以非管理员方式启动,因此部署时需要创建非管理员用户
step1:添加普通用户
useradd elasticsearchstep2:解压elasticsearch压缩包(压缩包名称按实际填写)到指定路径(例如/home/elasticsearch)
tar xvf elasticsearch-9.1.0-linux-aarch64.tar.gz -C /home/elasticsearchstep3:为elasticsearch所在目录设置权限
chown -R elasticsearch:elasticsearch /home/elasticsearch/step4:启动elasticsearch(压缩包名称按实际填写)
/home/elasticsearch/elasticsearch-9.1.0/bin/elasticsearch &step5:elasticsearch首次启动将在控制台打印默认用户名密码,需要记录下来并用于opentelemetry-collector中elasticsearch密码配置。
说明:如果未能记录下初始启动过程中的密码,也可以使用
bin/elasticsearch-reset-password -u elastic进行密码重置
初始启动过程默认密码信息打印如下,Password for the elastic user下一行即为默认密码:
✅ Elasticsearch security features have been automatically configured!
✅ Authentication is enabled and cluster connections are encrypted.
ℹ️ Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
xxxxxx
...说明:elasticsearch默认不提供可视化界面,需要通过grafana可视化后端进行查看数据
4.5 grafana后端搭建
grafana后端用于对接zipkin、prometheus和elasticsearch后端,可通过grafana统一查看所有可观测数据,无需分别登陆zipkin或prometheus的可视化界面
1)grafana安装包可从grafana下载地址进行下载
2)设置语言为中文(可选) 进入conf目录下,编辑defaults.ini文件如下字段:
default_language = zh-Hans3)启动grafana 运行bin目录下grafana-server即可
./bin/grafana-server &4)登录grafana WEB UI 默认端口号为3000,默认用户名和密码均为admin
http://{ip}:3000说明:此处的ip为grafana 部署环境的ip,由于上述配置采用全零监听,针对Linux/Windows系统对外IP即可访问(可通过ifconfig或ipconfig进行查询),如果本地环回地址未被占用,也可使用127.0.0.1进行访问
登录到grafana WEB UI之后,还需要配置数据源才能实现对接
1)点击连接 -> 数据源
2)点击右上角 添加数据源
3)选择对应类型的数据源,分别配置 prometheus、 elasticsearch、 zipkin数据源
4)在Connection位置配置数据源URL,例如 http://{ip}:{port}/
4.6 docker一键式部署
由于可观测功能涉及的可视化后端数量众多,且部署过程可能存在一些问题在本指南中没有说明,因此openUBMC提供了一份集成所有可视化后端的docker文件,集成在manifest中的dockerfile文件中,开发者确保可视化后端的下载地址均能访问即可,在完成manifest初始化之后就能将docker镜像生成出来
需要注意的是,运行镜像命令须如下执行,需要额外添加用于可视化后端通信的端口绑定及文件夹挂载,否则会出现端口无法访问、bmc与可视化后端通信失败等问题,openubmc/xx.yy为生成的镜像名称
docker run \
-p <BMC Studio服务端口>:10000 \
-p <qemu ssh对外服务端口>:<qemu ssh服务端口> \
-p <zipkin对外服务端口>:9411/tcp \
-p <otelcol对外服务端口>:4318/tcp \
-p <prometheus对外服务端口>:49092/tcp \
-p <grafana对外服务端口>:3000/tcp \
...
-d --mount type=bind,source=/root/workspace,target=/home/workspace \
--mount type=bind,source=<证书存放路径>,target=/root/workspace/observability/collector-contrib/cert \
--restart=always --name openubmc openubmc/xx.yy下述四个端口为可视化后端正常运行必须使用的端口,务必进行配置
9411端口为zipkin默认端口,zipkin对外服务端口建议设置为9411或者其他端口4318端口为otelcol默认端口,otelcol对外服务端口建议设置为4318或者其它端口49092端口为prometheus默认端口,prometheus对外服务端口建议设置为49092或者其他端口3000端口为grafana默认端口,grafana对外服务端口建议设置为3000或者其他端口
证书存放路径为可视化后端需要使用的证书和密钥,请将准备好的证书中的SSL证书与SSL密钥存放路径进行替换,dockerfile中/root/workspace/observability/collector-contrib/cert为镜像中otelcol读取证书文件的路径,不要进行更改
4.6.1 执行部署脚本
进入容器后执行以下步骤:
- 1、执行
su root切换为root用户 - 2、执行
cd /root/workspace/observability进入可视化后端集成的目录 - 3、执行
bash observability.sh start启动可视化后端 - 4、一般来说执行完成第三步之后,直接执行第五步即可,但如果没有看到回显
Password for the [elastic] user successfully reset.的话,说明Elasticsearch密码重置失败,需要再次执行expect /root/workspace/observability/reset_elastic_password.exp重置Elasticsearch密码为预置密码 - 5、执行完成上述步骤后访问grafana对外服务端口(如127.0.0.1:3000)访问grafana终端,首次登陆grafana需要使用
用户名为admin、密码为admin进行登录 - 6、此外还可以直接访问zipkin对外服务端口(如127.0.0.1:9411)访问zipkin终端来查询跟踪数据;直接访问prometheus对外服务端口(如127.0.0.1:49092)访问prometheus终端来查询度量数据
grafana使用方法可阅读可观测特性使用指南进行了解
5. 常见问题解答
5.1 DT时如何导出可观测数据
跟踪数据导出:DT场景下span结束时会通过文件流导出器将span数据导出到os.get_env('PROJECT_DIR') .. '/test/.test_temp_data/test_trace.txt,用户在DT中需要收集span数据时调用trace.flush()手动刷新缓冲区,后续可通过对比span数据进行DT自测
指标数据导出:DT场景下会通过文件流导出器将metrics数据导出到os.get_env('PROJECT_DIR') .. '/test/.test_temp_data/metric.txt
注:导出数据为txt格式,非json格式