Skip to content

Commit

Permalink
feat: setup code attributes in Elixir macros
Browse files Browse the repository at this point in the history
  • Loading branch information
btkostner committed Dec 5, 2024
1 parent 9f7affe commit 7f02834
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 6 deletions.
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
@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

Check warning on line 62 in apps/opentelemetry_api/lib/open_telemetry/attributes.ex

View check run for this annotation

Codecov / codecov/patch

apps/opentelemetry_api/lib/open_telemetry/attributes.ex#L62

Added line #L62 was not covered by tests
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 =

Check warning on line 45 in apps/opentelemetry_api/lib/open_telemetry/tracer.ex

View check run for this annotation

Codecov / codecov/patch

apps/opentelemetry_api/lib/open_telemetry/tracer.ex#L45

Added line #L45 was not covered by tests
__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 =

Check warning on line 106 in apps/opentelemetry_api/lib/open_telemetry/tracer.ex

View check run for this annotation

Codecov / codecov/patch

apps/opentelemetry_api/lib/open_telemetry/tracer.ex#L106

Added line #L106 was not covered by tests
__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
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

0 comments on commit 7f02834

Please sign in to comment.