仪表化
Instrumention 是指您自己向应用程序添加可观察性代码的过程。
如果您正在 instrumenting 应用程序,您需要为您使用的语言使用 OpenTelemetry SDK。然后,您将使用 SDK 初始化 OpenTelemetry,并使用 API instrument 您的代码。这将从您的应用程序和任何也带有 instrumention 的已安装库中发出遥测数据。
如果您正在 instrumenting 库,请仅安装您语言的 OpenTelemetry API 包。您的库不会自行发出遥测数据。只有当它作为使用 OpenTelemetry SDK 的应用程序的一部分时,它才会发出遥测数据。有关 instrumenting 库的更多信息,请参阅 Libraries。
有关 OpenTelemetry API 和 SDK 的更多信息,请参阅 specification。
设置
将以下依赖添加到您的项目中
opentelemetry_api:包含您用于 instrument 代码的接口。Tracer.with_span和Tracer.set_attribute等功能在此定义。opentelemetry:包含实现 API 中定义的接口的 SDK。没有它,API 中的所有函数都将是 no-ops(无操作)。
# mix.exs
def deps do
[
{:opentelemetry, "~> 1.3"},
{:opentelemetry_api, "~> 1.2"},
]
end
追踪
初始化追踪
要开始 tracing,需要 TracerProvider 来创建 Tracer。当 OpenTelemetry SDK 应用程序 (opentelemetry) 启动时,它会启动并配置一个全局 TracerProvider。一旦 TracerProvider 启动,就会为每个加载的 OTP 应用程序创建一个 Tracer。
如果 TracerProvider 未成功创建(例如,opentelemetry 应用程序未启动或启动失败),则用于 tracing 的 OpenTelemetry API 将使用 no-op 实现,并且不会生成数据。
获取 Tracer
当 opentelemetry 应用程序启动时,会为每个 OTP 应用程序创建一个 Tracer。使用该 Tracer 的模块所属的 OTP 应用程序的名称和版本,就是该 Tracer 的名称和版本。如果使用 Tracer 的调用不在模块中,例如在使用交互式 shell 时,则会使用名称和版本为空的 Tracer。
通过 OTP 应用程序中模块的名称,可以查找创建的 Tracer 的记录。
opentelemetry:get_application_tracer(?MODULE)
:opentelemetry.get_application_tracer(__MODULE__)
这就是 Erlang 和 Elixir 宏如何自动获取 Span 的启动和更新的 Tracer,而无需您在每次调用时都传递该变量。
创建 Span
现在您已经初始化了 Tracer,您可以创建 Span 了。
?with_span(main, #{}, fun() ->
%% do work here.
%% when this function returns the Span ends
end).
require OpenTelemetry.Tracer
...
OpenTelemetry.Tracer.with_span :main do
# do work here
# when the block ends the Span ends
end
上面的代码示例显示了如何创建一个活动的 Span,这是最常见的 Span 类型。
创建嵌套 Span
parent_function() ->
?with_span(parent, #{}, fun child_function/0).
child_function() ->
%% this is the same process, so the span parent set as the active
%% span in the with_span call above will be the active span in this function
?with_span(child, #{},
fun() ->
%% do work here. when this function returns, child will complete.
end).
require OpenTelemetry.Tracer
def parent_function() do
OpenTelemetry.Tracer.with_span :parent do
child_function()
end
end
def child_function() do
# this is the same process, so the span :parent set as the active
# span in the with_span call above will be the active span in this function
OpenTelemetry.Tracer.with_span :child do
## do work here. when this function returns, :child will complete.
end
end
跨进程的 Span
前一节中的示例是在同一进程内具有父子关系的 Span,其中父 Span 在创建子 Span 时可用于进程字典。在跨进程(通过生成新进程或向现有进程发送消息)时,无法通过进程字典使用。相反,必须手动将上下文作为变量传递。
要跨进程传递 Span,我们需要启动一个不与特定进程关联的 Span。这可以通过宏 start_span 来完成。与 with_span 不同,start_span 宏不会将新 Span 设置为进程字典上下文中当前活动的 Span。
通过附加上下文并将新 Span 设置为进程中当前活动的 Span,可以在新进程中将 Span 连接为父 Span。应附加整个上下文,以免丢失其他遥测数据,例如 Baggage。
SpanCtx = ?start_span(child),
Ctx = otel_ctx:get_current(),
proc_lib:spawn_link(fun() ->
otel_ctx:attach(Ctx),
?set_current_span(SpanCtx),
%% do work here
?end_span(SpanCtx)
end),
span_ctx = OpenTelemetry.Tracer.start_span(:child)
ctx = OpenTelemetry.Ctx.get_current()
task = Task.async(fn ->
OpenTelemetry.Ctx.attach(ctx)
OpenTelemetry.Tracer.set_current_span(span_ctx)
# do work here
# end span here
OpenTelemetry.Tracer.end_span(span_ctx)
end)
_ = Task.await(task)
链接新 Span
一个 Span 可以创建零个或多个 Span Link,这些链接在因果关系上将其与另一个 Span 连接起来。Span Link 需要 Span Context 才能创建。
Parent = ?current_span_ctx,
proc_lib:spawn_link(fun() ->
%% a new process has a new context so the span created
%% by the following `with_span` will have no parent
Link = opentelemetry:link(Parent),
?with_span('other-process', #{links => [Link]},
fun() -> ok end)
end),
parent = OpenTelemetry.Tracer.current_span_ctx()
task = Task.async(fn ->
# a new process has a new context so the span created
# by the following `with_span` will have no parent
link = OpenTelemetry.link(parent)
Tracer.with_span :"my-task", %{links: [link]} do
:hello
end
end)
向 Span 添加属性
Attributes 允许您将键值对附加到 Span,以便它包含有关其跟踪的当前操作的更多信息。
以下示例显示了两种设置 Span 属性的方法:一种是在启动选项中设置属性,另一种是在 Span 操作体中使用 set_attributes 再次设置。
?with_span(my_span, #{attributes => [{'start-opts-attr', <<"start-opts-value">>}]},
fun() ->
?set_attributes([{'my-attribute', <<"my-value">>},
{another_attribute, <<"value-of-attribute">>}])
end)
Tracer.with_span :span_1, %{attributes: [{:"start-opts-attr", <<"start-opts-value">>}]} do
Tracer.set_attributes([{:"my-attributes", "my-value"},
{:another_attribute, "value-of-attributes"}])
end
语义属性
语义属性是由 OpenTelemetry Specification 定义的属性,用于为 HTTP 方法、状态码、用户代理等常见概念在多种语言、框架和运行时之间提供一组共享的属性键。这些属性键是从规范生成的,并包含在 opentelemetry_semantic_conventions 中。
例如,HTTP 客户端或服务器的 Instrument 需要包含语义属性,如 URL 的 scheme。
-include_lib("opentelemetry_semantic_conventions/include/trace.hrl").
?with_span(my_span, #{attributes => [{?HTTP_SCHEME, <<"https">>}]},
fun() ->
...
end)
alias OpenTelemetry.SemanticConventions.Trace, as: Trace
Tracer.with_span :span_1, %{attributes: [{Trace.http_scheme(), <<"https">>}]} do
end
添加事件
一个 Span Event 是 Span 上的一个可读消息,代表一个没有持续时间、只能由单个时间戳跟踪的离散事件。您可以将其视为一个原始日志。
?add_event(<<"Gonna try it">>),
%% Do the thing
?add_event(<<"Did it!">>),
Tracer.add_event("Gonna try it")
%% Do the thing
Tracer.add_event("Did it!")
事件也可以拥有自己的属性。
?add_event(<<"Process exited with reason">>, [{pid, Pid)}, {reason, Reason}]))
Tracer.add_event("Process exited with reason", pid: pid, reason: Reason)
设置 Span 状态
可以为 Span 设置 Status,通常用于指定 Span 未成功完成 - StatusCode.ERROR。在极少数情况下,您可以用 StatusCode.OK 覆盖错误状态,但不要对成功完成的 Span 设置 StatusCode.OK。
可以在 Span 结束前的任何时候设置状态
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
?set_status(?OTEL_STATUS_ERROR, <<"this is not ok">>)
Tracer.set_status(:error, "this is not ok")
指标
要生成 Metrics,必须将依赖项 opentelemetry_experimental_api 和 opentelemetry_experimental 添加到项目中。opentelemetry_experimental 的应用程序环境配置用于配置 MeterProvider,它在应用程序启动时初始化。在启动时,MeterProvider 会自动创建 Meters,然后使用适当的 Meter 来创建 Instrument,具体取决于您在代码中创建 Instrument 的位置。OpenTelemetry Erlang 目前支持以下 Instrument:
- Counter,一种支持非负增量的同步 Instrument。
- Asynchronous Counter,一种支持非负增量的异步 Instrument。
- Histogram,一种支持统计上有意义的任意值的同步 Instrument,例如直方图、摘要或百分位数。
- Asynchronous Gauge,一种支持非累加值的异步 Instrument,例如房间温度。
- UpDownCounter,一种支持增量和减量的同步 Instrument,例如活动请求数。
- Asynchronous UpDownCounter,一种支持增量和减量的异步 Instrument。
有关同步和异步仪器以及哪种类型最适合您的用例的更多信息,请参阅 Supplementary Guidelines (补充指南)。
初始化 Metrics
如果您正在 Instrument 一个库,请跳过此步骤。
要启用应用程序中的 Metrics,您需要有一个已初始化的 MeterProvider 和一个 Reader。这可以通过配置 opentelemetry_experimental 应用程序来完成。
{opentelemetry_experimental,
[{readers, [#{module => otel_metric_reader,
config => #{export_interval_ms => 1000,
exporter => {otel_exporter_metrics_otlp, #{}}}}]}]},
此配置告诉应用程序创建一个具有单个 Reader 的 MetricProvider。Reader 每秒将数据导出到 OTLP 接收器(例如 Collector),默认情况下位于 localhost:4318。要更改端点,请向映射添加 endpoints => ["<host>:<port>"] 并配置使用的协议为 protocol => http_protobuf | grpc。
使用 exporter => {otel_exporter_metrics_console, #{}} 将 Metrics 输出到控制台。
获取 Meter
Instrument 是使用 Meter 创建的。手动获取 Meter 不是必需的,当使用 Instrument 创建宏时,会自动完成。
同步和异步 instruments
使用 Counter
Counter 可用于测量非负的、递增的值。
可以使用宏 ?create_counter 创建 Counter。
?create_counter(my_fun_counter, #{description => ~"Number of times this function
is called."})
要增加 Counter,请使用 ?counter_add 宏,传入 Instrument 名称、增加值和属性映射。
?counter_add(my_fun_counter, 1, #{}),
使用 UpDown Counter
UpDown Counter 可以增减,允许您观察一个向上或向下变化的累积值。
例如,以下是报告某些集合项目数量的方法。
create_items_counter() ->
?create_counter('items.counter', #{description => ~"Number of items",
unit => '{items}'})
add_item(Item) ->
...
?updown_counter_add('items.counter', 1),
remove_item(Item) ->
...
?updown_counter_add('items.counter', -1),
使用 Histogram
Histogram 用于测量随时间推移的值的分布。
?create_histogram('task.duration', #{description => ~"Duration of a task",
unit => 's'}),
然后使用宏 ?histogram_record 来记录一个测量值。
{Microseconds, Result} = timer:tc(TaskFun),
?histogram_record('task.duration', Microseconds),
使用 Observable Counter
Observable Counter 可用于测量一个累加的、非负的、单调递增的值。
例如,以下是如何报告 Erlang 节点启动以来的时间。
?create_observable_counter('uptime', fun(_Args) ->
Uptime = erlang:convert_time_unit(erlang:monotonic_time() - erlang:system_info(start_time), native, seconds),
[{Uptime, #{}}]
end,
[],
#{description => ~"The duration since the node started.",
unit => 's'}),
使用 Observable UpDown Counter
Observable UpDown Counter 可以增减,允许您测量一个累加的、非负的、非单调递增的累积值。
例如,Web 服务器的活动 HTTP 连接数。
?create_observable_updown_counter('http.server.active_requests', fun(_Args) ->
ActiveRequests = ....
[{ActiveRequests, #{}}]
end,
[],
#{description => ~"Number of active HTTP server requests.",
unit => {request}'}),
使用 Observable Gauge
Observable Gauge 应用于测量非累加值。
例如,以下是如何报告节点上 ETS 表的内存使用情况。
?create_observable_gauge('memory.ets', fun(_Args) ->
EtsMemory = erlang:memory(ets),
[{EtsMemory, #{}}]
end,
[],
#{description => ~"Memory used by ETS tables.",
unit => 'By'}),
添加属性
属性可以作为映射添加到任何测量中,位于记录宏的最后一个位置。
?updown_counter_add('items.counter', 1, #{~"key-1" => ~"value-1"}),
注册 View
View 为 SDK 用户提供了自定义 SDK 生成的指标输出的灵活性。您可以自定义要处理或忽略的指标仪器。您还可以自定义聚合以及要在指标上报告的属性。
每个 Instrument 都有一个默认的 View,它保留原始名称、描述和属性,并且有一个基于 Instrument 类型的默认聚合。当注册的 View 匹配 Instrument 时,默认 View 将被注册的 View 替换。其他匹配 Instrument 的注册 View 是累加的,并导致 Instrument 导出多个 Metrics。
以下是如何创建将 latency Instrument 重命名为 request.latency 的 View。
{opentelemetry_experimental,
[...
{views, [#{name => request.latency',
selector => #{instrument_name => 'latency'}}]}
]},
或者,如果您想要一个表示延迟的直方图。
{opentelemetry_experimental,
[...
{views, [#{selector => #{instrument_name => 'latency'},
aggregation_module => otel_aggregation_histogram_explicit}]}
]},
SDK 会在导出 Metrics 之前过滤 Metrics 和属性。例如,您可以使用 View 来减少高基数 Metrics 的内存使用量,或删除可能包含敏感数据的属性。
以下是如何创建一个删除延迟的 View。
{opentelemetry_experimental,
[...
{views, [#{selector => #{instrument_name => 'latency'},
aggregation_module => otel_aggregation_drop}]}
]},
可以使用通配符来匹配所有 Instrument。
{opentelemetry_experimental,
[...
{views, [#{selector => #{instrument_name => '*'},
aggregation_module => otel_aggregation_drop}]}
]},
由于 View 是累加的,因此任何附加的 View 都意味着可以导出特定的 Metrics,而所有其他没有匹配项(除了通配符)的 Metrics 都将被删除。
日志
日志 API 位于 opentelemetry-erlang 存储库的 apps/opentelemetry_experimental_api 中,目前不稳定,文档待定。
下一步
您还需要配置适当的 Exporter,以便 导出您的遥测数据到一个或多个遥测后端。