From 31fb6f51bc0423b40ea777d0029e1d5754b7bf80 Mon Sep 17 00:00:00 2001 From: Derek Kraan Date: Thu, 15 Sep 2022 11:29:35 +0200 Subject: [PATCH] Add OpentelemetryMonitor to contrib --- .../opentelemetry_monitor/.formatter.exs | 4 ++ .../opentelemetry_monitor/.gitignore | 26 +++++++++ .../opentelemetry_monitor/README.md | 39 +++++++++++++ .../lib/opentelemetry_monitor.ex | 56 +++++++++++++++++++ instrumentation/opentelemetry_monitor/mix.exs | 44 +++++++++++++++ .../test/opentelemetry_monitor_test.exs | 4 ++ .../test/test_helper.exs | 1 + 7 files changed, 174 insertions(+) create mode 100644 instrumentation/opentelemetry_monitor/.formatter.exs create mode 100644 instrumentation/opentelemetry_monitor/.gitignore create mode 100644 instrumentation/opentelemetry_monitor/README.md create mode 100644 instrumentation/opentelemetry_monitor/lib/opentelemetry_monitor.ex create mode 100644 instrumentation/opentelemetry_monitor/mix.exs create mode 100644 instrumentation/opentelemetry_monitor/test/opentelemetry_monitor_test.exs create mode 100644 instrumentation/opentelemetry_monitor/test/test_helper.exs diff --git a/instrumentation/opentelemetry_monitor/.formatter.exs b/instrumentation/opentelemetry_monitor/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/instrumentation/opentelemetry_monitor/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/instrumentation/opentelemetry_monitor/.gitignore b/instrumentation/opentelemetry_monitor/.gitignore new file mode 100644 index 00000000..37920e07 --- /dev/null +++ b/instrumentation/opentelemetry_monitor/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +opentelemetry_monitor-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/instrumentation/opentelemetry_monitor/README.md b/instrumentation/opentelemetry_monitor/README.md new file mode 100644 index 00000000..75c8c93d --- /dev/null +++ b/instrumentation/opentelemetry_monitor/README.md @@ -0,0 +1,39 @@ +# OpentelemetryMonitor + +This library makes it possible to monitor a process, and close its spans when it has died. + +Without this, a crashed process can result in missing spans. + +To use, add the process to your supervision tree: + +```elixir +children = [ + OpentelemetryMonitor +] +``` + +Then, call `OpentelemetryMonitor.monitor(span_ctx)` as appropriate. + +Example: + +```elixir +OpentelemetryMonitor.monitor(OpenTelemetry.Tracer.current_span_ctx()) +``` + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `opentelemetry_monitor` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:opentelemetry_monitor, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/instrumentation/opentelemetry_monitor/lib/opentelemetry_monitor.ex b/instrumentation/opentelemetry_monitor/lib/opentelemetry_monitor.ex new file mode 100644 index 00000000..4029b295 --- /dev/null +++ b/instrumentation/opentelemetry_monitor/lib/opentelemetry_monitor.ex @@ -0,0 +1,56 @@ +defmodule OpentelemetryMonitor do + use GenServer + + def start_link(_arg) do + GenServer.start_link(__MODULE__, nil, name: __MODULE__) + end + + def init(nil) do + _table_id = :ets.new(__MODULE__, [:bag, :public, {:write_concurrency, true}, :named_table]) + {:ok, nil} + end + + def handle_call({:monitor, pid}, _from, state) do + Process.monitor(pid) + {:reply, :ok, state} + end + + def handle_info({:DOWN, _ref, :process, pid, :normal}, state) do + :ets.take(__MODULE__, pid) + |> Enum.each(fn {_pid, ctx} -> + _span_ctx = OpenTelemetry.Tracer.set_current_span(ctx) + _ = OpenTelemetry.Tracer.end_span() + end) + + {:noreply, state} + end + + def handle_info({:DOWN, _ref, :process, pid, {:shutdown, _}}, state) do + :ets.take(__MODULE__, pid) + |> Enum.each(fn {_pid, ctx} -> + _span_ctx = OpenTelemetry.Tracer.set_current_span(ctx) + _ = OpenTelemetry.Tracer.end_span() + end) + + {:noreply, state} + end + + def handle_info({:DOWN, _ref, :process, pid, reason}, state) do + :ets.take(__MODULE__, pid) + |> Enum.each(fn {_pid, ctx} -> + _span_ctx = OpenTelemetry.Tracer.set_current_span(ctx) + _ = OpenTelemetry.Tracer.add_event("Process died", [{"reason", inspect(reason)}]) + _ = OpenTelemetry.Tracer.end_span() + end) + + {:noreply, state} + end + + def monitor(span_ctx) do + if Application.fetch_env!(:opentelemetry, :processors) != [] do + # monitor first, because the monitor is necessary to clean the ets table. + :ok = GenServer.call(__MODULE__, {:monitor, self()}) + true = :ets.insert(__MODULE__, {self(), span_ctx}) + end + end +end diff --git a/instrumentation/opentelemetry_monitor/mix.exs b/instrumentation/opentelemetry_monitor/mix.exs new file mode 100644 index 00000000..47bd2e5e --- /dev/null +++ b/instrumentation/opentelemetry_monitor/mix.exs @@ -0,0 +1,44 @@ +defmodule OpentelemetryMonitor.MixProject do + use Mix.Project + + def project do + [ + app: :opentelemetry_monitor, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + package: package(), + deps: deps(), + source_url: + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_monitor" + ] + end + + def application do + [ + extra_applications: [:logger] + ] + end + + defp package() do + [ + description: "Enables closing otel spans of processes that die", + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/instrumentation/opentelemetry_monitor", + "OpenTelemetry Erlang" => "https://github.com/open-telemetry/opentelemetry-erlang", + "OpenTelemetry Erlang Contrib" => + "https://github.com/open-telemetry/opentelemetry-erlang-contrib", + "OpenTelemetry.io" => "https://opentelemetry.io" + } + ] + end + + defp deps do + [ + {:ex_doc, "~> 0.28", only: :dev, runtime: false}, + {:opentelemetry_api, "~> 1.0"} + ] + end +end diff --git a/instrumentation/opentelemetry_monitor/test/opentelemetry_monitor_test.exs b/instrumentation/opentelemetry_monitor/test/opentelemetry_monitor_test.exs new file mode 100644 index 00000000..f1a1027c --- /dev/null +++ b/instrumentation/opentelemetry_monitor/test/opentelemetry_monitor_test.exs @@ -0,0 +1,4 @@ +defmodule OpentelemetryMonitorTest do + use ExUnit.Case + doctest OpentelemetryMonitor +end diff --git a/instrumentation/opentelemetry_monitor/test/test_helper.exs b/instrumentation/opentelemetry_monitor/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/instrumentation/opentelemetry_monitor/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()