diff --git a/CHANGELOG.md b/CHANGELOG.md index 16200aaa..f883bfa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 @@ -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`. @@ -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 diff --git a/apps/opentelemetry_api/lib/open_telemetry/tracer.ex b/apps/opentelemetry_api/lib/open_telemetry/tracer.ex index e67ee33e..02cb0bb2 100644 --- a/apps/opentelemetry_api/lib/open_telemetry/tracer.ex +++ b/apps/opentelemetry_api/lib/open_telemetry/tracer.ex @@ -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 @@ -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__), @@ -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 @@ -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)) + + 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 diff --git a/test/otel_tests.exs b/test/otel_tests.exs index 07277812..9016e5b0 100644 --- a/test/otel_tests.exs +++ b/test/otel_tests.exs @@ -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) @@ -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 @@ -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 @@ -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 @@ -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", @@ -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