From de17c311940e09783d27c09e58435cae31c9e987 Mon Sep 17 00:00:00 2001 From: Bryan Naegele Date: Fri, 31 Mar 2023 09:51:27 -0600 Subject: [PATCH] OpentelemetryReq plugin (#160) * OpentelemetryReq plugin * Split span handling * version bump * Remove set * Unused var * Set parent ctx back on completion --- .github/labeler.yml | 5 +- .github/workflows/elixir.yml | 37 +++ README.md | 1 + .../opentelemetry_req/.formatter.exs | 4 + instrumentation/opentelemetry_req/.gitignore | 26 +++ .../opentelemetry_req/CHANGELOG.md | 20 ++ instrumentation/opentelemetry_req/LICENSE | 201 +++++++++++++++++ instrumentation/opentelemetry_req/README.md | 19 ++ .../lib/opentelemetry_req.ex | 211 ++++++++++++++++++ instrumentation/opentelemetry_req/mix.exs | 63 ++++++ instrumentation/opentelemetry_req/mix.lock | 20 ++ .../test/opentelemetry_req_test.exs | 4 + .../opentelemetry_req/test/test_helper.exs | 1 + 13 files changed, 611 insertions(+), 1 deletion(-) create mode 100644 instrumentation/opentelemetry_req/.formatter.exs create mode 100644 instrumentation/opentelemetry_req/.gitignore create mode 100644 instrumentation/opentelemetry_req/CHANGELOG.md create mode 100644 instrumentation/opentelemetry_req/LICENSE create mode 100644 instrumentation/opentelemetry_req/README.md create mode 100644 instrumentation/opentelemetry_req/lib/opentelemetry_req.ex create mode 100644 instrumentation/opentelemetry_req/mix.exs create mode 100644 instrumentation/opentelemetry_req/mix.lock create mode 100644 instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs create mode 100644 instrumentation/opentelemetry_req/test/test_helper.exs diff --git a/.github/labeler.yml b/.github/labeler.yml index ceb593a..e125a39 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -68,6 +68,9 @@ opentelemetry_process_propagator: opentelemetry_redix: - instrumentation/opentelemetry_redix/**/* +opentelemetry_req: + - instrumentation/opentelemetry_req/**/* + opentelemetry_finch: - instrumentation/opentelemetry_finch/**/* @@ -78,4 +81,4 @@ opentelemetry_tesla: - instrumentation/opentelemetry_tesla/**/* opentelemetry_xray: - - utilities/opentelemetry_xray/**/* \ No newline at end of file + - utilities/opentelemetry_xray/**/* diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 60371e3..51556cd 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -188,6 +188,43 @@ jobs: - name: Test run: mix test + opentelemetry-req: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_req')) + env: + app: 'opentelemetry_req' + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-20.04 + name: Opentelemetry Req test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v3 + - 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@v3 + with: + path: | + ~/deps + ~/_build + key: ${{ runner.os }}-build-${{ matrix.otp_version }}-${{ matrix.elixir_version }}-v3-${{ hashFiles('**/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 + opentelemetry-finch: needs: [test-matrix] if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_finch')) diff --git a/README.md b/README.md index 9da9fdd..8b8f008 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ OpenTelemetry can collect tracing data using instrumentation. Vendors/Users can - [opentelemetry-cowboy](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_cowboy) - [opentelemetry-phoenix](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_phoenix) - [opentelemetry-ecto](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_ecto) +- [opentelemetry-req](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_req) ## Supported Runtimes diff --git a/instrumentation/opentelemetry_req/.formatter.exs b/instrumentation/opentelemetry_req/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/instrumentation/opentelemetry_req/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/instrumentation/opentelemetry_req/.gitignore b/instrumentation/opentelemetry_req/.gitignore new file mode 100644 index 0000000..2380898 --- /dev/null +++ b/instrumentation/opentelemetry_req/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +opentelemetry_req-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/instrumentation/opentelemetry_req/CHANGELOG.md b/instrumentation/opentelemetry_req/CHANGELOG.md new file mode 100644 index 0000000..775badb --- /dev/null +++ b/instrumentation/opentelemetry_req/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +## 0.1.2 + +### Fixes + +* Fix ctx not being set back to parent upon completion + +## 0.1.1 + +### Fixes + +* Fix client span to be the ctx injected to headers + +## 0.1.0 + +### Features + +* Initial release + diff --git a/instrumentation/opentelemetry_req/LICENSE b/instrumentation/opentelemetry_req/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/instrumentation/opentelemetry_req/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_req/README.md b/instrumentation/opentelemetry_req/README.md new file mode 100644 index 0000000..615baa5 --- /dev/null +++ b/instrumentation/opentelemetry_req/README.md @@ -0,0 +1,19 @@ +# OpentelemetryReq + +[![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_req) + +[Req](https://hex.pm/packages/req) plugin for OpenTelemetry instrumentation and propagation. + +See [Docs](https://hex.pm/packages/opentelemetry_req) for usage instructions. + +## Installation + +```elixir +def deps do + [ + {:opentelemetry_req, "~> 0.1.0"} + ] +end +``` + diff --git a/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex b/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex new file mode 100644 index 0000000..a0675e6 --- /dev/null +++ b/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex @@ -0,0 +1,211 @@ +defmodule OpentelemetryReq do + @moduledoc """ + Wraps the request in an opentelemetry span. Span names must be paramaterized, so the + `req_path_params` module and step should be registered before this step. This step is + expected by default and an error will be raised if the path params option is + not set for the request. + + Given the steps pipeline can be halted to skip further steps from running, it is important + to _append_ request and response steps _after_ this step to ensure execution. Spans are not + created until the request is completed or errored. + + ### Example + + ``` + client = + Req.new() + |> OpentelemetryReq.attach() + |> Req.Request.merge_options( + base_url: "http://localhost:4000", + propagate_trace_ctx: true + ) + + client + |> Req.get( + url: "/api/users/:user_id", + path_params: [user_id: user_id] + ) + ``` + ## Request Options + + * `:span_name` - `String.t()` if provided, overrides the span name. Defaults to `nil`. + * `:no_path_params` - `boolean()` when set to `true` no path params are expected for the request. Defaults to `false` + * `:propagate_trace_ctx` - `boolean()` when set to `true`, trace headers will be propagated. Defaults to `false` + """ + + alias OpenTelemetry.Tracer + alias OpenTelemetry.SemanticConventions.Trace + require Trace + require Tracer + require Logger + + def attach(%Req.Request{} = request, options \\ []) do + request + |> Req.Request.register_options([:span_name, :no_path_params, :propagate_trace_ctx]) + |> Req.Request.merge_options(options) + |> Req.Request.append_request_steps( + require_path_params: &require_path_params_option/1, + start_span: &start_span/1, + put_trace_headers: &maybe_put_trace_headers/1 + ) + |> Req.Request.prepend_response_steps(otel_end_span: &end_span/1) + |> Req.Request.prepend_error_steps(otel_end_span: &end_errored_span/1) + end + + defp start_span(request) do + span_name = span_name(request) + + attrs = build_req_attrs(request) + + parent_ctx = OpenTelemetry.Ctx.get_current() + Process.put(:otel_parent_ctx, parent_ctx) + + Tracer.start_span(span_name, %{ + attributes: attrs, + kind: :client + }) + |> Tracer.set_current_span() + + request + end + + defp end_span({request, %Req.Response{} = response}) do + attrs = + Map.put(%{}, Trace.http_status_code(), response.status) + |> maybe_append_resp_content_length(response) + + Tracer.set_attributes(attrs) + + if response.status >= 400 do + OpenTelemetry.Tracer.set_status(OpenTelemetry.status(:error, "")) + end + + OpenTelemetry.Tracer.end_span() + + Process.delete(:otel_parent_ctx) + |> OpenTelemetry.Ctx.attach() + + {request, response} + end + + defp end_errored_span({request, exception}) do + OpenTelemetry.Tracer.set_status(OpenTelemetry.status(:error, format_exception(exception))) + + OpenTelemetry.Tracer.end_span() + + Process.delete(:otel_parent_ctx) + |> OpenTelemetry.Ctx.attach() + + {request, exception} + end + + defp format_exception(%{__exception__: true} = exception) do + Exception.message(exception) + end + + defp format_exception(_), do: "" + + defp span_name(request) do + Req.Request.get_private(request, :path_params_template) + end + + defp build_req_attrs(request) do + uri = request.url + url = url(uri) + + %{ + Trace.http_method() => http_method(request.method), + Trace.http_url() => url, + Trace.http_target() => uri.path, + Trace.net_host_name() => uri.host, + Trace.http_scheme() => uri.scheme + } + |> maybe_append_req_content_length(request) + |> maybe_append_retry_count(request) + end + + defp maybe_append_req_content_length(attrs, req) do + case Req.Request.get_header(req, "content-length") do + [] -> + attrs + + [length] -> + Map.put(attrs, Trace.http_request_content_length(), length) + end + end + + defp maybe_append_resp_content_length(attrs, req) do + case Req.Response.get_header(req, "content-length") do + [] -> + attrs + + [length] -> + Map.put(attrs, Trace.http_response_content_length(), length) + end + end + + defp maybe_append_retry_count(attrs, req) do + retry_count = Req.Request.get_private(req, :req_retry_count, 0) + + if retry_count > 0 do + Map.put(attrs, Trace.http_retry_count(), retry_count) + else + attrs + end + end + + defp url(uri) do + if uri.query do + uri.path <> "?" <> uri.query + else + uri.path + end + end + + defp http_method(method) do + case method do + :get -> :GET + :head -> :HEAD + :post -> :POST + :patch -> :PATCH + :put -> :PUT + :delete -> :DELETE + :connect -> :CONNECT + :options -> :OPTIONS + :trace -> :TRACE + end + end + + defp maybe_put_trace_headers(request) do + if request.options[:propagate_trace_ctx] do + Map.put(request, :headers, :otel_propagator_text_map.inject(request.headers)) + else + request + end + end + + defp require_path_params_option(request) do + unless request.options[:no_path_params] do + unless Map.has_key?(request.options, :path_params) do + {Req.Request.halt(request), __MODULE__.PathParamsOptionError.new()} + else + request + end + else + request + end + end + + defmodule PathParamsOptionError do + defexception [:message] + + def new do + %__MODULE__{} + end + + @impl true + def message(_) do + "req_path_params path parameter options must be provided" + end + end +end diff --git a/instrumentation/opentelemetry_req/mix.exs b/instrumentation/opentelemetry_req/mix.exs new file mode 100644 index 0000000..7ee372b --- /dev/null +++ b/instrumentation/opentelemetry_req/mix.exs @@ -0,0 +1,63 @@ +defmodule OpentelemetryReq.MixProject do + use Mix.Project + + def project do + [ + app: :opentelemetry_req, + description: description(), + version: "0.1.2", + elixir: "~> 1.12", + start_permanent: Mix.env() == :prod, + deps: deps(), + name: "Opentelemetry Req", + docs: [ + main: "OpentelemetryReq" + ], + elixirc_paths: elixirc_paths(Mix.env()), + package: package(), + source_url: + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_req" + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + defp description do + "Trace Req requests with OpenTelemetry." + end + + defp package do + [ + description: "OpenTelemetry tracing for Req", + files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*), + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_req", + "OpenTelemetry Erlang" => "https://github.com/open-telemetry/opentelemetry-erlang", + "OpenTelemetry Erlang Contrib" => + "https://github.com/open-telemetry/opentelemetry-erlang-contrib", + "OpenTelemetry.io" => "https://opentelemetry.io" + } + ] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:jason, "~> 1.3"}, + {:opentelemetry_api, "~> 1.0"}, + {:opentelemetry_semantic_conventions, "~> 0.2"}, + {:req, ">= 0.3.5"}, + {:ex_doc, "~> 0.29", only: [:dev, :test]} + ] + end +end diff --git a/instrumentation/opentelemetry_req/mix.lock b/instrumentation/opentelemetry_req/mix.lock new file mode 100644 index 0000000..87c69d2 --- /dev/null +++ b/instrumentation/opentelemetry_req/mix.lock @@ -0,0 +1,20 @@ +%{ + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, + "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [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", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"}, + "finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, + "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.1", "7b69ed4f40025c005de0b74fce8c0549625d59cb4df12d15c32fe6dc5076ff42", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "6d7a27b7cad2ad69a09cabf6670514cafcec717c8441beb5c96322bac3d05350"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, + "req": {:hex, :req, "0.3.6", "541350d2cc359a8ad17059f2629c18be56d0c85ce0e4ddb27694b6ba482fe923", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "9181047f32b05f8737f6b5917af5ee5385219158bbe4e507f4ec57791a0a78c3"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, +} diff --git a/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs b/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs new file mode 100644 index 0000000..e44c5a5 --- /dev/null +++ b/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs @@ -0,0 +1,4 @@ +defmodule OpentelemetryReqTest do + use ExUnit.Case + doctest OpentelemetryReq +end diff --git a/instrumentation/opentelemetry_req/test/test_helper.exs b/instrumentation/opentelemetry_req/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/instrumentation/opentelemetry_req/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()