Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add source code attributes to Elixir tracer macros #550

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
38 changes: 20 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Add source code attributes to Elixir tracer macros](https://github.com/open-telemetry/opentelemetry-erlang/pull/550)

## SDK 1.3.0 - 2023-03-21

### Fixes
Expand Down Expand Up @@ -57,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [Old experimental metrics
api](https://github.com/open-telemetry/opentelemetry-erlang/pull/479)

## Experimental SDK 0.1.0 - 2022-10-19

### Added
Expand All @@ -70,12 +72,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Handler](https://github.com/open-telemetry/opentelemetry-erlang/pull/468)
- [OTLP Metrics
exporting](https://github.com/open-telemetry/opentelemetry-erlang/pull/456)

### Fixed

- [Fix OTLP Protocol allowed
values](https://github.com/open-telemetry/opentelemetry-erlang/pull/420)
values](https://github.com/open-telemetry/opentelemetry-erlang/pull/420)

## API 1.1.1 - 2022-10-19

### Fixed
Expand All @@ -88,22 +90,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [Replaced telemetry_library record with library info as resource
attributes](https://github.com/open-telemetry/opentelemetry-erlang/pull/457)

## Exporter 1.2.1 - 2022-09-08

### Fixes

- Gradualizer cleanup

## Exporter 1.2.0 - 2022-09-08

### Fixes

- [Fix InstrumentationScope encoding in OTLP
protobufs](https://github.com/open-telemetry/opentelemetry-erlang/pull/451)

### Added

- [Exporter now respects top-level `ssl_options` application environment value
and handles endpoint parse errors](https://github.com/open-telemetry/opentelemetry-erlang/pull/442)

Expand Down Expand Up @@ -141,7 +143,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
If you were using the record directly, please use the function
`opentelemetry:instrumentation_scope/3` or
`opentelemetry:instrumentation_library/3` to create an `instrumentation_scope`
record.
record.

## SDK 1.1.0 - 2022-8-31

Expand Down Expand Up @@ -273,7 +275,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
ordering issues](https://github.com/open-telemetry/opentelemetry-erlang/pull/338)
- [elixir span docs: fix reference to attributes
type](https://github.com/open-telemetry/opentelemetry-erlang/pull/336)

### [API]

#### Fixed
Expand Down Expand Up @@ -302,7 +304,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `add_event` function and macros fixed to accept both a map of attributes or a
list.

## [API 1.0.0-rc.4] - 2021-12-25

### Added
Expand All @@ -314,9 +316,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Configurable limits for the number of Attributes, Events and Links allowed in
a Span, an Event or a Link -- defaults to 128. The length of each Attribute's
value can also be limited but has a default of infinity.

Environment variables added to configure the limits:

- `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`: Limit on number of Attributes on a Span.
- `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT`: Limit length of Attribute values.
- `OTEL_SPAN_EVENT_COUNT_LIMIT`: Limit number of Events on a Span.
Expand Down Expand Up @@ -358,7 +360,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `otel_propagator_trace_context:extract/1` no longer crashes on `undefined`
header values

## [API 1.0.0-rc.3.1] - 2021-10-12

##### Fixed
Expand All @@ -380,16 +382,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
application vsn `0.1.0` and also manually register a Tracer named `mod_a` with
version `1.1.1` then use of macros like `?with_span` will use the `app_a`
version `0.1.0` Named Tracer and manual use of a Named Tracer like:

```
Tracer = opentelemetry:get_tracer(mod_a),
otel_tracer:with_span(Tracer, span_name, #{}, fun() -> ... end),
```

will use Named Tracer `mod_a` with version `1.1.1`. In previous versions after
registering a Tracer named `mod_a` it would override the `mod_a` pointing to
the `app_a` Tracer.

Additionally, manual registration of a Named Tracer with the name `app_a` will
not override the application registered Tracer of `app_a`.

Expand Down Expand Up @@ -421,7 +423,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
{text_map_injectors, [trace_context, baggage]},
{text_map_extractors, [b3multi, trace_context, baggage]}
```

##### Fixed

- `b3` propagator renamed `b3multi` to properly convey it is the version of the
Expand Down
76 changes: 71 additions & 5 deletions apps/opentelemetry_api/lib/open_telemetry/tracer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,48 @@ defmodule OpenTelemetry.Tracer do
end
"""

require OpenTelemetry.SemanticConventions.Trace
alias OpenTelemetry.SemanticConventions.Trace, as: Conventions

defmacrop code_attributes do
quote do
code_function =
case __CALLER__.function do
{func_name, func_arity} -> "#{func_name}/#{func_arity}"
nil -> nil
end

source_attrs = %{
Conventions.code_function() => code_function,
Conventions.code_namespace() => __CALLER__.module,
Conventions.code_filepath() => __CALLER__.file,
Conventions.code_lineno() => __CALLER__.line
}
end
end

@doc """
Starts a new span and does not make it the current active span of the current process.

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
code_attrs = code_attributes() |> Macro.escape()
thread_id_key = Conventions.thread_id()

quote bind_quoted: [
name: name,
start_opts: opts,
code_attrs: code_attrs
] do
start_opts =
Map.new(start_opts)
|> Map.update(:attributes, code_attrs, &Map.merge(&1, code_attrs))

:otel_tracer.start_span(
:opentelemetry.get_application_tracer(__MODULE__),
name,
Map.new(start_opts)
start_opts
)
end
end
Expand All @@ -37,7 +68,22 @@ 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
code_attrs = code_attributes() |> Macro.escape()
thread_id_key = Conventions.thread_id()

quote bind_quoted: [
ctx: ctx,
name: name,
start_opts: opts,
code_attrs: code_attrs,
thread_id_key: thread_id_key
] do
code_attrs = Map.put(code_attrs, thread_id_key, :erlang.system_info(:scheduler_id))

start_opts =
Map.new(start_opts)
|> Map.update(:attributes, code_attrs, &Map.merge(&1, code_attrs))

:otel_tracer.start_span(
ctx,
:opentelemetry.get_application_tracer(__MODULE__),
Expand Down Expand Up @@ -70,11 +116,21 @@ defmodule OpenTelemetry.Tracer do
See `start_span/2` and `end_span/0`.
"""
defmacro with_span(name, start_opts \\ quote(do: %{}), do: block) do
code_attrs = code_attributes() |> Macro.escape()
thread_id_key = Conventions.thread_id()

quote do
code_attrs =
Map.put(unquote(code_attrs), unquote(thread_id_key), :erlang.system_info(:scheduler_id))

start_opts =
Map.new(unquote(start_opts))
|> Map.update(:attributes, code_attrs, &Map.merge(&1, code_attrs))

:otel_tracer.with_span(
:opentelemetry.get_application_tracer(__MODULE__),
unquote(name),
Map.new(unquote(start_opts)),
start_opts,
fn _ -> unquote(block) end
)
end
Expand All @@ -88,12 +144,22 @@ defmodule OpenTelemetry.Tracer do
See `start_span/2` and `end_span/0`.
"""
defmacro with_span(ctx, name, start_opts, do: block) do
code_attrs = code_attributes() |> Macro.escape()
thread_id_key = Conventions.thread_id()

quote do
code_attrs =
Map.put(unquote(code_attrs), unquote(thread_id_key), :erlang.system_info(:scheduler_id))
Copy link
Member

Choose a reason for hiding this comment

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

Looks like thread_id is still in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad, I haven't had a chance to properly sit down with this and get any context back. I promise I will get to that soon.


start_opts =
Map.new(unquote(start_opts))
|> Map.update(:attributes, code_attrs, &Map.merge(&1, code_attrs))

:otel_tracer.with_span(
unquote(ctx),
:opentelemetry.get_application_tracer(__MODULE__),
unquote(name),
Map.new(unquote(start_opts)),
start_opts,
fn _ -> unquote(block) end
)
end
Expand Down
87 changes: 52 additions & 35 deletions test/otel_tests.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ defmodule OtelTests do
@fields Record.extract(:span_ctx, from_lib: "opentelemetry_api/include/opentelemetry.hrl")
Record.defrecordp(:span_ctx, @fields)

@fields Record.extract(:attributes, from_lib: "opentelemetry/src/otel_attributes.erl")
Record.defrecordp(:attributes, @fields)

setup do
Application.load(:opentelemetry)

Expand All @@ -36,14 +39,18 @@ defmodule OtelTests do
Tracer.set_attributes([{"attr-2", "value-2"}])
end

attributes =
:otel_attributes.new([{"attr-1", "value-1"}, {"attr-2", "value-2"}], 128, :infinity)
assert_receive {:span, span_record}

assert_receive {:span,
span(
name: "span-1",
attributes: ^attributes
)}
span(name: "span-1", attributes: attribute_record) = span_record
attrs = attributes(attribute_record, :map)

assert Map.get(attrs, :"code.filepath") |> String.ends_with?("otel_tests.exs")
assert Map.get(attrs, :"code.function") |> String.starts_with?("test ")
assert Map.get(attrs, :"code.lineno") |> is_integer()
assert Map.get(attrs, :"code.namespace") == __MODULE__
assert Map.get(attrs, :"thread.id") == :erlang.system_info(:scheduler_id)
assert Map.get(attrs, "attr-1") == "value-1"
assert Map.get(attrs, "attr-2") == "value-2"
end

test "use Tracer to start a Span as currently active with an explicit parent" do
Expand All @@ -57,23 +64,21 @@ defmodule OtelTests do

span_ctx(span_id: parent_span_id) = Span.end_span(s1)

attributes = :otel_attributes.new([], 128, :infinity)
assert_receive {:span, span(name: "span-1")}
assert_receive {:span, span2}

assert_receive {:span,
span(
name: "span-1",
attributes: ^attributes
)}
assert span(span2, :name) == "span-2"
assert span(span2, :parent_span_id) == parent_span_id

attributes =
:otel_attributes.new([{"attr-1", "value-1"}, {"attr-2", "value-2"}], 128, :infinity)
attributes(map: attrs) = span(span2, :attributes)

assert_receive {:span,
span(
name: "span-2",
parent_span_id: ^parent_span_id,
attributes: ^attributes
)}
assert Map.get(attrs, :"code.filepath") |> String.ends_with?("otel_tests.exs")
assert Map.get(attrs, :"code.function") |> String.starts_with?("test ")
assert Map.get(attrs, :"code.lineno") |> is_integer()
assert Map.get(attrs, :"code.namespace") == __MODULE__
assert Map.get(attrs, :"thread.id") == :erlang.system_info(:scheduler_id)
assert Map.get(attrs, "attr-1") == "value-1"
assert Map.get(attrs, "attr-2") == "value-2"
end

test "use Span to set attributes" do
Expand All @@ -83,14 +88,19 @@ defmodule OtelTests do

assert span_ctx() = Span.end_span(s)

attributes =
:otel_attributes.new([{"attr-1", "value-1"}, {"attr-2", "value-2"}], 128, :infinity)
assert_receive {:span, span2}

assert_receive {:span,
span(
name: "span-2",
attributes: ^attributes
)}
assert span(span2, :name) == "span-2"

attributes(map: attrs) = span(span2, :attributes)

assert Map.get(attrs, :"code.filepath") |> String.ends_with?("otel_tests.exs")
assert Map.get(attrs, :"code.function") |> String.starts_with?("test ")
assert Map.get(attrs, :"code.lineno") |> is_integer()
assert Map.get(attrs, :"code.namespace") == __MODULE__
assert Map.get(attrs, :"thread.id") == :erlang.system_info(:scheduler_id)
assert Map.get(attrs, "attr-1") == "value-1"
assert Map.get(attrs, "attr-2") == "value-2"
end

test "create child Span in Task" do
Expand Down Expand Up @@ -197,16 +207,23 @@ defmodule OtelTests do
assert span_ctx() = Span.end_span(s2)
assert span_ctx() = Span.end_span(s3)

attributes =
:otel_attributes.new([{"attr-1", "value-1"}, {"attr-2", "value-2"}], 128, :infinity)

assert_receive {:span,
span(
name: "span-1",
parent_span_id: :undefined,
attributes: ^attributes
attributes: attribute_record
)}

attrs = attributes(attribute_record, :map)

assert Map.get(attrs, :"code.filepath") |> String.ends_with?("otel_tests.exs")
assert Map.get(attrs, :"code.function") |> String.starts_with?("test ")
assert Map.get(attrs, :"code.lineno") |> is_integer()
assert Map.get(attrs, :"code.namespace") == __MODULE__
assert Map.get(attrs, :"thread.id") == :erlang.system_info(:scheduler_id)
assert Map.get(attrs, "attr-1") == "value-1"
assert Map.get(attrs, "attr-2") == "value-2"

assert_receive {:span,
span(
name: "span-2",
Expand Down Expand Up @@ -241,9 +258,9 @@ defmodule OtelTests do
attributes =
:otel_attributes.new(
[
{"exception.type", "Elixir.RuntimeError"},
{"exception.message", "my error message"},
{"exception.stacktrace", stacktrace}
{:"exception.type", "Elixir.RuntimeError"},
{:"exception.message", "my error message"},
{:"exception.stacktrace", stacktrace}
],
128,
:infinity
Expand Down