From 446c745d51d1fb87385669d341914d7e8a99f302 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 10 Mar 2026 12:41:18 +0000 Subject: [PATCH 1/3] feat(otel): automatic setup based on config --- lib/sentry/application.ex | 22 ++ lib/sentry/config.ex | 88 ++++++ lib/sentry/opentelemetry/setup.ex | 171 ++++++++++++ .../auto_setup_integration_test.exs | 188 +++++++++++++ test/sentry/opentelemetry/setup_test.exs | 252 ++++++++++++++++++ test_integrations/phoenix_app/config/dev.exs | 7 + test_integrations/phoenix_app/config/test.exs | 9 +- .../lib/phoenix_app/application.ex | 12 +- 8 files changed, 737 insertions(+), 12 deletions(-) create mode 100644 lib/sentry/opentelemetry/setup.ex create mode 100644 test/sentry/opentelemetry/auto_setup_integration_test.exs create mode 100644 test/sentry/opentelemetry/setup_test.exs diff --git a/lib/sentry/application.ex b/lib/sentry/application.ex index dda62440..30e442fd 100644 --- a/lib/sentry/application.ex +++ b/lib/sentry/application.ex @@ -28,6 +28,12 @@ defmodule Sentry.Application do end integrations_config = Config.integrations() + otel_config = Keyword.get(integrations_config, :opentelemetry, []) + + # Configure OTel SDK before supervisor starts (and ideally before :opentelemetry starts) + if Config.tracing?() do + maybe_configure_otel_sdk(otel_config) + end maybe_span_storage = if Config.tracing?() do @@ -69,6 +75,7 @@ defmodule Sentry.Application do with {:ok, pid} <- Supervisor.start_link(children, strategy: :one_for_one, name: Sentry.Supervisor) do start_integrations(integrations_config) + maybe_setup_otel_instrumentations(otel_config) maybe_add_logger_handler() {:ok, pid} end @@ -137,6 +144,21 @@ defmodule Sentry.Application do |> Enum.any?(fn %{module: module} -> module == Sentry.LoggerHandler end) end + if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do + defp maybe_configure_otel_sdk(otel_config) do + Sentry.OpenTelemetry.Setup.maybe_configure_otel_sdk(otel_config) + end + + defp maybe_setup_otel_instrumentations(otel_config) do + if Config.tracing?() do + Sentry.OpenTelemetry.Setup.maybe_setup_instrumentations(otel_config) + end + end + else + defp maybe_configure_otel_sdk(_otel_config), do: :ok + defp maybe_setup_otel_instrumentations(_otel_config), do: :ok + end + # In tests, we do not run a global rate limiter; tests start their own when # they need it. if Mix.env() == :test do diff --git a/lib/sentry/config.ex b/lib/sentry/config.ex index 85126696..f7a35f92 100644 --- a/lib/sentry/config.ex +++ b/lib/sentry/config.ex @@ -168,6 +168,94 @@ defmodule Sentry.Config do """ ] ] + ], + opentelemetry: [ + type: :keyword_list, + default: [], + doc: """ + Configuration for automatic OpenTelemetry setup when tracing is enabled + (`:traces_sample_rate` or `:traces_sampler` is set). When tracing is enabled, the SDK + automatically configures the OpenTelemetry SDK (span processor, sampler, propagator) + and sets up available instrumentation libraries. + + The SDK only sets OpenTelemetry application config if the user hasn't already configured + it via `config :opentelemetry` entries, ensuring backward compatibility. + + *Available since 12.1.0*. + """, + keys: [ + auto_setup: [ + type: :boolean, + default: true, + doc: """ + Whether to auto-configure the OpenTelemetry SDK with Sentry's span processor, + sampler, and propagator. Set to `false` if you want to configure the OTel SDK manually + via `config :opentelemetry` entries. + """ + ], + sampler_opts: [ + type: :keyword_list, + default: [], + doc: """ + Options passed to `Sentry.OpenTelemetry.Sampler`. For example, use `drop: ["span_name"]` + to drop specific spans from sampling. + """ + ], + phoenix: [ + type: {:or, [:boolean, :keyword_list]}, + default: true, + doc: """ + Auto-setup `OpentelemetryPhoenix` if available. Pass a keyword list to provide options + (e.g., `[adapter: :bandit]`). Set to `false` to disable. + """ + ], + bandit: [ + type: :boolean, + default: true, + doc: """ + Auto-setup `OpentelemetryBandit` if available. Set to `false` to disable. + """ + ], + cowboy: [ + type: :boolean, + default: true, + doc: """ + Auto-setup `OpentelemetryCowboy` if available. Set to `false` to disable. + """ + ], + oban: [ + type: :boolean, + default: true, + doc: """ + Auto-setup `OpentelemetryOban` if available. Set to `false` to disable. + """ + ], + ecto: [ + type: {:or, [:boolean, :keyword_list]}, + default: false, + doc: """ + Auto-setup `OpentelemetryEcto` if available. Requires the `:repos` option with a list + of repo telemetry prefixes (e.g., `[repos: [[:my_app, :repo]]]`). Additional options + are passed through to `OpentelemetryEcto.setup/2`. Defaults to `false` since repo + names cannot be auto-detected. + """ + ], + live_view: [ + type: :boolean, + default: true, + doc: """ + Auto-setup `Sentry.OpenTelemetry.LiveViewPropagator` for distributed tracing context + propagation to LiveView processes. Set to `false` to disable. + """ + ], + logger_metadata: [ + type: :boolean, + default: true, + doc: """ + Auto-setup `OpentelemetryLoggerMetadata` if available. Set to `false` to disable. + """ + ] + ] ] ] diff --git a/lib/sentry/opentelemetry/setup.ex b/lib/sentry/opentelemetry/setup.ex new file mode 100644 index 00000000..95af118e --- /dev/null +++ b/lib/sentry/opentelemetry/setup.ex @@ -0,0 +1,171 @@ +if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do + defmodule Sentry.OpenTelemetry.Setup do + @moduledoc false + + require Logger + + @doc """ + Configures the OpenTelemetry SDK with Sentry's span processor, sampler, and propagator. + + Called during `Sentry.Application.start/2`, before the supervisor starts. + Only applies configuration if the user hasn't already configured it via + `config :opentelemetry` entries. + """ + @spec maybe_configure_otel_sdk(keyword()) :: :ok + def maybe_configure_otel_sdk(otel_config) do + unless Keyword.get(otel_config, :auto_setup) == false do + maybe_set_span_processor() + maybe_set_sampler(Keyword.get(otel_config, :sampler_opts, [])) + maybe_set_propagators() + end + + :ok + end + + @doc """ + Auto-detects and sets up available instrumentation libraries. + + Called after the Sentry supervisor starts. Respects per-instrumentation config + to enable/disable individual libraries. + """ + @spec maybe_setup_instrumentations(keyword()) :: :ok + def maybe_setup_instrumentations(otel_config) do + # Order matters: LiveView propagator MUST come before Phoenix + maybe_setup(:live_view, otel_config, fn _opts -> + setup_if_available(Sentry.OpenTelemetry.LiveViewPropagator, :setup, []) + end) + + maybe_setup(:bandit, otel_config, fn _opts -> + setup_if_available(OpentelemetryBandit, :setup, []) + end) + + maybe_setup(:cowboy, otel_config, fn _opts -> + setup_if_available(OpentelemetryCowboy, :setup, []) + end) + + maybe_setup(:phoenix, otel_config, fn opts -> + if Code.ensure_loaded?(OpentelemetryPhoenix) do + opts = if opts == [], do: detect_phoenix_adapter_opts(), else: opts + apply(OpentelemetryPhoenix, :setup, [opts]) + end + end) + + maybe_setup(:oban, otel_config, fn _opts -> + setup_if_available(OpentelemetryOban, :setup, []) + end) + + maybe_setup(:ecto, otel_config, fn opts -> + repos = Keyword.get(opts, :repos, []) + ecto_opts = Keyword.drop(opts, [:repos]) + + for repo <- repos do + setup_if_available(OpentelemetryEcto, :setup, [repo, ecto_opts]) + end + end) + + maybe_setup(:logger_metadata, otel_config, fn _opts -> + setup_if_available(OpentelemetryLoggerMetadata, :setup, []) + end) + + :ok + end + + # OTel SDK configuration + + defp maybe_set_span_processor do + if Application.get_env(:opentelemetry, :span_processor) == nil and + Application.get_env(:opentelemetry, :processors) == nil do + if otel_started?() do + Logger.warning( + "[Sentry] OpenTelemetry has already started. " <> + "Cannot auto-configure span processor. " <> + "Add `config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []}` " <> + "to your config or ensure :sentry starts before :opentelemetry." + ) + else + Application.put_env( + :opentelemetry, + :span_processor, + {Sentry.OpenTelemetry.SpanProcessor, []} + ) + end + end + end + + defp maybe_set_sampler(sampler_opts) do + if Application.get_env(:opentelemetry, :sampler) == nil do + if otel_started?() do + Logger.warning( + "[Sentry] OpenTelemetry has already started. " <> + "Cannot auto-configure sampler. " <> + "Add `config :opentelemetry, sampler: {Sentry.OpenTelemetry.Sampler, #{inspect(sampler_opts)}}` " <> + "to your config or ensure :sentry starts before :opentelemetry." + ) + else + Application.put_env( + :opentelemetry, + :sampler, + {Sentry.OpenTelemetry.Sampler, sampler_opts} + ) + end + end + end + + defp maybe_set_propagators do + if Application.get_env(:opentelemetry, :text_map_propagators) == nil do + propagators = [:trace_context, :baggage, Sentry.OpenTelemetry.Propagator] + + if otel_started?() do + # Propagators can be reconfigured at runtime + composite = :otel_propagator_text_map_composite.create(propagators) + :opentelemetry.set_text_map_propagator(composite) + else + Application.put_env(:opentelemetry, :text_map_propagators, propagators) + end + end + end + + # Instrumentation setup helpers + + defp maybe_setup(key, otel_config, setup_fn) do + config_value = Keyword.get(otel_config, key, default_for(key)) + + unless config_value == false do + opts = if is_list(config_value), do: config_value, else: [] + + try do + setup_fn.(opts) + rescue + e -> + Logger.warning( + "[Sentry] Failed to auto-setup #{key} instrumentation: #{Exception.message(e)}" + ) + end + end + end + + defp setup_if_available(module, function, args) do + if Code.ensure_loaded?(module) do + apply(module, function, args) + end + end + + defp detect_phoenix_adapter_opts do + cond do + Code.ensure_loaded?(Bandit) -> [adapter: :bandit] + Code.ensure_loaded?(:cowboy) -> [adapter: :cowboy2] + true -> [] + end + end + + defp default_for(:ecto), do: false + defp default_for(_key), do: true + + defp otel_started? do + case List.keyfind(Application.started_applications(), :opentelemetry, 0) do + nil -> false + _ -> true + end + end + end +end diff --git a/test/sentry/opentelemetry/auto_setup_integration_test.exs b/test/sentry/opentelemetry/auto_setup_integration_test.exs new file mode 100644 index 00000000..544c9f70 --- /dev/null +++ b/test/sentry/opentelemetry/auto_setup_integration_test.exs @@ -0,0 +1,188 @@ +defmodule Sentry.OpenTelemetry.AutoSetupIntegrationTest do + use Sentry.Case, async: false + + import ExUnit.CaptureLog + import Sentry.TestHelpers + + alias Sentry.OpenTelemetry.Setup + + setup do + otel_keys = [:span_processor, :processors, :sampler, :text_map_propagators] + + saved = + Map.new(otel_keys, fn key -> + {key, Application.get_env(:opentelemetry, key)} + end) + + on_exit(fn -> + for {key, value} <- saved do + if value do + Application.put_env(:opentelemetry, key, value) + else + Application.delete_env(:opentelemetry, key) + end + end + end) + + %{saved_otel_env: saved} + end + + defp clear_otel_env do + Application.delete_env(:opentelemetry, :span_processor) + Application.delete_env(:opentelemetry, :processors) + Application.delete_env(:opentelemetry, :sampler) + Application.delete_env(:opentelemetry, :text_map_propagators) + end + + describe "full auto-setup flow" do + test "with default config, warns about OTel already started for processor/sampler" do + clear_otel_env() + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + assert log =~ "Cannot auto-configure span processor" + assert log =~ "Cannot auto-configure sampler" + + refute log =~ "propagator" + end + + test "with auto_setup: false, no OTel SDK configuration happens" do + clear_otel_env() + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk(auto_setup: false) + end) + + assert log == "" + assert Application.get_env(:opentelemetry, :span_processor) == nil + assert Application.get_env(:opentelemetry, :sampler) == nil + assert Application.get_env(:opentelemetry, :text_map_propagators) == nil + end + + test "respects existing user configuration" do + Application.put_env( + :opentelemetry, + :span_processor, + {:otel_batch_processor, %{}} + ) + + Application.put_env( + :opentelemetry, + :sampler, + {:parent_based, %{root: :always_on}} + ) + + Application.put_env( + :opentelemetry, + :text_map_propagators, + [:trace_context, :baggage] + ) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + assert log == "" + + assert Application.get_env(:opentelemetry, :span_processor) == + {:otel_batch_processor, %{}} + + assert Application.get_env(:opentelemetry, :sampler) == + {:parent_based, %{root: :always_on}} + + assert Application.get_env(:opentelemetry, :text_map_propagators) == + [:trace_context, :baggage] + end + end + + describe "instrumentation auto-setup with config" do + test "all instrumentations disabled via config" do + config = [ + live_view: false, + bandit: false, + cowboy: false, + phoenix: false, + oban: false, + ecto: false, + logger_metadata: false + ] + + assert :ok = Setup.maybe_setup_instrumentations(config) + end + + test "default config enables available instrumentations gracefully" do + assert :ok = Setup.maybe_setup_instrumentations([]) + end + + test "phoenix with keyword list options does not raise" do + assert :ok = Setup.maybe_setup_instrumentations(phoenix: [adapter: :bandit]) + end + + test "ecto requires explicit repos config" do + assert :ok = Setup.maybe_setup_instrumentations([]) + + assert :ok = Setup.maybe_setup_instrumentations(ecto: [repos: []]) + end + end + + describe "config-driven integration" do + test "opentelemetry config is properly nested under integrations" do + put_test_config( + integrations: [ + opentelemetry: [ + auto_setup: false, + sampler_opts: [drop: ["healthcheck"]], + phoenix: [adapter: :bandit], + ecto: [repos: [[:my_app, :repo]], db_statement: :enabled] + ] + ] + ) + + integrations = Sentry.Config.integrations() + otel_config = Keyword.fetch!(integrations, :opentelemetry) + + assert Keyword.fetch!(otel_config, :auto_setup) == false + assert Keyword.fetch!(otel_config, :sampler_opts) == [drop: ["healthcheck"]] + assert Keyword.fetch!(otel_config, :phoenix) == [adapter: :bandit] + + assert Keyword.fetch!(otel_config, :ecto) == [ + repos: [[:my_app, :repo]], + db_statement: :enabled + ] + + assert Keyword.fetch!(otel_config, :bandit) == true + assert Keyword.fetch!(otel_config, :oban) == true + assert Keyword.fetch!(otel_config, :live_view) == true + end + + test "empty opentelemetry config uses all defaults" do + put_test_config(integrations: [opentelemetry: []]) + + integrations = Sentry.Config.integrations() + otel_config = Keyword.fetch!(integrations, :opentelemetry) + + assert Keyword.fetch!(otel_config, :auto_setup) == true + assert Keyword.fetch!(otel_config, :ecto) == false + assert Keyword.fetch!(otel_config, :phoenix) == true + end + + test "omitting opentelemetry from integrations config uses defaults" do + put_test_config(integrations: []) + + integrations = Sentry.Config.integrations() + otel_config = Keyword.get(integrations, :opentelemetry) + + assert Keyword.fetch!(otel_config, :auto_setup) == true + assert Keyword.fetch!(otel_config, :ecto) == false + end + + test "Setup module is defined when tracing is compatible" do + assert Code.ensure_loaded?(Sentry.OpenTelemetry.Setup) + end + end +end diff --git a/test/sentry/opentelemetry/setup_test.exs b/test/sentry/opentelemetry/setup_test.exs new file mode 100644 index 00000000..13806214 --- /dev/null +++ b/test/sentry/opentelemetry/setup_test.exs @@ -0,0 +1,252 @@ +defmodule Sentry.OpenTelemetry.SetupTest do + use Sentry.Case, async: false + + import ExUnit.CaptureLog + import Sentry.TestHelpers + + alias Sentry.OpenTelemetry.Setup + + describe "maybe_configure_otel_sdk/1" do + test "warns when OTel already started and span_processor not configured" do + prev = Application.get_env(:opentelemetry, :span_processor) + Application.delete_env(:opentelemetry, :span_processor) + Application.delete_env(:opentelemetry, :processors) + + on_exit(fn -> + if prev, do: Application.put_env(:opentelemetry, :span_processor, prev) + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + assert log =~ "Cannot auto-configure span processor" + end + + test "does not warn when span_processor is already configured" do + prev = Application.get_env(:opentelemetry, :span_processor) + + Application.put_env( + :opentelemetry, + :span_processor, + {Sentry.OpenTelemetry.SpanProcessor, []} + ) + + on_exit(fn -> + if prev do + Application.put_env(:opentelemetry, :span_processor, prev) + else + Application.delete_env(:opentelemetry, :span_processor) + end + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + refute log =~ "span processor" + end + + test "does not warn when processors key is set" do + prev_processor = Application.get_env(:opentelemetry, :span_processor) + prev_processors = Application.get_env(:opentelemetry, :processors) + Application.delete_env(:opentelemetry, :span_processor) + Application.put_env(:opentelemetry, :processors, [{:otel_batch_processor, %{}}]) + + on_exit(fn -> + if prev_processor, + do: Application.put_env(:opentelemetry, :span_processor, prev_processor) + + if prev_processors do + Application.put_env(:opentelemetry, :processors, prev_processors) + else + Application.delete_env(:opentelemetry, :processors) + end + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + refute log =~ "span processor" + end + + test "warns when OTel already started and sampler not configured" do + prev = Application.get_env(:opentelemetry, :sampler) + Application.delete_env(:opentelemetry, :sampler) + + on_exit(fn -> + if prev, do: Application.put_env(:opentelemetry, :sampler, prev) + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk(sampler_opts: [drop: ["test_span"]]) + end) + + assert log =~ "Cannot auto-configure sampler" + end + + test "does not warn when sampler is already configured" do + prev = Application.get_env(:opentelemetry, :sampler) + Application.put_env(:opentelemetry, :sampler, {Sentry.OpenTelemetry.Sampler, []}) + + on_exit(fn -> + if prev do + Application.put_env(:opentelemetry, :sampler, prev) + else + Application.delete_env(:opentelemetry, :sampler) + end + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + refute log =~ "sampler" + end + + test "reconfigures propagators at runtime when OTel already started" do + prev = Application.get_env(:opentelemetry, :text_map_propagators) + Application.delete_env(:opentelemetry, :text_map_propagators) + + on_exit(fn -> + if prev, do: Application.put_env(:opentelemetry, :text_map_propagators, prev) + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk([]) + end) + + refute log =~ "propagator" + end + + test "does not reconfigure propagators when already configured" do + prev = Application.get_env(:opentelemetry, :text_map_propagators) + custom = [:trace_context, :baggage] + Application.put_env(:opentelemetry, :text_map_propagators, custom) + + on_exit(fn -> + if prev do + Application.put_env(:opentelemetry, :text_map_propagators, prev) + else + Application.delete_env(:opentelemetry, :text_map_propagators) + end + end) + + Setup.maybe_configure_otel_sdk([]) + + assert Application.get_env(:opentelemetry, :text_map_propagators) == custom + end + + test "skips all configuration when auto_setup is false" do + prev_processor = Application.get_env(:opentelemetry, :span_processor) + prev_sampler = Application.get_env(:opentelemetry, :sampler) + prev_propagators = Application.get_env(:opentelemetry, :text_map_propagators) + Application.delete_env(:opentelemetry, :span_processor) + Application.delete_env(:opentelemetry, :processors) + Application.delete_env(:opentelemetry, :sampler) + Application.delete_env(:opentelemetry, :text_map_propagators) + + on_exit(fn -> + if prev_processor, + do: Application.put_env(:opentelemetry, :span_processor, prev_processor) + + if prev_sampler, do: Application.put_env(:opentelemetry, :sampler, prev_sampler) + + if prev_propagators, + do: Application.put_env(:opentelemetry, :text_map_propagators, prev_propagators) + end) + + log = + capture_log(fn -> + Setup.maybe_configure_otel_sdk(auto_setup: false) + end) + + refute log =~ "span processor" + refute log =~ "sampler" + refute log =~ "propagator" + end + end + + describe "maybe_setup_instrumentations/1" do + test "skips instrumentations that are set to false" do + assert :ok = + Setup.maybe_setup_instrumentations( + live_view: false, + bandit: false, + cowboy: false, + phoenix: false, + oban: false, + ecto: false, + logger_metadata: false + ) + end + + test "handles unavailable modules gracefully" do + assert :ok = Setup.maybe_setup_instrumentations([]) + end + + test "respects ecto disabled by default" do + assert :ok = Setup.maybe_setup_instrumentations([]) + end + end + + describe "config validation" do + test "accepts valid opentelemetry integration config" do + put_test_config( + integrations: [ + opentelemetry: [ + auto_setup: true, + sampler_opts: [drop: ["test"]], + phoenix: [adapter: :bandit], + bandit: true, + cowboy: false, + oban: true, + ecto: [repos: [[:my_app, :repo]]], + live_view: true, + logger_metadata: false + ] + ] + ) + + config = Sentry.Config.integrations() + otel_config = Keyword.get(config, :opentelemetry, []) + + assert Keyword.get(otel_config, :auto_setup) == true + assert Keyword.get(otel_config, :sampler_opts) == [drop: ["test"]] + assert Keyword.get(otel_config, :phoenix) == [adapter: :bandit] + assert Keyword.get(otel_config, :cowboy) == false + assert Keyword.get(otel_config, :ecto) == [repos: [[:my_app, :repo]]] + assert Keyword.get(otel_config, :logger_metadata) == false + end + + test "uses correct defaults for opentelemetry config" do + put_test_config(integrations: [opentelemetry: []]) + + config = Sentry.Config.integrations() + otel_config = Keyword.get(config, :opentelemetry, []) + + assert Keyword.get(otel_config, :auto_setup) == true + assert Keyword.get(otel_config, :sampler_opts) == [] + assert Keyword.get(otel_config, :phoenix) == true + assert Keyword.get(otel_config, :bandit) == true + assert Keyword.get(otel_config, :cowboy) == true + assert Keyword.get(otel_config, :oban) == true + assert Keyword.get(otel_config, :ecto) == false + assert Keyword.get(otel_config, :live_view) == true + assert Keyword.get(otel_config, :logger_metadata) == true + end + + test "rejects invalid opentelemetry config" do + assert_raise ArgumentError, ~r/invalid value for :auto_setup/, fn -> + put_test_config(integrations: [opentelemetry: [auto_setup: "yes"]]) + end + end + end +end diff --git a/test_integrations/phoenix_app/config/dev.exs b/test_integrations/phoenix_app/config/dev.exs index 4802cdd4..6dd55c7c 100644 --- a/test_integrations/phoenix_app/config/dev.exs +++ b/test_integrations/phoenix_app/config/dev.exs @@ -94,6 +94,13 @@ config :sentry, logs: [ level: :info, metadata: :all + ], + integrations: [ + opentelemetry: [ + sampler_opts: [drop: ["Elixir.Oban.Stager process"]], + phoenix: [adapter: :bandit], + ecto: [repos: [[:phoenix_app, :repo]], db_statement: :enabled] + ] ] config :phoenix_app, Oban, diff --git a/test_integrations/phoenix_app/config/test.exs b/test_integrations/phoenix_app/config/test.exs index 9c7997b5..f1610295 100644 --- a/test_integrations/phoenix_app/config/test.exs +++ b/test_integrations/phoenix_app/config/test.exs @@ -44,10 +44,15 @@ config :sentry, level: :info, excluded_domains: [:cowboy, :ranch], metadata: [:request_id, :user_id] + ], + integrations: [ + opentelemetry: [ + sampler_opts: [drop: ["Elixir.Oban.Stager process"]], + phoenix: [adapter: :bandit], + ecto: [repos: [[:phoenix_app, :repo]], db_statement: :enabled] + ] ] -config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []} - config :phoenix_app, Oban, repo: PhoenixApp.Repo, engine: Oban.Engines.Lite, diff --git a/test_integrations/phoenix_app/lib/phoenix_app/application.ex b/test_integrations/phoenix_app/lib/phoenix_app/application.ex index ea8f4144..a6e0af24 100644 --- a/test_integrations/phoenix_app/lib/phoenix_app/application.ex +++ b/test_integrations/phoenix_app/lib/phoenix_app/application.ex @@ -9,16 +9,8 @@ defmodule PhoenixApp.Application do def start(_type, _args) do :ok = Application.ensure_started(:inets) - OpentelemetryBandit.setup() - - # Set up Sentry's LiveView context propagation BEFORE OpentelemetryPhoenix - # This enables distributed tracing context to flow from HTTP requests to LiveView WebSocket processes - Sentry.OpenTelemetry.LiveViewPropagator.setup() - - OpentelemetryPhoenix.setup(adapter: :bandit) - OpentelemetryOban.setup() - OpentelemetryEcto.setup([:phoenix_app, :repo], db_statement: :enabled) - OpentelemetryLoggerMetadata.setup() + # OpenTelemetry instrumentation libraries are now auto-configured by Sentry + # via the `integrations: [opentelemetry: [...]]` config. children = [ PhoenixAppWeb.Telemetry, From 3604f0388ff9632684ef5c01e83bac4e838f1aba Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 10 Mar 2026 15:16:28 +0000 Subject: [PATCH 2/3] fix(tests): remove send_result configuration --- test_integrations/phoenix_app/config/test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test_integrations/phoenix_app/config/test.exs b/test_integrations/phoenix_app/config/test.exs index f1610295..5afdcc3b 100644 --- a/test_integrations/phoenix_app/config/test.exs +++ b/test_integrations/phoenix_app/config/test.exs @@ -37,7 +37,6 @@ config :sentry, enable_source_code_context: true, root_source_code_paths: [File.cwd!()], test_mode: true, - send_result: :sync, traces_sample_rate: 1.0, enable_logs: true, logs: [ From 34b289c1b87e355ab7242a2a542fe0af92e02263 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Wed, 11 Mar 2026 06:42:16 +0000 Subject: [PATCH 3/3] fix(tests): ensure test mode is on for propagator tests --- test/sentry/opentelemetry/propagator_test.exs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/sentry/opentelemetry/propagator_test.exs b/test/sentry/opentelemetry/propagator_test.exs index 80942ab9..71fd05b0 100644 --- a/test/sentry/opentelemetry/propagator_test.exs +++ b/test/sentry/opentelemetry/propagator_test.exs @@ -1,6 +1,8 @@ defmodule Sentry.OpenTelemetry.PropagatorTest do use ExUnit.Case, async: true + import Sentry.TestHelpers + alias Sentry.OpenTelemetry.Propagator @moduletag skip: not Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() @@ -236,6 +238,10 @@ defmodule Sentry.OpenTelemetry.PropagatorTest do end describe "integration with OpenTelemetry" do + setup do + put_test_config(test_mode: true) + end + test "round-trip inject and extract preserves trace context" do Tracer.with_span "test_span" do ctx = :otel_ctx.get_current()