手册

适用于 OpenTelemetry Erlang/Elixir 的手动仪表化

Manual instrumentation is the act of adding observability code to an app yourself.

If you’re instrumenting an app, you need to use the OpenTelemetry SDK for your language. You’ll then use the SDK to initialize OpenTelemetry and the API to instrument your code. This will emit telemetry from your app, and any library you installed that also comes with instrumentation.

If you’re instrumenting a library, only install the OpenTelemetry API package for your language. Your library will not emit telemetry on its own. It will only emit telemetry when it is part of an app that uses the OpenTelemetry SDK. For more on instrumenting libraries, see Libraries.

For more information about the OpenTelemetry API and SDK, see the specification.

设置

将以下依赖项添加到您的项目中:

  • opentelemetry_api:包含您用于仪表化代码的接口,如 Tracer.with_spanTracer.set_attribute
  • opentelemetry:包含实现 API 中定义的接口的 SDK。如果没有该依赖项,API 中的所有函数将成为无操作。
# mix.exs
def deps do
  [
    {:opentelemetry, "~> 1.3"},
    {:opentelemetry_api, "~> 1.2"},
  ]
end

跟踪

初始化跟踪

要开始 跟踪,需要一个用于创建 TracerTracerProvider。当 OpenTelemetry SDK Application (opentelemetry) 启动时,它会启动并配置一个全局 TracerProvider。一旦 TracerProvider 启动,将为每个已加载的 OTP 应用程序创建一个 Tracer

如果没有成功创建 TracerProvider(例如,未启动或启动失败 opentelemetry 应用程序),跟踪的 OpenTelemetry APIs 将使用无操作实现,不会生成数据。

获取 Tracer

opentelemetry Application 启动时,为每个 OTP 应用程序创建一个 Tracer。使用 Tracer 的模块的名称和版本与该模块所在的 OTP 应用程序的名称和版本相同。如果在不是模块的调用 Tracer 的情况下(例如在使用交互式 shell 时),将使用具有空白名称和版本的 Tracer

可以通过 OTP 应用程序的模块名称查找创建的 Tracer 记录:

opentelemetry:get_application_tracer(?MODULE)
:opentelemetry.get_application_tracer(__MODULE__)

这是 Erlang 和 Elixir 宏如何自动获取 SpanTracer 的示例,而无需在每个调用中传递变量。

创建 Spans

现在,您已初始化了 Tracerr,可以创建 Spans 了。

?with_span(main, #{}, fun() ->
                        %% 这里执行工作。
                        %% 当此函数返回时,Span 结束。
                      end).
require OpenTelemetry.Tracer

...

OpenTelemetry.Tracer.with_span :main do
  # 在此处执行工作
  # 块结束时,Span 结束
end

上面的代码示例展示了如何创建一个活动的 Span,这是创建的最常见的 Span 类型之一。

创建嵌套的 Spans

parent_function() ->
    ?with_span(parent, #{}, fun child_function/0).

child_function() ->
    %% 这是相同的进程,因此在上面的 with_span 调用中设置为活动 Span 的父 Span
    ?with_span(child, #{},
               fun() ->
                   %% 在这里执行工作。当此函数返回时,child 将完成。
               end).
require OpenTelemetry.Tracer

def parent_function() do
    OpenTelemetry.Tracer.with_span :parent do
        child_function()
    end
end

def child_function() do
    # 这是相同的进程,因此在上面的 with_span 调用中设置为活动 Span 的父 Span
    OpenTelemetry.Tracer.with_span :child do
        ## 在这里执行工作。当此函数返回时,:child 将完成。
    end
end

在单独的进程中的 Spans

前一节中的示例是具有父-子关系的 Spans,父 Span 在创建子 Span 时在进程字典中可用。在跨进程时,无论是生成新进程还是向现有进程发送消息,都无法以这种方式使用进程字典。相反,必须手动将上下文作为变量传递。

要将 Spans 传递给其他进程,我们需要启动一个与特定进程无关的 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),

                        %% 在这里执行工作

                        ?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)
                      # 在这里执行工作

                      # 在这里结束 span
                      OpenTelemetry.Tracer.end_span(span_ctx)
                  end)

_ = Task.await(task)

链接新 Span

可以为一个 Span 创建零个或多个 Span 链接,这些链接使其与另一个 Span 以因果关系相连。Link 需要一个 Span 上下文来创建。

Parent = ?current_span_ctx,
proc_lib:spawn_link(fun() ->
                       %% 新进程具有新的上下文,因此以下 `with_span` 创建的 Span 将没有父 Span
                       Link = opentelemetry:link(Parent),
                       ?with_span('other-process', #{links => [Link]},
                                  fun() -> ok end)
                    end),
parent = OpenTelemetry.Tracer.current_span_ctx()
task = Task.async(fn ->
                    # 新进程具有新的上下文,因此以下 `with_span` 创建的 Span 将没有父 Span
                    link = OpenTelemetry.link(parent)
                    Tracer.with_span :"my-task", %{links: [link]} do
                      :hello
                    end
                 end)

向 Span 添加属性

Attributes 允许您将键/值对附加到 Span 上,以便传递有关正在跟踪的当前操作的更多信息。

以下示例展示了两种在 Span 操作的主体中设置属性的方法,一种是通过设置启动选项中的属性,另一种是通过 set_attributes 在 Span 主体中设置属性:

?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 规范 定义的属性,旨在为常见概念(如 HTTP 方法、状态码、用户代理等)提供跨多种语言、框架和运行时的共享属性键。这些属性键是通过规范生成并提供在 opentelemetry_semantic_conventions 中。

例如,一个用于 HTTP 客户端或服务器的仪表化工具需要包含 URL 的方案这样的语义属性:

-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 事件 是一个人类可读的消息,表示一个无持续时间的离散事件,可以通过单个时间戳进行跟踪。你可以将它看作是一个原始日志。

?add_event(<<"Gonna try it">>),

%% 进行操作

?add_event(<<"Did it!">>),
Tracer.add_event("Gonna try it")

%% 进行操作

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,但请勿将 StatusCode.OK 设置为成功完成的 Spans 的状态。

状态可以在 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")

指标

指标 API 位于 opentelemetry-erlang 仓库的 apps/opentelemetry_experimental_api 中,目前不稳定,文档待定。

日志

日志 API 位于 opentelemetry-erlang 仓库的 apps/opentelemetry_experimental_api 中,目前不稳定,文档待定。

下一步

您还需要配置适当的导出器将您的遥测数据导出到一个或多个遥测后端。请参阅这里

最后修改 December 10, 2023: translate (a4350d6e)