diff --git a/apps/opentelemetry_api/lib/open_telemetry/span.ex b/apps/opentelemetry_api/lib/open_telemetry/span.ex index 91a885cc0..0fdceff13 100644 --- a/apps/opentelemetry_api/lib/open_telemetry/span.ex +++ b/apps/opentelemetry_api/lib/open_telemetry/span.ex @@ -77,6 +77,28 @@ defmodule OpenTelemetry.Span do @spec add_events(OpenTelemetry.span_ctx(), [OpenTelemetry.event()]) :: boolean() defdelegate add_events(span_ctx, events), to: :otel_span + @doc """ + Record an exception as an event, following the semantics convetions for exceptions. + + If trace is not provided, the stacktrace is retrieved from `Process.info/2` + """ + @spec record_exception(OpenTelemetry.span_ctx(), Exception.t()) :: boolean() + def record_exception(span_ctx, exception, trace \\ nil, attributes \\ []) + + def record_exception(span_ctx, exception, trace, attributes) when is_exception(exception) do + exception_type = to_string(exception.__struct__) + + exception_attributes = [ + {"exception.type", exception_type}, + {"exception.message", Exception.message(exception)}, + {"exception.stacktrace", Exception.format_stacktrace(trace)} + ] + + add_event(span_ctx, "exception", exception_attributes ++ attributes) + end + + def record_exception(_, _, _, _), do: false + @doc """ Sets the Status of the currently active Span. diff --git a/apps/opentelemetry_api/mix.exs b/apps/opentelemetry_api/mix.exs index 4aa1ca800..717f78cc7 100644 --- a/apps/opentelemetry_api/mix.exs +++ b/apps/opentelemetry_api/mix.exs @@ -9,7 +9,7 @@ defmodule OpenTelemetry.MixProject do app: app, version: version(Keyword.fetch!(desc, :vsn)), description: to_string(Keyword.fetch!(desc, :description)), - elixir: "~> 1.8", + elixir: "~> 1.11", start_permanent: Mix.env() == :prod, # We should never have dependencies deps: deps(Keyword.fetch!(config, :deps)), diff --git a/test/otel_tests.exs b/test/otel_tests.exs index ad8078fcf..bf4621e6e 100644 --- a/test/otel_tests.exs +++ b/test/otel_tests.exs @@ -7,6 +7,8 @@ defmodule OtelTests do require Record @fields Record.extract(:span, from_lib: "opentelemetry/include/otel_span.hrl") Record.defrecordp(:span, @fields) + @event_fields Record.extract(:event, from_lib: "opentelemetry_api/include/opentelemetry.hrl") + Record.defrecordp(:event, @event_fields) test "use Tracer to set attributes" do :otel_batch_processor.set_exporter(:otel_exporter_pid, self()) @@ -34,4 +36,39 @@ defmodule OtelTests do {"attr-2", "value-2"}])} end + test "Span.record_exception/4 should return false if passed an invalid exception" do + Tracer.with_span "span-3" do + refute OpenTelemetry.Span.record_exception(Tracer.current_span_ctx(), :not_an_exception) + end + end + + test "Span.record_exception/4 should add an exception event to the span" do + :otel_batch_processor.set_exporter(:otel_exporter_pid, self()) + s = Tracer.start_span("span-4") + + try do + raise RuntimeError, "my error message" + rescue + ex -> + assert Span.record_exception(s, ex, __STACKTRACE__) + assert Span.end_span(s) + + stacktrace = Exception.format_stacktrace(__STACKTRACE__) + + assert_receive {:span, + span( + name: "span-4", + events: [ + event( + name: "exception", + attributes: [ + {"exception.type", "Elixir.RuntimeError"}, + {"exception.message", "my error message"}, + {"exception.stacktrace", ^stacktrace} + ] + ) + ] + )} + end + end end