diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..7eca31f --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["{mix,.formatter}.exs", "{examples,instrumentation,propagators,test}/**/*.{ex,exs}"] +] diff --git a/.github/labeler.yml b/.github/labeler.yml index a2bf59e..3fd70a2 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,6 +5,8 @@ elixir: - propagators/**/*.exs - exporters/**/*.ex - exporters/**/*.exs + - examples/**/*.ex + - examples/**/*.exs erlang: - instrumentation/**/*.erl @@ -16,6 +18,9 @@ erlang: - exporters/**/*.erl - exporters/**/*.hrl - exporters/**/rebar.config + - examples/**/*.erl + - examples/**/*.hrl + - examples/**/rebar.config instrumentation: - instrumentation/**/* @@ -23,5 +28,11 @@ instrumentation: propagators: - propagators/**/* +examples: + - examples/**/* + scope-ci: - .github/workflows/** + +opentelemetry_phoenix: + - instrumentation/opentelemetry_phoenix diff --git a/.github/test-matrix.json b/.github/test-matrix.json new file mode 100644 index 0000000..8093158 --- /dev/null +++ b/.github/test-matrix.json @@ -0,0 +1,20 @@ +{ + "otp_version": ["24.0.4", "23.3.4.2", "22.3.4.20"], + "elixir_version": ["1.12.2", "1.11.4", "1.10.4"], + "include": [ + { + "otp_version": "21.3.8.24", + "elixir_version": "1.10.4", + "rebar3_version": "3.15.2" + } + ], + "exclude": [ + { + "otp_version": "21.3.8.24", + "elixir_version": "1.12.2" + } + ], + "apps": [ + "opentelemetry_phoenix" + ] +} diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml new file mode 100644 index 0000000..151e0ef --- /dev/null +++ b/.github/workflows/elixir.yml @@ -0,0 +1,70 @@ +name: Elixir + +on: + pull_request: + branches: + - 'main' + types: [opened, reopened, synchronize, labeled] + push: + branches: + - 'main' + +jobs: + # test-matrix: + # runs-on: ubuntu-latest + # outputs: + # matrix: ${{ steps.set-matrix.outputs.matrix }} + # steps: + # - uses: actions/checkout@v2 + # - name: Read file + # id: set-matrix + # run: | + # echo "::set-output name=matrix::$(cat ./.github/test-matrix.json | jq -r -s '.|tojson')" + opentelemetry-phoenix: + # needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_phoenix')) + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-18.04 + name: Opentelemetry Phoenix test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04] + otp_version: ["24.0.4", "23.3.4.2", "22.3.4.20"] + elixir_version: ["1.12.2", "1.11.4"] + rebar3_version: ["3.16.1"] + include: + - otp_version: '21.3.8.24' + elixir_version: '1.11.4' + rebar3_version: '3.15.2' + os: ubuntu-18.04 + exclude: + - otp_version: '21.3.8.24' + elixir_version: '1.12.1' + env: + app: 'opentelemetry_phoenix' + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ matrix.otp_version }} + elixir-version: ${{ matrix.elixir_version }} + rebar3-version: ${{ matrix.rebar3_version }} + - name: Cache + uses: actions/cache@v2 + with: + path: | + ${{ env.app }}/deps + ${{ env.app }}/_build + key: ${{ runner.os }}-build-${{ matrix.otp_version }}-${{ matrix.elixir_version }}-v3-${{ hashFiles(format('{0}{1}', github.workspace, 'instrumentation/${{ env.app }}/mix.lock')) }} + - name: Fetch deps + if: steps.deps-cache.outputs.cache-hit != 'true' + run: mix deps.get + - name: Compile project + run: mix compile --warnings-as-errors + - name: Check formatting + run: mix format --check-formatted + - name: Test + run: mix test diff --git a/.gitignore b/.gitignore index 751a61d..c1d4e7c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,20 @@ erl_crash.dump # rebar 2.x .rebar rel/example_project -ebin/*.beam deps # rebar 3 .rebar3 _build/ _checkouts/ + +_* +*.swp +*.swo +.erlang.cookie +ebin +*.iml +doc + +.tool-versions + diff --git a/CODEOWNERS b/CODEOWNERS index 5e3e043..982216a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -12,4 +12,6 @@ # https://help.github.com/en/articles/about-code-owners # -* @open-telemetry/erlang-approvers +@open-telemetry/erlang-approvers + +/instrumentation/opentelemetry_phoenix @bryannaegele @tristansloughter \ No newline at end of file diff --git a/instrumentation/opentelemetry_phoenix/.formatter.exs b/instrumentation/opentelemetry_phoenix/.formatter.exs new file mode 100644 index 0000000..3ebaf3c --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/.formatter.exs @@ -0,0 +1,5 @@ +# Used by "mix format" +[ + line_length: 120, + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/instrumentation/opentelemetry_phoenix/.gitignore b/instrumentation/opentelemetry_phoenix/.gitignore new file mode 100644 index 0000000..3acaeac --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/.gitignore @@ -0,0 +1,28 @@ +# 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_phoenix-*.tar + +test/support/deps/ +test/support/_build/ + +priv \ No newline at end of file diff --git a/instrumentation/opentelemetry_phoenix/CHANGELOG.md b/instrumentation/opentelemetry_phoenix/CHANGELOG.md new file mode 100644 index 0000000..e924120 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 0.2.0 + +### Changed + +* Upgraded to Opentelemetry v0.5.0 + diff --git a/instrumentation/opentelemetry_phoenix/LICENSE b/instrumentation/opentelemetry_phoenix/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/opentelemetry_phoenix/README.md b/instrumentation/opentelemetry_phoenix/README.md new file mode 100644 index 0000000..09d0d13 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/README.md @@ -0,0 +1,29 @@ +# OpentelemetryPhoenix + +[![EEF Observability WG project](https://img.shields.io/badge/EEF-Observability-black)](https://github.com/erlef/eef-observability-wg) +[![Hex.pm](https://img.shields.io/hexpm/v/opentelemetry_phoenix)](https://hex.pm/packages/opentelemetry_phoenix) +![Build Status](https://github.com/opentelemetry-beam/opentelemetry_phoenix/workflows/Tests/badge.svg) + +Telemetry handler that creates Opentelemetry spans from Phoenix events. + +After installing, setup the handler in your application behaviour before your +top-level supervisor starts. + +```elixir +OpentelemetryPhoenix.setup() +``` + +See the documentation for `OpentelemetryPhoenix.setup/1` for additional options that +may be supplied. + + +## Installation + +```elixir +def deps do + [ + {:opentelemetry_phoenix, "~> 1.0.0-rc"} + ] +end +``` + diff --git a/instrumentation/opentelemetry_phoenix/config/config.exs b/instrumentation/opentelemetry_phoenix/config/config.exs new file mode 100644 index 0000000..ed8ab8d --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/config/config.exs @@ -0,0 +1,34 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +# This configuration is loaded before any dependency and is restricted +# to this project. If another project depends on this project, this +# file won't be loaded nor affect the parent project. For this reason, +# if you want to provide default values for your application for +# third-party users, it should be done in your "mix.exs" file. + +# You can configure your application as: +# +# config :opentelemetry_ecto, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:opentelemetry_ecto, :key) +# +# You can also configure a third-party app: +# +# config :logger, level: :info +# + +# It is also possible to import configuration files, relative to this +# directory. For example, you can emulate configuration per environment +# by uncommenting the line below and defining dev.exs, test.exs and such. +# Configuration from the imported file will override the ones defined +# here (which is why it is important to import them last). +# +try do + import_config "#{Mix.env()}.exs" +rescue + _ -> :ok +end diff --git a/instrumentation/opentelemetry_phoenix/config/test.exs b/instrumentation/opentelemetry_phoenix/config/test.exs new file mode 100644 index 0000000..ebb65c2 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/config/test.exs @@ -0,0 +1,4 @@ +config :opentelemetry, + sampler: {:always_on, %{}}, + tracer: :ot_tracer_default, + processors: [{:ot_batch_processor, %{scheduled_delay_ms: 1}}] diff --git a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex new file mode 100644 index 0000000..50baa09 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex @@ -0,0 +1,221 @@ +defmodule OpentelemetryPhoenix do + @moduledoc """ + OpentelemetryPhoenix uses [telemetry](https://hexdocs.pm/telemetry/) handlers to create `OpenTelemetry` spans. + + Current events which are supported include endpoint start/stop, router start/stop, + and router exceptions. + + ## Usage + + In your application start: + + def start(_type, _args) do + OpenTelemetry.register_application_tracer(:my_app) + OpentelemetryPhoenix.setup() + + children = [ + {Phoenix.PubSub, name: MyApp.PubSub}, + MyAppWeb.Endpoint + ] + + opts = [strategy: :one_for_one, name: MyStore.Supervisor] + Supervisor.start_link(children, opts) + end + + """ + + require OpenTelemetry.Tracer + alias OpenTelemetry.Span + alias OpentelemetryPhoenix.Reason + + @tracer_id :opentelemetry_phoenix + + @typedoc "Setup options" + @type opts :: [endpoint_prefix()] + + @typedoc "The endpoint prefix in your endpoint. Defaults to `[:phoenix, :endpoint]`" + @type endpoint_prefix :: {:endpoint_prefix, [atom()]} + + @doc """ + Initializes and configures the telemetry handlers. + """ + @spec setup(opts()) :: :ok + def setup(opts \\ []) do + opts = ensure_opts(opts) + + {:ok, otel_phx_vsn} = :application.get_key(@tracer_id, :vsn) + OpenTelemetry.register_tracer(@tracer_id, otel_phx_vsn) + + attach_endpoint_start_handler(opts) + attach_endpoint_stop_handler(opts) + attach_router_start_handler() + attach_router_dispatch_exception_handler() + + :ok + end + + defp ensure_opts(opts), do: Keyword.merge(default_opts(), opts) + + defp default_opts do + [endpoint_prefix: [:phoenix, :endpoint]] + end + + @doc false + def attach_endpoint_start_handler(opts) do + :telemetry.attach( + {__MODULE__, :endpoint_start}, + opts[:endpoint_prefix] ++ [:start], + &__MODULE__.handle_endpoint_start/4, + %{} + ) + end + + @doc false + def attach_endpoint_stop_handler(opts) do + :telemetry.attach( + {__MODULE__, :endpoint_stop}, + opts[:endpoint_prefix] ++ [:stop], + &__MODULE__.handle_endpoint_stop/4, + %{} + ) + end + + @doc false + def attach_router_start_handler do + :telemetry.attach( + {__MODULE__, :router_dispatch_start}, + [:phoenix, :router_dispatch, :start], + &__MODULE__.handle_router_dispatch_start/4, + %{} + ) + end + + @doc false + def attach_router_dispatch_exception_handler do + :telemetry.attach( + {__MODULE__, :router_dispatch_exception}, + [:phoenix, :router_dispatch, :exception], + &__MODULE__.handle_router_dispatch_exception/4, + %{} + ) + end + + @doc false + def handle_endpoint_start(_event, _measurements, %{conn: %{adapter: adapter} = conn} = meta, _config) do + # TODO: maybe add config for what paths are traced? Via sampler? + :otel_propagator.text_map_extract(conn.req_headers) + + peer_data = Plug.Conn.get_peer_data(conn) + + user_agent = header_value(conn, "user-agent") + peer_ip = Map.get(peer_data, :address) + + attributes = [ + "http.client_ip": client_ip(conn), + "http.flavor": http_flavor(adapter), + "http.host": conn.host, + "http.method": conn.method, + "http.scheme": "#{conn.scheme}", + "http.target": conn.request_path, + "http.user_agent": user_agent, + "net.host.ip": to_string(:inet_parse.ntoa(conn.remote_ip)), + "net.host.port": conn.port, + "net.peer.ip": to_string(:inet_parse.ntoa(peer_ip)), + "net.peer.port": peer_data.port, + "net.transport": :"IP.TCP" + ] + + # start the span with a default name. Route name isn't known until router dispatch + OpentelemetryTelemetry.start_telemetry_span(@tracer_id, "HTTP #{conn.method}", meta, %{kind: :server}) + |> Span.set_attributes(attributes) + end + + @doc false + def handle_endpoint_stop(_event, _measurements, %{conn: conn} = meta, _config) do + # ensure the correct span is current and update the status + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta) + + Span.set_attribute(ctx, :"http.status", conn.status) + + if conn.status >= 400 do + Span.set_status(ctx, OpenTelemetry.status(:error, "")) + end + + # end the Phoenix span + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta) + end + + @doc false + def handle_router_dispatch_start(_event, _measurements, meta, _config) do + attributes = [ + "phoenix.plug": meta.plug, + "phoenix.action": meta.plug_opts, + "http.route": meta.route + ] + + # Add more info that we now know about but don't close the span + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta) + Span.update_name(ctx, "#{meta.route}") + Span.set_attributes(ctx, attributes) + end + + @doc false + def handle_router_dispatch_exception( + _event, + _measurements, + %{kind: kind, reason: reason, stacktrace: stacktrace} = meta, + _config + ) do + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta) + + {[reason: reason], attrs} = + Reason.normalize(reason) + |> Keyword.split([:reason]) + + # try to normalize all errors to Elixir exceptions + exception = Exception.normalize(kind, reason, stacktrace) + + # record exception and mark the span as errored + Span.record_exception(ctx, exception, stacktrace, attrs) + Span.set_status(ctx, OpenTelemetry.status(:error, "")) + + # do not close the span as endpoint stop will still be called with + # more info, including the status code, which is nil at this stage + end + + defp http_flavor({_adapter_name, meta}) do + case Map.get(meta, :version) do + :"HTTP/1.0" -> :"1.0" + :"HTTP/1.1" -> :"1.1" + :"HTTP/2.0" -> :"2.0" + :"HTTP/2" -> :"2.0" + :SPDY -> :SPDY + :QUIC -> :QUIC + nil -> "" + end + end + + defp client_ip(%{remote_ip: remote_ip} = conn) do + case header_value(conn, "x-forwarded-for") do + "" -> + remote_ip + |> :inet_parse.ntoa() + |> to_string() + + ip_address -> + ip_address + |> String.split(",", parts: 2) + |> List.first() + end + end + + defp header_value(conn, header) do + case Plug.Conn.get_req_header(conn, header) do + [] -> + "" + + [value | _] -> + value + end + end +end diff --git a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex new file mode 100644 index 0000000..b63c18a --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex @@ -0,0 +1,83 @@ +defmodule OpentelemetryPhoenix.Reason do + def normalize(%{reason: reason}) do + # %Plug.Conn.WrapperError{} + normalize(reason) + end + + def normalize(:badarg) do + [reason: :badarg] + end + + def normalize(:badarith) do + [reason: :badarith] + end + + def normalize(:system_limit) do + [reason: :system_limit] + end + + def normalize(:cond_clause) do + [reason: :cond_clause] + end + + def normalize(:undef) do + [reason: :undef] + end + + def normalize({:badarity, {fun, args}}) do + {:arity, arity} = Function.info(fun, :arity) + [reason: :badarity, function: "#{inspect(fun)}", arity: arity, args: args] + end + + def normalize({:badfun, term}) do + [reason: :badfun, term: term] + end + + def normalize({:badstruct, struct, term}) do + [reason: :badstruct, struct: struct, term: term] + end + + def normalize({:badmatch, term}) do + [reason: :badmatch, term: term] + end + + def normalize({:badmap, term}) do + [reason: :badmap, term: term] + end + + def normalize({:badbool, op, term}) do + [reason: :badbool, operator: op, term: term] + end + + def normalize({:badkey, key}) do + [reason: :badkey, key: key] + end + + def normalize({:badkey, key, map}) do + [reason: :badkey, key: key, map: map] + end + + def normalize({:case_clause, term}) do + [reason: :case_clause, term: term] + end + + def normalize({:with_clause, term}) do + [reason: :with_clause, term: term] + end + + def normalize({:try_clause, term}) do + [reason: :try_clause, term: term] + end + + def normalize({:badarg, payload}) do + [reason: :badarg, payload: payload] + end + + def normalize(other) do + [reason: other] + end + + def normalize(other, _stacktrace) do + [reason: other] + end +end diff --git a/instrumentation/opentelemetry_phoenix/mix.exs b/instrumentation/opentelemetry_phoenix/mix.exs new file mode 100644 index 0000000..2473da2 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/mix.exs @@ -0,0 +1,68 @@ +defmodule OpentelemetryPhoenix.MixProject do + use Mix.Project + + def project do + [ + app: :opentelemetry_phoenix, + description: description(), + version: "1.0.0-rc.2", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + dialyzer: [ + plt_add_apps: [:ex_unit, :mix], + plt_core_path: "priv/plts", + plt_local_path: "priv/plts" + ], + deps: deps(), + name: "Opentelemetry Phoenix", + docs: [ + main: "OpentelemetryPhoenix", + extras: ["README.md"] + ], + elixirc_paths: elixirc_paths(Mix.env()), + package: package(), + source_url: "https://github.com/opentelemetry-beam/opentelemetry_phoenix" + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [] + ] + end + + defp description do + "Trace Phoenix requests with OpenTelemetry." + end + + defp package do + [ + description: "OpenTelemetry tracing for the Phoenix Framework", + files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*), + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => "https://github.com/opentelemetry-beam/opentelemetry_phoenix", + "OpenTelemetry Erlang" => "https://github.com/open-telemetry/opentelemetry-erlang", + "OpenTelemetry.io" => "https://opentelemetry.io" + } + ] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:opentelemetry_api, "~> 1.0.0-rc"}, + {:opentelemetry, "~> 1.0.0-rc"}, + {:opentelemetry_telemetry, "~> 1.0.0-beta"}, + {:telemetry, "~> 0.4"}, + {:plug, "~> 1.11", only: [:dev, :test]}, + {:ex_doc, "~> 0.24", only: [:dev], runtime: false}, + {:plug_cowboy, "~> 2.4", only: [:test]}, + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false} + ] + end +end diff --git a/instrumentation/opentelemetry_phoenix/mix.lock b/instrumentation/opentelemetry_phoenix/mix.lock new file mode 100644 index 0000000..2574ce6 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/mix.lock @@ -0,0 +1,24 @@ +%{ + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "opentelemetry": {:hex, :opentelemetry, "1.0.0-rc.2", "d3e1fd9debfd73e00b0241cac464be7cd6ca6ac2bd38ab2ebe0c92401c76a342", [:rebar3], [{:opentelemetry_api, "~> 1.0.0-rc.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "2f810e2eed70a9ea0c9b6943969b59e37f96a2f9e10920045a6c7676c2ab8181"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.0.0-rc.2", "a0ec5b242bb7ce7563b4891e77dcfa529defc9e42c19a5a702574c5ac3d0c6e7", [:mix, :rebar3], [], "hexpm", "426a969c8ee2afa8ab55b58e6e40e81c1f934c064459a1acb530f54042f9a9a3"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.0.0-beta.2", "b840eee9e68307ad7fa4ee316da19db3f8e30763b87737d3304782ca3cc296a2", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0.0-rc.1", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2.1", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "e8b12f42614d0aeb6a49001c75ca035544950f736fdbb240177838674f99e1e2"}, + "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.5.0", "51c998f788c4e68fc9f947a5eba8c215fbb1d63a520f7604134cab0270ea6513", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b2c8925a5e2587446f33810a58c01e66b3c345652eeec809b76ba007acde71a"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, + "telemetry_registry": {:hex, :telemetry_registry, "0.2.1", "fe648a691f2128e4279d993cd010994c67f282354dc061e697bf070d4b87b480", [:mix, :rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4221cefbcadd0b3e7076960339223742d973f1371bc20f3826af640257bc3690"}, +} diff --git a/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs b/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs new file mode 100644 index 0000000..d7057e4 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs @@ -0,0 +1,262 @@ +defmodule OpentelemetryPhoenixTest do + use ExUnit.Case, async: false + doctest OpentelemetryPhoenix + + require OpenTelemetry.Tracer + require OpenTelemetry.Span + require Record + + alias PhoenixMeta, as: Meta + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do + Record.defrecord(name, spec) + end + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do + Record.defrecord(name, spec) + end + + setup do + :application.stop(:opentelemetry) + :application.set_env(:opentelemetry, :tracer, :otel_tracer_default) + + :application.set_env(:opentelemetry, :processors, [ + {:otel_batch_processor, %{scheduled_delay_ms: 1}} + ]) + + :application.start(:opentelemetry) + + :otel_batch_processor.set_exporter(:otel_exporter_pid, self()) + :ok + end + + test "records spans for Phoenix web requests" do + OpentelemetryPhoenix.setup() + + :telemetry.execute( + [:phoenix, :endpoint, :start], + %{system_time: System.system_time()}, + Meta.endpoint_start() + ) + + :telemetry.execute( + [:phoenix, :router_dispatch, :start], + %{system_time: System.system_time()}, + Meta.router_dispatch_start() + ) + + :telemetry.execute( + [:phoenix, :endpoint, :stop], + %{duration: 444}, + Meta.endpoint_stop() + ) + + assert_receive {:span, + span( + name: "/users/:user_id", + attributes: list, + parent_span_id: 13_235_353_014_750_950_193 + )} + + assert [ + "http.client_ip": "10.211.55.2", + "http.flavor": :"1.1", + "http.host": "localhost", + "http.method": "GET", + "http.route": "/users/:user_id", + "http.scheme": "http", + "http.status": 200, + "http.target": "/users/123", + "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", + "net.host.ip": "10.211.55.2", + "net.host.port": 4000, + "net.peer.ip": "10.211.55.2", + "net.peer.port": 64291, + "net.transport": :"IP.TCP", + "phoenix.action": :user, + "phoenix.plug": Elixir.MyStoreWeb.PageController + ] == List.keysort(list, 0) + end + + test "parses x-forwarded-for with single value" do + OpentelemetryPhoenix.setup() + + x_forwarded_for_request("203.0.113.195") + + assert_receive {:span, span(attributes: list)} + + assert Keyword.fetch!(list, :"http.client_ip") == "203.0.113.195" + end + + test "parses x-forwarded-for with multiple values" do + OpentelemetryPhoenix.setup() + + x_forwarded_for_request("203.0.113.195, 70.41.3.18, 150.172.238.178") + + assert_receive {:span, span(attributes: list)} + + assert Keyword.fetch!(list, :"http.client_ip") == "203.0.113.195" + end + + test "records exceptions for Phoenix web requests" do + OpentelemetryPhoenix.setup() + + :telemetry.execute( + [:phoenix, :endpoint, :start], + %{system_time: System.system_time()}, + Meta.endpoint_start(:exception) + ) + + :telemetry.execute( + [:phoenix, :router_dispatch, :start], + %{system_time: System.system_time()}, + Meta.router_dispatch_start(:exception) + ) + + :telemetry.execute( + [:phoenix, :router_dispatch, :exception], + %{duration: 222}, + Meta.router_dispatch_exception(:normal) + ) + + :telemetry.execute( + [:phoenix, :endpoint, :stop], + %{duration: 444}, + Meta.endpoint_stop(:exception) + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, + span( + name: "/users/:user_id/exception", + attributes: list, + kind: :server, + events: [ + event( + name: "exception", + attributes: [ + {"exception.type", "Elixir.ErlangError"}, + {"exception.message", "Erlang error: :badkey"}, + {"exception.stacktrace", _stacktrace}, + {:key, :name}, + {:map, %{username: "rick"}} + ] + ) + ], + parent_span_id: 13_235_353_014_750_950_193, + status: ^expected_status + )} + + assert [ + "http.client_ip": "10.211.55.2", + "http.flavor": :"1.1", + "http.host": "localhost", + "http.method": "GET", + "http.route": "/users/:user_id/exception", + "http.scheme": "http", + "http.status": 500, + "http.target": "/users/123/exception", + "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", + "net.host.ip": "10.211.55.2", + "net.host.port": 4000, + "net.peer.ip": "10.211.55.2", + "net.peer.port": 64291, + "net.transport": :"IP.TCP", + "phoenix.action": :code_exception, + "phoenix.plug": MyStoreWeb.PageController + ] == List.keysort(list, 0) + end + + test "records exceptions for Phoenix web requests with plug wrappers" do + OpentelemetryPhoenix.setup() + + :telemetry.execute( + [:phoenix, :endpoint, :start], + %{system_time: System.system_time()}, + Meta.endpoint_start(:exception) + ) + + :telemetry.execute( + [:phoenix, :router_dispatch, :start], + %{system_time: System.system_time()}, + Meta.router_dispatch_start(:exception) + ) + + :telemetry.execute( + [:phoenix, :router_dispatch, :exception], + %{duration: 222}, + Meta.router_dispatch_exception(:plug_wrapper) + ) + + :telemetry.execute( + [:phoenix, :endpoint, :stop], + %{duration: 444}, + Meta.endpoint_stop(:exception) + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, + span( + name: "/users/:user_id/exception", + attributes: list, + kind: :server, + events: [ + event( + name: "exception", + attributes: [ + {"exception.type", "Elixir.ArithmeticError"}, + {"exception.message", "bad argument in arithmetic expression"}, + {"exception.stacktrace", _stacktrace} + ] + ) + ], + parent_span_id: 13_235_353_014_750_950_193, + status: ^expected_status + )} + + assert [ + "http.client_ip": "10.211.55.2", + "http.flavor": :"1.1", + "http.host": "localhost", + "http.method": "GET", + "http.route": "/users/:user_id/exception", + "http.scheme": "http", + "http.status": 500, + "http.target": "/users/123/exception", + "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", + "net.host.ip": "10.211.55.2", + "net.host.port": 4000, + "net.peer.ip": "10.211.55.2", + "net.peer.port": 64291, + "net.transport": :"IP.TCP", + "phoenix.action": :code_exception, + "phoenix.plug": MyStoreWeb.PageController + ] == List.keysort(list, 0) + end + + defp x_forwarded_for_request(x_forwarded_for) do + meta = Meta.endpoint_start() + + meta = %{ + meta + | conn: %{ + meta.conn + | req_headers: [{"x-forwarded-for", x_forwarded_for} | meta.conn.req_headers] + } + } + + :telemetry.execute( + [:phoenix, :endpoint, :start], + %{system_time: System.system_time()}, + meta + ) + + :telemetry.execute( + [:phoenix, :endpoint, :stop], + %{duration: 444}, + Meta.endpoint_stop() + ) + end +end diff --git a/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex b/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex new file mode 100644 index 0000000..a598608 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex @@ -0,0 +1,1030 @@ +defmodule PhoenixMeta do + def router_dispatch_exception(:plug_wrapper) do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64921}, + pid: "", + port: 4000, + qs: "", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"user_id" => "123"}, + path_info: ["exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :plug_session_fetch => fn -> :ok end + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + error: %Plug.Conn.WrapperError{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64921}, + pid: "", + port: 4000, + qs: "", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123", "exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_action => :code_exception, + :phoenix_controller => MyStoreWeb.PageController, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_flash => %{}, + :phoenix_format => "html", + :phoenix_layout => {MyStoreWeb.LayoutView, :app}, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :phoenix_view => MyStoreWeb.PageView, + :plug_session => %{}, + :plug_session_fetch => :done + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"}, + {"x-frame-options", "SAMEORIGIN"}, + {"x-xss-protection", "1; mode=block"}, + {"x-content-type-options", "nosniff"}, + {"x-download-options", "noopen"}, + {"x-permitted-cross-domain-policies", "none"}, + {"cross-origin-window-policy", "deny"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + kind: :error, + reason: :badarith, + stack: [ + {MyStoreWeb.PageController, :code_exception, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 9]}, + {MyStoreWeb.PageController, :action, 2, [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {Phoenix.Router, :__call__, 2, [file: 'lib/phoenix/router.ex', line: 352]}, + {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: 'lib/plug/debugger.ex', line: 132]}, + {MyStoreWeb.Endpoint, :call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: 'lib/phoenix/endpoint/cowboy2_handler.ex', line: 65]}, + {:cowboy_handler, :execute, 2, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl', + line: 37 + ]}, + {:cowboy_stream_h, :execute, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 300 + ]}, + {:cowboy_stream_h, :request_process, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 291 + ]}, + {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]} + ] + }, + kind: :error, + reason: %Plug.Conn.WrapperError{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123", "exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_action => :code_exception, + :phoenix_controller => MyStoreWeb.PageController, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_flash => %{}, + :phoenix_format => "html", + :phoenix_layout => {MyStoreWeb.LayoutView, :app}, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :phoenix_view => MyStoreWeb.PageView, + :plug_session => %{}, + :plug_session_fetch => :done + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"}, + {"x-frame-options", "SAMEORIGIN"}, + {"x-xss-protection", "1; mode=block"}, + {"x-content-type-options", "nosniff"}, + {"x-download-options", "noopen"}, + {"x-permitted-cross-domain-policies", "none"}, + {"cross-origin-window-policy", "deny"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + kind: :error, + reason: :badarith, + stack: [ + {MyStoreWeb.PageController, :code_exception, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 9]}, + {MyStoreWeb.PageController, :action, 2, [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {Phoenix.Router, :__call__, 2, [file: 'lib/phoenix/router.ex', line: 352]}, + {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: 'lib/plug/debugger.ex', line: 132]}, + {MyStoreWeb.Endpoint, :call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: 'lib/phoenix/endpoint/cowboy2_handler.ex', line: 65]}, + {:cowboy_handler, :execute, 2, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl', + line: 37 + ]}, + {:cowboy_stream_h, :execute, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 300 + ]}, + {:cowboy_stream_h, :request_process, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 291 + ]}, + {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]} + ] + }, + stacktrace: [ + {MyStoreWeb.PageController, :code_exception, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 9]}, + {MyStoreWeb.PageController, :action, 2, [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, + [file: 'lib/my_store_web/controllers/page_controller.ex', line: 1]}, + {Phoenix.Router, :__call__, 2, [file: 'lib/phoenix/router.ex', line: 352]}, + {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: 'lib/plug/debugger.ex', line: 132]}, + {MyStoreWeb.Endpoint, :call, 2, [file: 'lib/my_store_web/endpoint.ex', line: 1]}, + {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: 'lib/phoenix/endpoint/cowboy2_handler.ex', line: 65]}, + {:cowboy_handler, :execute, 2, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl', + line: 37 + ]}, + {:cowboy_stream_h, :execute, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 300 + ]}, + {:cowboy_stream_h, :request_process, 3, + [ + file: '/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl', + line: 291 + ]}, + {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]} + ] + } + end + + def router_dispatch_exception(:normal) do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64921}, + pid: "", + port: 4000, + qs: "", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"user_id" => "123"}, + path_info: ["exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :plug_session_fetch => fn -> :ok end + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + kind: :error, + reason: { + :badkey, + :name, + %{ + username: "rick" + } + }, + stacktrace: [ + {MyStore.Users, :sort_by_name, 2, [file: 'lib/my_store/users.ex', line: 159]}, + {Enum, :"-to_sort_fun/1-fun-0-", 3, [file: 'lib/enum.ex', line: 2542]}, + {:lists, :sort, 2, [file: 'lists.erl', line: 969]} + ] + } + end + + def endpoint_stop do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 2, + version: :"HTTP/1.1" + }}, + assigns: %{layout: {MyStoreWeb.LayoutView, "app.html"}}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_action => :user, + :phoenix_controller => MyStoreWeb.PageController, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_flash => %{}, + :phoenix_format => "html", + :phoenix_layout => {MyStoreWeb.LayoutView, :app}, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :phoenix_template => "index.html", + :phoenix_view => MyStoreWeb.PageView, + :plug_session => %{}, + :plug_session_fetch => :done + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123", + resp_body: [ + "\n\n \n \n \n \n MyStore ยท Phoenix Framework\n \n \n \n \n
", + " \n \n\n" + ], + resp_cookies: %{}, + resp_headers: [ + {"content-type", "text/html; charset=utf-8"}, + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"}, + {"x-frame-options", "SAMEORIGIN"}, + {"x-xss-protection", "1; mode=block"}, + {"x-content-type-options", "nosniff"}, + {"x-download-options", "noopen"}, + {"x-permitted-cross-domain-policies", "none"}, + {"cross-origin-window-policy", "deny"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :set, + status: 200 + } + } + end + + def endpoint_stop(:exception) do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123", "exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_action => :code_exception, + :phoenix_controller => MyStoreWeb.PageController, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_flash => %{}, + :phoenix_format => "html", + :phoenix_layout => {MyStoreWeb.LayoutView, :app}, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :phoenix_view => MyStoreWeb.PageView, + :plug_session => %{}, + :plug_session_fetch => :done + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: + "\n\n\n \n ArithmeticError at GET /users/123/exception\n \n...", + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"}, + {"x-frame-options", "SAMEORIGIN"}, + {"x-xss-protection", "1; mode=block"}, + {"x-content-type-options", "nosniff"}, + {"x-download-options", "noopen"}, + {"x-permitted-cross-domain-policies", "none"}, + {"cross-origin-window-policy", "deny"}, + {"content-type", "text/html; charset=utf-8"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :set, + status: 500 + } + } + end + + def endpoint_start do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 2, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %Plug.Conn.Unfetched{aspect: :body_params}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1"}, + path_info: ["users", "123"], + path_params: %{}, + port: 4000, + private: %{ + phoenix_endpoint: MyStoreWeb.Endpoint, + phoenix_request_logger: {"request_logger", "request_logger"} + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + } + } + end + + def endpoint_start(:exception) do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %Plug.Conn.Unfetched{aspect: :body_params}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1"}, + path_info: ["users", "123", "exception"], + path_params: %{}, + port: 4000, + private: %{ + phoenix_endpoint: MyStoreWeb.Endpoint, + phoenix_request_logger: {"request_logger", "request_logger"} + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + } + } + end + + def router_dispatch_start do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 2, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :plug_session_fetch => fn -> :ok end + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + log: :debug, + path_params: %{"user_id" => "123"}, + pipe_through: [:browser], + plug: MyStoreWeb.PageController, + plug_opts: :user, + route: "/users/:user_id" + } + end + + def router_dispatch_start(:exception) do + %{ + conn: %Plug.Conn{ + adapter: + {Plug.Cowboy.Conn, + %{ + bindings: %{}, + body_length: 0, + cert: :undefined, + has_body: false, + headers: %{ + "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "accept-encoding" => "gzip, deflate", + "accept-language" => "en-US,en;q=0.5", + "cache-control" => "max-age=0", + "connection" => "keep-alive", + "host" => "localhost:4000", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE", + "upgrade-insecure-requests" => "1", + "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" + }, + host: "localhost", + host_info: :undefined, + method: "GET", + path: "/users/123/exception", + path_info: :undefined, + peer: {{10, 211, 55, 2}, 64291}, + pid: "", + port: 4000, + qs: "page=1", + ref: MyStoreWeb.Endpoint.HTTP, + scheme: "http", + sock: {{10, 211, 55, 2}, 4000}, + streamid: 1, + version: :"HTTP/1.1" + }}, + assigns: %{}, + before_send: [], + body_params: %{}, + cookies: %{}, + halted: false, + host: "localhost", + method: "GET", + owner: "", + params: %{"page" => "1", "user_id" => "123"}, + path_info: ["users", "123", "exception"], + path_params: %{"user_id" => "123"}, + port: 4000, + private: %{ + MyStoreWeb.Router => {[], %{}}, + :phoenix_endpoint => MyStoreWeb.Endpoint, + :phoenix_request_logger => {"request_logger", "request_logger"}, + :phoenix_router => MyStoreWeb.Router, + :plug_session_fetch => fn -> :ok end + }, + query_params: %{"page" => "1"}, + query_string: "page=1", + remote_ip: {10, 211, 55, 2}, + req_cookies: %{}, + req_headers: [ + {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, + {"accept-encoding", "gzip, deflate"}, + {"accept-language", "en-US,en;q=0.5"}, + {"cache-control", "max-age=0"}, + {"connection", "keep-alive"}, + {"host", "localhost:4000"}, + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"upgrade-insecure-requests", "1"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} + ], + request_path: "/users/123/exception", + resp_body: nil, + resp_cookies: %{}, + resp_headers: [ + {"cache-control", "max-age=0, private, must-revalidate"}, + {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"} + ], + scheme: :http, + script_name: [], + secret_key_base: "", + state: :unset, + status: nil + }, + log: :debug, + path_params: %{"user_id" => "123"}, + pipe_through: [:browser], + plug: MyStoreWeb.PageController, + plug_opts: :code_exception, + route: "/users/:user_id/exception" + } + end +end diff --git a/instrumentation/opentelemetry_phoenix/test/test_helper.exs b/instrumentation/opentelemetry_phoenix/test/test_helper.exs new file mode 100644 index 0000000..f9cd0a1 --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/test/test_helper.exs @@ -0,0 +1,9 @@ +# :application.load(:opentelemetry) +# :application.set_env(:opentelemetry, :tracer, :ot_tracer_default) +# :application.set_env(:opentelemetry, :processors, [{:ot_batch_processor, %{scheduled_delay_ms: 1}}]) + +# Application.ensure_all_started(:telemetry) +# Application.ensure_all_started(:opentelemetry) +# Application.ensure_all_started(:opentelemetry_phoenix) + +ExUnit.start() diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..de49573 --- /dev/null +++ b/mix.exs @@ -0,0 +1,17 @@ +defmodule OtelContribTests.MixProject do + use Mix.Project + + def project do + [ + app: :otel_contrib_tests, + version: "0.1.0", + deps: deps() + ] + end + + def deps do + [ + # {:opentelemetry, path: "apps/opentelemetry", only: :test, override: true} + ] + end +end