Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions apps/opentelemetry_api/lib/open_telemetry/attributes.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
defmodule OpenTelemetry.Attributes do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an otel_attributes module that something named OpenTelemetry.Attributes should be the Elixir equivalent of. While that isn't needed I think this should be named different anyway. Maybe CodeAttributes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I can update that.

@moduledoc """
This module contains utility functions for span attributes.

Elixir has built in variables like `__ENV__` and `__CALLER__` that can be used to generate
span attributes like `code.function`, `code.lineno`, and `code.namespace` either during runtime
or compile time. This module provides a function to generate these attributes from a `t:Macro.Env`
struct.

For more information, view the [OpenTelemetry Semantic Conventions](OSC).

[OSC]: https://opentelemetry.io/docs/specs/semconv/attributes-registry
"""

@code_filepath :"code.filepath"
@code_function :"code.function"
@code_lineno :"code.lineno"
@code_namespace :"code.namespace"

@doc """
A function used to generate attributes from a `t:Macro.Env` struct.

This function is used to generate span attributes like `code.function`, `code.lineno`, and
`code.namespace` from a `__CALLER__` variable during compile time or a `__ENV__` variable
run time.

## Usage

# During run time
def my_function() do
OpenTelemetry.Attributes.from_macro_env(__ENV__)
end

iex> my_function()
%{code_function: "my_function/0", ...}

# During compile time in a macro
defmacro my_macro() do
attributes =
__CALLER__
|> OpenTelemetry.Attributes.from_macro_env()
|> Macro.escape()

quote do
unquote(attributes)
end
end

def my_other_function() do
my_macro()
end

iex> my_macro()
%{code_function: "my_other_function/0", ...}

"""
@spec from_macro_env(Macro.Env.t()) :: OpenTelemetry.attributes_map()
def from_macro_env(%Macro.Env{} = env) do
function_arty =
case env.function do
{func_name, func_arity} -> "#{func_name}/#{func_arity}"
nil -> nil
end

%{
@code_function => function_arty,
@code_namespace => to_string(env.module),
@code_filepath => env.file,
@code_lineno => env.line
}
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|> Map.new()
end
end
45 changes: 39 additions & 6 deletions apps/opentelemetry_api/lib/open_telemetry/tracer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ defmodule OpenTelemetry.Tracer do
The current active Span is used as the parent of the created Span.
"""
defmacro start_span(name, opts \\ quote(do: %{})) do
quote bind_quoted: [name: name, start_opts: opts] do
attributes =
__CALLER__
|> OpenTelemetry.Attributes.from_macro_env()
|> Macro.escape()

quote bind_quoted: [name: name, start_opts: opts, attributes: attributes] do
:otel_tracer.start_span(
:opentelemetry.get_application_tracer(__MODULE__),
name,
Map.new(start_opts)
OpenTelemetry.Tracer.merge_start_opts(start_opts, attributes)
)
end
end
Expand All @@ -37,12 +42,17 @@ defmodule OpenTelemetry.Tracer do
The current active Span is used as the parent of the created Span.
"""
defmacro start_span(ctx, name, opts) do
quote bind_quoted: [ctx: ctx, name: name, start_opts: opts] do
attributes =
__CALLER__
|> OpenTelemetry.Attributes.from_macro_env()
|> Macro.escape()

quote bind_quoted: [ctx: ctx, name: name, start_opts: opts, attributes: attributes] do
:otel_tracer.start_span(
ctx,
:opentelemetry.get_application_tracer(__MODULE__),
name,
Map.new(start_opts)
OpenTelemetry.Tracer.merge_start_opts(start_opts, attributes)
)
end
end
Expand Down Expand Up @@ -70,11 +80,16 @@ defmodule OpenTelemetry.Tracer do
See `start_span/2` and `end_span/0`.
"""
defmacro with_span(name, start_opts \\ quote(do: %{}), do: block) do
attributes =
__CALLER__
|> OpenTelemetry.Attributes.from_macro_env()
|> Macro.escape()

quote do
:otel_tracer.with_span(
:opentelemetry.get_application_tracer(__MODULE__),
unquote(name),
Map.new(unquote(start_opts)),
OpenTelemetry.Tracer.merge_start_opts(unquote(start_opts), unquote(attributes)),
fn _ -> unquote(block) end
)
end
Expand All @@ -88,12 +103,17 @@ defmodule OpenTelemetry.Tracer do
See `start_span/2` and `end_span/0`.
"""
defmacro with_span(ctx, name, start_opts, do: block) do
attributes =
__CALLER__
|> OpenTelemetry.Attributes.from_macro_env()
|> Macro.escape()

quote do
:otel_tracer.with_span(
unquote(ctx),
:opentelemetry.get_application_tracer(__MODULE__),
unquote(name),
Map.new(unquote(start_opts)),
OpenTelemetry.Tracer.merge_start_opts(unquote(start_opts), unquote(attributes)),
fn _ -> unquote(block) end
)
end
Expand Down Expand Up @@ -221,4 +241,17 @@ defmodule OpenTelemetry.Tracer do
def update_name(name) do
:otel_span.update_name(:otel_tracer.current_span_ctx(), name)
end

@doc false
@spec merge_start_opts(OpenTelemetry.Span.start_opts(), OpenTelemetry.attributes_map()) ::
OpenTelemetry.Span.start_opts()
def merge_start_opts(start_opts, builtin_attributes) do
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this public but undocumented. Not sure if we want to document and make it part of the public API

start_opts
|> Map.new()
|> Map.update(:attributes, builtin_attributes, fn specified_attributes ->
specified_attributes
|> Map.new(fn {k, v} -> {to_string(k), v} end)
|> Map.merge(builtin_attributes)
end)
end
end
9 changes: 9 additions & 0 deletions apps/opentelemetry_api/test/open_telemetry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,13 @@ defmodule OpenTelemetryTest do
Ctx.detach(token)
assert %{"a" => {"b", []}} = Baggage.get_all()
end

test "from_macro_env/1" do
attributes = OpenTelemetry.Attributes.from_macro_env(__ENV__)

assert attributes[:"code.filepath"] =~ "open_telemetry_test.exs"
assert attributes[:"code.function"] =~ "from_macro_env/1"
assert attributes[:"code.lineno"] == 149
assert attributes[:"code.namespace"] == "Elixir.OpenTelemetryTest"
end
end