diff --git a/config/runtime.exs b/config/runtime.exs index 4115f7f8d..430f8280d 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -387,6 +387,27 @@ config :sentry, ] ] +config :opentelemetry, :resource, service: %{name: nerves_hub_app} + +if otlp_endpoint = System.get_env("OTLP_ENDPOINT") do + config :opentelemetry_exporter, + otlp_protocol: :http_protobuf, + otlp_endpoint: otlp_endpoint, + otlp_headers: [{System.get_env("OTLP_AUTH_HEADER"), System.get_env("OTLP_AUTH_HEADER_VALUE")}] + + otlp_sampler_ratio = + if ratio = System.get_env("OTLP_SAMPLER_RATIO") do + String.to_float(ratio) + else + nil + end + + config :opentelemetry, + sampler: {:parent_based, %{root: {NervesHub.Telemetry.FilteredSampler, otlp_sampler_ratio}}} +else + config :opentelemetry, traces_exporter: :none +end + if host = System.get_env("STATSD_HOST") do config :nerves_hub, :statsd, host: System.get_env("STATSD_HOST"), diff --git a/lib/nerves_hub/application.ex b/lib/nerves_hub/application.ex index 8d107b846..088bea481 100644 --- a/lib/nerves_hub/application.ex +++ b/lib/nerves_hub/application.ex @@ -12,6 +12,8 @@ defmodule NervesHub.Application do raise "fwup could not be found in the $PATH. This is a requirement of NervesHubWeb and cannot start otherwise" end + setup_open_telemetry() + _ = :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ config: %{metadata: [:file, :line]} @@ -47,6 +49,25 @@ defmodule NervesHub.Application do Supervisor.start_link(children, opts) end + defp setup_open_telemetry() do + if System.get_env("ECTO_IPV6") do + :httpc.set_option(:ipfamily, :inet6fb4) + end + + :ok = NervesHub.Telemetry.Customizations.setup() + + :ok = OpentelemetryBandit.setup() + :ok = OpentelemetryPhoenix.setup(adapter: :bandit) + :ok = OpentelemetryOban.setup(trace: [:jobs]) + + :ok = + NervesHub.Repo.config() + |> Keyword.fetch!(:telemetry_prefix) + |> OpentelemetryEcto.setup(db_statement: :enabled) + + :ok + end + def config_change(changed, _new, removed) do NervesHubWeb.Endpoint.config_change(changed, removed) :ok diff --git a/lib/nerves_hub/deployments/orchestrator.ex b/lib/nerves_hub/deployments/orchestrator.ex index b72aeab82..8e2fddbf6 100644 --- a/lib/nerves_hub/deployments/orchestrator.ex +++ b/lib/nerves_hub/deployments/orchestrator.ex @@ -9,6 +9,7 @@ defmodule NervesHub.Deployments.Orchestrator do """ use GenServer + use OpenTelemetryDecorator require Logger @@ -48,6 +49,7 @@ defmodule NervesHub.Deployments.Orchestrator do As devices update and reconnect, the new orchestrator is told that the update was successful, and the process is repeated. """ + @decorate with_span("Deployments.Orchestrator.trigger_update") def trigger_update(deployment) do :telemetry.execute([:nerves_hub, :deployment, :trigger_update], %{count: 1}) @@ -106,6 +108,7 @@ defmodule NervesHub.Deployments.Orchestrator do {:ok, deployment, {:continue, :boot}} end + @decorate with_span("Deployments.Orchestrator.boot") def handle_continue(:boot, deployment) do _ = PubSub.subscribe(NervesHub.PubSub, "deployment:#{deployment.id}") @@ -126,6 +129,7 @@ defmodule NervesHub.Deployments.Orchestrator do {:noreply, deployment} end + @decorate with_span("Deployments.Orchestrator.handle_info:deployments/update") def handle_info(%Broadcast{event: "deployments/update"}, deployment) do deployment = deployment diff --git a/lib/nerves_hub/telemetry/customizations.ex b/lib/nerves_hub/telemetry/customizations.ex new file mode 100644 index 000000000..528b231f4 --- /dev/null +++ b/lib/nerves_hub/telemetry/customizations.ex @@ -0,0 +1,23 @@ +defmodule NervesHub.Telemetry.Customizations do + alias OpenTelemetry.Tracer + require OpenTelemetry.Tracer + + def setup() do + :telemetry.attach_many( + {__MODULE__, :bandit_customizations}, + [ + [:bandit, :request, :stop] + ], + &__MODULE__.handle_request/4, + nil + ) + end + + def handle_request([:bandit, :request, :stop], _measurements, %{conn: conn}, _config) do + if conn.request_path =~ ~r/\/websocket$/ do + Tracer.update_name("WEBSOCKET #{conn.request_path}") + end + + :ok + end +end diff --git a/lib/nerves_hub/telemetry/filtered_sampler.ex b/lib/nerves_hub/telemetry/filtered_sampler.ex new file mode 100644 index 000000000..063f6fce2 --- /dev/null +++ b/lib/nerves_hub/telemetry/filtered_sampler.ex @@ -0,0 +1,88 @@ +defmodule NervesHub.Telemetry.FilteredSampler do + # Inspired by https://arathunku.com/b/2024/notes-on-adding-opentelemetry-to-an-elixir-app/ + + # TODO: Add ratio sampling support + + require OpenTelemetry.Tracer, as: Tracer + require Logger + + @behaviour :otel_sampler + + @ignored_static_paths ~r/^\/(assets|fonts|images|css)\/.*/ + + @ignored_url_paths [ + "/status/alive", + "/phoenix/live_reload/socket/websocket", + "/live/websocket", + "/favicon.ico", + "/" + ] + + @ignored_span_names [ + "Channels.DeviceSocket.heartbeat", + "nerves_hub.repo.query:schema_migrations" + ] + + @impl :otel_sampler + def setup(probability \\ nil) do + if probability do + [ratio_sampler_config: :otel_sampler_trace_id_ratio_based.setup(probability)] + else + [] + end + end + + @impl :otel_sampler + def description(_sampler_config), do: "NervesHub.Sampler" + + @impl :otel_sampler + def should_sample( + ctx, + trace_id, + links, + span_name, + span_kind, + attributes, + sampler_config + ) do + result = drop_trace?(span_name, attributes) + + tracestate = Tracer.current_span_ctx(ctx) |> OpenTelemetry.Span.tracestate() + + case result do + true -> + {:drop, [], tracestate} + + false -> + if config = sampler_config[:ratio_sampler_config] do + :otel_sampler_trace_id_ratio_based.should_sample( + ctx, + trace_id, + links, + span_name, + span_kind, + attributes, + config + ) + else + {:record_and_sample, [], tracestate} + end + end + end + + def drop_trace?(span_name, attributes) do + cond do + Enum.member?(@ignored_span_names, span_name) -> + true + + span_name == :GET && Enum.member?(@ignored_url_paths, attributes[:"url.path"]) -> + true + + span_name == :GET && (attributes[:"url.path"] || "") =~ @ignored_static_paths -> + true + + true -> + false + end + end +end diff --git a/lib/nerves_hub_web/channels/device_channel.ex b/lib/nerves_hub_web/channels/device_channel.ex index 481ce2afc..ccefdbf7d 100644 --- a/lib/nerves_hub_web/channels/device_channel.ex +++ b/lib/nerves_hub_web/channels/device_channel.ex @@ -6,6 +6,7 @@ defmodule NervesHubWeb.DeviceChannel do """ use Phoenix.Channel + use OpenTelemetryDecorator require Logger @@ -19,6 +20,7 @@ defmodule NervesHubWeb.DeviceChannel do alias NervesHub.Repo alias Phoenix.Socket.Broadcast + @decorate with_span("Channels.DeviceChannel.join") def join("device", params, %{assigns: %{device: device}} = socket) do with {:ok, device} <- update_metadata(device, params) do send(self(), {:after_join, params}) @@ -31,6 +33,7 @@ defmodule NervesHubWeb.DeviceChannel do end end + @decorate with_span("Channels.DeviceChannel.handle_info:after_join") def handle_info({:after_join, params}, %{assigns: %{device: device}} = socket) do device = maybe_update_deployment(device) @@ -77,6 +80,7 @@ defmodule NervesHubWeb.DeviceChannel do {:stop, :shutdown, socket} end + @decorate with_span("Channels.DeviceChannel.handle_info:device_registration") def handle_info({:device_registation, attempt}, socket) do %{assigns: %{device: device}} = socket @@ -98,6 +102,7 @@ defmodule NervesHubWeb.DeviceChannel do # We can save a fairly expensive query by checking the incoming deployment's payload # If it matches, we can set the deployment directly and only do 3 queries (update, two preloads) + @decorate with_span("Channels.DeviceChannel.handle_info:deployments/changed,deployment:none") def handle_info( %Broadcast{event: "deployments/changed", topic: "deployment:none", payload: payload}, %{assigns: %{device: device}} = socket @@ -121,6 +126,7 @@ defmodule NervesHubWeb.DeviceChannel do {:noreply, assign_deployment(socket, payload)} end + @decorate with_span("Channels.DeviceChannel.handle_info:deployments/changed") def handle_info( %Broadcast{event: "deployments/changed", payload: payload}, %{assigns: %{device: device}} = socket @@ -138,6 +144,7 @@ defmodule NervesHubWeb.DeviceChannel do end end + @decorate with_span("Channels.DeviceChannel.handle_info:resolve_changed_deployment") def handle_info(:resolve_changed_deployment, %{assigns: %{device: device}} = socket) do :telemetry.execute([:nerves_hub, :devices, :deployment, :changed], %{count: 1}) @@ -171,6 +178,7 @@ defmodule NervesHubWeb.DeviceChannel do {:noreply, socket} end + @decorate with_span("Channels.DeviceChannel.handle_info:deployments/update") def handle_info({"deployments/update", inflight_update}, %{assigns: %{device: device}} = socket) do device = deployment_preload(device) @@ -215,6 +223,7 @@ defmodule NervesHubWeb.DeviceChannel do end # Update local state and tell the various servers of the new information + @decorate with_span("Channels.DeviceChannel.handle_info:devices-updated") def handle_info(%Broadcast{event: "devices/updated"}, %{assigns: %{device: device}} = socket) do device = Repo.reload(device) @@ -355,6 +364,7 @@ defmodule NervesHubWeb.DeviceChannel do end end + @decorate with_span("Channels.DeviceChannel.handle_in:location:update") def handle_in("location:update", location, %{assigns: %{device: device}} = socket) do metadata = Map.put(device.connection_metadata, "location", location) @@ -405,6 +415,7 @@ defmodule NervesHubWeb.DeviceChannel do {:noreply, socket} end + @decorate with_span("Channels.DeviceChannel.handle_in:health_check_report") def handle_in("health_check_report", %{"value" => device_status}, socket) do device_meta = for {key, val} <- Map.from_struct(socket.assigns.device.firmware_metadata), @@ -474,6 +485,7 @@ defmodule NervesHubWeb.DeviceChannel do :ok end + @decorate with_span("Channels.DeviceChannel.maybe_update_deployment") defp maybe_update_deployment(device) do device |> Deployments.preload_with_firmware_and_archive() diff --git a/lib/nerves_hub_web/channels/device_socket.ex b/lib/nerves_hub_web/channels/device_socket.ex index ae70778e9..3293c042d 100644 --- a/lib/nerves_hub_web/channels/device_socket.ex +++ b/lib/nerves_hub_web/channels/device_socket.ex @@ -1,5 +1,6 @@ defmodule NervesHubWeb.DeviceSocket do use Phoenix.Socket + use OpenTelemetryDecorator require Logger @@ -28,6 +29,7 @@ defmodule NervesHubWeb.DeviceSocket do end @impl Phoenix.Socket.Transport + @decorate with_span("Channels.DeviceSocket.terminate") def terminate(reason, {_channels_info, socket} = state) do on_disconnect(reason, socket) super(reason, state) @@ -42,6 +44,7 @@ defmodule NervesHubWeb.DeviceSocket do super(msg, {state, socket}) end + @decorate with_span("Channels.DeviceSocket.heartbeat") defp heartbeat( %Phoenix.Socket.Message{topic: "phoenix", event: "heartbeat"}, %{ @@ -84,6 +87,7 @@ defmodule NervesHubWeb.DeviceSocket do # Used by Devices connecting with SSL certificates @impl Phoenix.Socket + @decorate with_span("Channels.DeviceSocket.connect") def connect(_params, socket, %{peer_data: %{ssl_cert: ssl_cert}}) when not is_nil(ssl_cert) do X509.Certificate.from_der!(ssl_cert) @@ -103,6 +107,7 @@ defmodule NervesHubWeb.DeviceSocket do end # Used by Devices connecting with HMAC Shared Secrets + @decorate with_span("Channels.DeviceSocket.connect") def connect(_params, socket, %{x_headers: x_headers}) when is_list(x_headers) and length(x_headers) > 0 do headers = Map.new(x_headers) @@ -210,12 +215,14 @@ defmodule NervesHubWeb.DeviceSocket do {:ok, socket} end + @decorate with_span("Channels.DeviceSocket.on_connect#registered") defp on_connect(%{assigns: %{device: %{status: :registered} = device}} = socket) do socket |> assign(device: Devices.set_as_provisioned!(device)) |> on_connect() end + @decorate with_span("Channels.DeviceSocket.on_connect#provisioned") defp on_connect(%{assigns: %{device: device}} = socket) do # Report connection and use connection id as reference {:ok, %DeviceConnection{id: connection_id}} = @@ -235,6 +242,9 @@ defmodule NervesHubWeb.DeviceSocket do |> assign(:reference_id, connection_id) end + @decorate with_span("Channels.DeviceSocket.on_disconnect") + defp on_disconnect(exit_reason, socket) + defp on_disconnect({:error, reason}, %{ assigns: %{ device: device, @@ -262,6 +272,7 @@ defmodule NervesHubWeb.DeviceSocket do shutdown(device, reference_id) end + @decorate with_span("Channels.DeviceSocket.shutdown") defp shutdown(device, reference_id) do :telemetry.execute([:nerves_hub, :devices, :disconnect], %{count: 1}, %{ ref_id: reference_id, diff --git a/lib/nerves_hub_web/live/devices/index.ex b/lib/nerves_hub_web/live/devices/index.ex index a699eef96..63339caef 100644 --- a/lib/nerves_hub_web/live/devices/index.ex +++ b/lib/nerves_hub_web/live/devices/index.ex @@ -3,6 +3,8 @@ defmodule NervesHubWeb.Live.Devices.Index do require Logger + require OpenTelemetry.Tracer, as: Tracer + alias NervesHub.AuditLogs alias NervesHub.Devices alias NervesHub.Devices.Alarms @@ -356,12 +358,14 @@ defmodule NervesHubWeb.Live.Devices.Index do end def handle_info(:refresh_device_list, socket) do - Process.send_after(self(), :refresh_device_list, @list_refresh_time) + Tracer.with_span "NervesHubWeb.Live.Devices.Index.refresh_device_list" do + Process.send_after(self(), :refresh_device_list, @list_refresh_time) - if socket.assigns.paginate_opts.total_pages == 1 do - {:noreply, assign_display_devices(socket)} - else - {:noreply, socket} + if socket.assigns.paginate_opts.total_pages == 1 do + {:noreply, assign_display_devices(socket)} + else + {:noreply, socket} + end end end diff --git a/mix.exs b/mix.exs index 81f7acfc3..99ec5efe4 100644 --- a/mix.exs +++ b/mix.exs @@ -18,6 +18,8 @@ defmodule NervesHub.MixProject do steps: [:assemble], include_executables_for: [:unix], applications: [ + opentelemetry_exporter: :permanent, + opentelemetry: :temporary, nerves_hub: :permanent ] ] @@ -95,6 +97,17 @@ defmodule NervesHub.MixProject do {:mox, "~> 1.0", only: [:test, :dev]}, {:nimble_csv, "~> 1.1"}, {:oban, "~> 2.11"}, + {:opentelemetry_exporter, "~> 1.8"}, + {:opentelemetry, "~> 1.5"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_ecto, "~> 1.2"}, + {:opentelemetry_phoenix, "~> 2.0.0-rc.1 "}, + {:opentelemetry_oban, "~> 1.0", + git: "https://github.com/joshk/opentelemetry-erlang-contrib", + branch: "update-obans-semantic-conventions", + subdir: "instrumentation/opentelemetry_oban"}, + {:opentelemetry_bandit, "~> 0.2.0-rc.1"}, + {:open_telemetry_decorator, "~> 1.5"}, {:phoenix, "~> 1.7.0"}, {:phoenix_ecto, "~> 4.0"}, {:phoenix_html, "~> 3.3.1", override: true}, diff --git a/mix.lock b/mix.lock index ca063dae6..fe2e18013 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,18 @@ %{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "assert_eventually": {:hex, :assert_eventually, "1.0.0", "f1539f28ba3ffa99a712433c77723c7103986932aa341d05eee94c333a920d15", [:mix], [{:ex_doc, ">= 0.0.0", [hex: :ex_doc, repo: "hexpm", optional: true]}], "hexpm", "c658ac4103c8bd82d0cf72a2fdb77477ba3fbc6b15228c5c801003d239625c69"}, "bandit": {:hex, :bandit, "1.6.0", "9cb6c67c27cecab2d0c93968cb957fa8decccb7275193c8bf33f97397b3ac25d", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "fd2491e564a7c5e11ff8496ebf530c342c742452c59de17ac0fb1f814a0ab01a"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, "castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"}, "contex": {:hex, :contex, "0.5.0", "5d8a6defbeb41f54adfcb0f85c4756d4f2b84aa5b0d809d45a5d2e90d91d0392", [:mix], [{:nimble_strftime, "~> 0.1.0", [hex: :nimble_strftime, repo: "hexpm", optional: false]}], "hexpm", "b7497a1790324d84247859df44ba4bcf2489d9bba1812a5375b2f2046b9e6fd7"}, "crontab": {:hex, :crontab, "1.1.14", "233fcfdc2c74510cabdbcb800626babef414e7cb13cea11ddf62e10e16e2bf76", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "4e3b9950bc22ae8d0395ffb5f4b127a140005cba95745abf5ff9ee7e8203c6fa"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.2.0", "df3d06bb9517e302b1bd265c1e7f16cda51547ad9d99892049340841f3e15836", [:mix], [], "hexpm", "af8daf87384b51b7e611fb1a1f2c4d4876b65ef968fa8bd3adf44cff401c7f21"}, @@ -29,8 +32,11 @@ "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hlclock": {:hex, :hlclock, "1.0.0", "7a72fc7a20a9382499216227edf97a8b118e21fc3fcad0e81b8d10c616ce1431", [:mix], [], "hexpm", "d3f994336a7fcbc68bf08b14b2101b61e57bef82c032a6e05c1cdc753612c941"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -49,7 +55,21 @@ "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_strftime": {:hex, :nimble_strftime, "0.1.1", "b988184d1bd945bc139b2c27dd00a6c0774ec94f6b0b580083abd62d5d07818b", [:mix], [], "hexpm", "89e599c9b8b4d1203b7bb5c79eb51ef7c6a28fbc6228230b312f8b796310d755"}, + "o11y": {:hex, :o11y, "0.2.6", "9386b36f9cc65ea838733b4838dea02334c4fb8721e53b904d02d50396df5cdc", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "ea631c5ba03b155c1bc5849804da5d375538659446e4bb9388a0427959d122ef"}, "oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"}, + "open_telemetry_decorator": {:hex, :open_telemetry_decorator, "1.5.7", "d0bb1942a3ea33e8fbf92ac44e084771a7dea076a559d974bf5b8ac11f76beaa", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: false]}, {:o11y, "~> 0.2", [hex: :o11y, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "80566f68159c727c77986c617478a63c4e51256061baf52503d58d9e2316c6c4"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_bandit": {:hex, :opentelemetry_bandit, "0.2.0-rc.1", "3b78e4a472c20102af2d6852a04a890a8780de26feda560a63283045bc9088f5", [:mix], [{:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:otel_http, "~> 0.2", [hex: :otel_http, repo: "hexpm", optional: false]}, {:plug, ">= 1.15.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9755c38e8b952204299f568cc30b5462851340c8b4b50702614ca403275b573a"}, + "opentelemetry_ecto": {:hex, :opentelemetry_ecto, "1.2.0", "2382cb47ddc231f953d3b8263ed029d87fbf217915a1da82f49159d122b64865", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "70dfa2e79932e86f209df00e36c980b17a32f82d175f0068bf7ef9a96cf080cf"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_liveview": {:hex, :opentelemetry_liveview, "1.0.0-rc.4", "52915a83809100f31f7b6ea42e00b964a66032b75cc56e5b4cbcf7e21d4a45da", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.0.0-beta.7", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e06ab69da7ee46158342cac42f1c22886bdeab53e8d8c4e237c3b3c2cf7b815d"}, + "opentelemetry_oban": {:git, "https://github.com/joshk/opentelemetry-erlang-contrib", "2ada0a1a7043706683701dfa1bda7560699be25b", [branch: "update-obans-semantic-conventions", subdir: "instrumentation/opentelemetry_oban"]}, + "opentelemetry_phoenix": {:hex, :opentelemetry_phoenix, "2.0.0-rc.1", "252f918a90f6aad4ea76133e03d9421b8014712d3d9186bd2bc54311563b282f", [:mix], [{:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.1", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:otel_http, "~> 0.2", [hex: :otel_http, repo: "hexpm", optional: false]}, {:plug, ">= 1.11.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "74accdfd9f3757b802064f267ecbc5208a97fcb54d100e8e834b56fe09e64a8e"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "otel_http": {:hex, :otel_http, "0.2.0", "b17385986c7f1b862f5d577f72614ecaa29de40392b7618869999326b9a61d8a", [:rebar3], [], "hexpm", "f2beadf922c8cfeb0965488dd736c95cc6ea8b9efce89466b3904d317d7cc717"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, @@ -80,8 +100,10 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, "telemetry_metrics_statsd": {:hex, :telemetry_metrics_statsd, "0.7.1", "3502235bb5b35ce50d608bf0f34369ef76eb92a4dbc8708c7e8780ca0da2d53e", [:mix], [{:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "06338d9dc3b4a202f11a6e706fd3feba4c46100d0aca23688dea0b8f801c361f"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, + "telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"}, "thousand_island": {:hex, :thousand_island, "1.3.6", "835a626a8a6f6a1e681b63e1132a8427e87ce443aaf4888fbf63b2df77539b97", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0ed8798084c8c49a223840b20598b022e4eb8c9f390fb6701864c307fc9aa2cd"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.24.0", "d00e2887551ff8cdae4d0340d90d9fcbc4943c7b5f49d32ed4bc23aff4db9a44", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "90b25a58ee433d91c17f036d4d354bf8859a089bfda60e68a86f8eecae45ef1b"}, "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "uuidv7": {:hex, :uuidv7, "1.0.0", "659179b2e248b98f96e7e988b882d369c055b6ae7a836237ccca52cd4d0f6988", [:mix], [{:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "0ecd337108456f7d8b1a9a54ef435443d3f8c10a5b685bd866ef9e396b444cbc"},