diff --git a/.github/labeler.yml b/.github/labeler.yml index 94dec14..9599c08 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -11,6 +11,9 @@ elixir: - examples/**/*.ex - examples/**/*.exs - examples/**/mix.lock + - utilities/**/*.ex + - utilities/**/*.exs + - utilities/**/mix.lock erlang: - instrumentation/**/*.erl @@ -25,6 +28,9 @@ erlang: - examples/**/*.erl - examples/**/*.hrl - examples/**/rebar.* + - utilities/**/*.erl + - utilities/**/*.hrl + - utilities/**/rebar.* instrumentation: - instrumentation/**/* @@ -35,6 +41,9 @@ propagators: examples: - examples/**/* +utilities: + - utilities/**/* + scope-ci: - .github/workflows/** @@ -46,3 +55,6 @@ opentelemetry_ecto: opentelemetry_phoenix: - instrumentation/opentelemetry_phoenix/**/* + +opentelemetry_telemetry: + - utilities/opentelemetry_telemetry/**/* diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index ca3d19b..b5dc615 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -104,3 +104,40 @@ jobs: run: mix format --check-formatted - name: Test run: mix test + + opentelemetry-telemetry: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) + env: + app: 'opentelemetry_telemetry' + defaults: + run: + working-directory: utilities/${{ env.app }} + runs-on: ubuntu-18.04 + name: Opentelemetry Telemetry 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@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: | + utilities/${{ env.app }}/deps + utilities/${{ env.app }}/_build + key: ${{ runner.os }}-build-${{ matrix.otp_version }}-${{ matrix.elixir_version }}-v3-${{ hashFiles(format('{0}{1}', github.workspace, 'utilities/${{ 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/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 4379164..ff5b1cb 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -51,3 +51,34 @@ jobs: run: rebar3 get-deps - name: Test run: rebar3 ct + + opentelemetry-telemetry: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'erlang') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) + env: + app: 'opentelemetry_telemetry' + defaults: + run: + working-directory: utilities/${{ env.app }} + runs-on: ubuntu-18.04 + name: Opentelemetry Telemetry test on OTP ${{ matrix.otp_version }} with Rebar3 ${{ matrix.rebar3_version }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ matrix.otp_version }} + rebar3-version: ${{ matrix.rebar3_version }} + - name: Cache + uses: actions/cache@v2 + with: + path: | + ${{ env.app }}/_build + key: ${{ runner.os }}-build-${{ matrix.otp_version }}-${{ matrix.rebar3_version }}-v3-${{ hashFiles(format('{0}{1}', github.workspace, 'utilities/${{ env.app }}/rebar.lock')) }} + - name: Fetch deps + if: steps.deps-cache.outputs.cache-hit != 'true' + run: rebar3 get-deps + - name: Test + run: rebar3 ct diff --git a/CODEOWNERS b/CODEOWNERS index f5987e1..542242b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,7 @@ @open-telemetry/erlang-approvers -/instrumentation/opentelemetry_cowboy @bryannaegele @tsloughter +/instrumentation/opentelemetry_cowboy @bryannaegele @tsloughter /instrumentation/opentelemetry_ecto @bryannaegele @tsloughter /instrumentation/opentelemetry_phoenix @bryannaegele @tsloughter +/utilities/opentelemetry_telemetry @bryannaegele @tsloughter diff --git a/utilities/opentelemetry_telemetry/.formatter.exs b/utilities/opentelemetry_telemetry/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/utilities/opentelemetry_telemetry/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/utilities/opentelemetry_telemetry/.gitignore b/utilities/opentelemetry_telemetry/.gitignore new file mode 100644 index 0000000..8d721ee --- /dev/null +++ b/utilities/opentelemetry_telemetry/.gitignore @@ -0,0 +1,32 @@ +edoc +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ +!_checkouts + +/_build +/cover +/deps +/doc +/.fetch +erl_crash.dump +*.ez +*.beam +/config/*.secret.exs +.elixir_ls/ diff --git a/utilities/opentelemetry_telemetry/CHANGELOG.md b/utilities/opentelemetry_telemetry/CHANGELOG.md new file mode 100644 index 0000000..4817d00 --- /dev/null +++ b/utilities/opentelemetry_telemetry/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +## 1.0.0-beta.4 + +### Changes + +* OpenTelemetry 1.0.0-rc.3 support + +## 1.0.0-beta.3 + +### Changes + +* telemetry v1.0.0 required + +## 1.0.0-beta.2 + +### Breaking Changes + +* The internal ctx handling mechanisms have been removed from the public API and replaced with more meaningful span operation functions to provide better abstractions diff --git a/utilities/opentelemetry_telemetry/LICENSE b/utilities/opentelemetry_telemetry/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/utilities/opentelemetry_telemetry/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/utilities/opentelemetry_telemetry/README.md b/utilities/opentelemetry_telemetry/README.md new file mode 100644 index 0000000..fd76c7d --- /dev/null +++ b/utilities/opentelemetry_telemetry/README.md @@ -0,0 +1,45 @@ +# OpentelemetryTelemetry + +A utility library for creating OpenTelemery spans from telemetry events. + +## Purpose + +Most libraries in the BEAM ecosystem leverage [telemetry](https://github.com/beam-telemetry/telemetry) events for exposing +event hook points for monitoring that library. While OpenTelemetry is a great project, +it is still one specification for monitoring software and it isn't +reasonable to ask library authors to support multiple conventions. + +OpentelemetryTelemetry provides mechanisms for otel instrumentation libraries +to leverage telemetry events for creating and managing spans. The instrumentation library +is then able to leverage the telemetry measurements and metadata for deriving +spans, adding attributes, set span names, etc. + +### What Opentelemetry is Not + +This library is only intended to provide utilities for working with telemetry +events to instrumentation libraries. As such, it should not be used directly +within your application code where the OpenTelemery API library should be leveraged. + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `opentelemetry_telemetry` to your list of dependencies: + +```erlang +{deps, [ + {opentelemetry_telemetry, "~> 1.0.0-beta.5"} +]}. +``` + +```elixir +def deps do + [ + {:opentelemetry_telemetry, "~> 1.0.0-beta.5"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/opentelemetry_telemetry](https://hexdocs.pm/opentelemetry_telemetry). + diff --git a/utilities/opentelemetry_telemetry/VERSION b/utilities/opentelemetry_telemetry/VERSION new file mode 100644 index 0000000..89e5b18 --- /dev/null +++ b/utilities/opentelemetry_telemetry/VERSION @@ -0,0 +1 @@ +1.0.0-beta.5 \ No newline at end of file diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/.gitignore b/utilities/opentelemetry_telemetry/_checkouts/test_app/.gitignore new file mode 100644 index 0000000..96f7b51 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/.gitignore @@ -0,0 +1,20 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ +!_checkouts diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/LICENSE b/utilities/opentelemetry_telemetry/_checkouts/test_app/LICENSE new file mode 100644 index 0000000..01f8d07 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/LICENSE @@ -0,0 +1,191 @@ + 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 + + Copyright 2020, Bryan Naegele . + + 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/utilities/opentelemetry_telemetry/_checkouts/test_app/README.md b/utilities/opentelemetry_telemetry/_checkouts/test_app/README.md new file mode 100644 index 0000000..c89e88c --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/README.md @@ -0,0 +1,9 @@ +test_app +===== + +An OTP application + +Build +----- + + $ rebar3 compile diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.config b/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.config new file mode 100644 index 0000000..a76d53b --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.config @@ -0,0 +1,33 @@ +{erl_opts, [debug_info]}. +{deps, [ + {test_child_app, "~> 0.1"} +]}. + +{profiles, [ + {test, [{erl_opts, [nowarn_export_all]}, + {deps, [ + {test_child_app, "~> 0.1"}, + {telemetry, "~> 1.0"} + ]}, + %% create junit xml for circleci + {ct_opts, [{ct_hooks, [cth_surefire]}]}, + {cover_enabled, true}, + {cover_opts, [verbose]}, + {paths, ["src", "test/support"]}, + %% convert to data codecov understands + {plugins, [covertool]}, + {covertool, [{coverdata_files, ["ct.coverdata"]}]} + ]}, + {docs, [{edoc_opts, [{preprocess, true}, + {title, "Test App"}]} + ]} +]}. + +{shell, [ + % {config, "config/sys.config"}, + {apps, [test_app]} +]}. + +%% take out warnings for unused exported functions +{xref_checks,[undefined_function_calls, undefined_functions, locals_not_used, + deprecated_function_calls, deprecated_functions]}. \ No newline at end of file diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.lock b/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.app.src b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.app.src new file mode 100644 index 0000000..7e833bd --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.app.src @@ -0,0 +1,16 @@ +{application, test_app, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {test_app_app, []}}, + {applications, + [kernel, + stdlib, + test_child_app + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.erl b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.erl new file mode 100644 index 0000000..dd90eb2 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app.erl @@ -0,0 +1,82 @@ +-module(test_app). + +-export([handler/1]). + +-telemetry_event #{ + event => [test_app, handler, start], + description => <<"Emitted at the start of the handler">>, + measurements => <<"#{system_time => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_app, handler, stop], + description => <<"Emitted at the end of the handler">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_app, handler, exception], + description => <<"The handler raised an exception">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{kind => atom(), reason => atom(), stacktrace => term()}">> + }. + +-telemetry_event #{ + event => [test_app, nested_span, start], + description => <<"Emitted at the start of the handler">>, + measurements => <<"#{system_time => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_app, nested_span, stop], + description => <<"Emitted at the end of the handler">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_app, nested_span, exception], + description => <<"The handler raised an exception">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{kind => atom(), reason => atom(), stacktrace => term()}">> + }. + + +-telemetry_event #{ + event => [test_app, only, stop], + description => <<"The handler raised an exception">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{kind => atom(), reason => atom(), stacktrace => term()}">> + }. + +-telemetry_event #{ + event => [test_app, cache, miss], + description => <<"Emitted at the start of the handler">>, + measurements => <<"#{system_time => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_app, cache, hit], + description => <<"Emitted at the end of the handler">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{}">> + }. + +handler(Args) -> + _ = telemetry:span( + [test_app, handler], + #{}, + fun() -> + case Args of + raise_exception -> + binary_to_list("heh, already a list"); + _ -> {nested_span(), #{}} + end + end). + +nested_span() -> + _ = telemetry:span( + [test_app, nested_span], + #{}, + fun() -> + {ok, #{}} + end). diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_app.erl b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_app.erl new file mode 100644 index 0000000..5cb1987 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_app.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%% @doc test_app public API +%% @end +%%%------------------------------------------------------------------- + +-module(test_app_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + test_app_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_sup.erl b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_sup.erl new file mode 100644 index 0000000..5a66cf5 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_app/src/test_app_sup.erl @@ -0,0 +1,38 @@ +%%%------------------------------------------------------------------- +%% @doc test_app top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(test_app_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== + +%% Child :: #{id => Id, start => {M, F, A}} +%% Optional keys are restart, shutdown, type, modules. +%% Before OTP 18 tuples must be used to specify a child. e.g. +%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} +init([]) -> + {ok, {{one_for_all, 0, 1}, []}}. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/.gitignore b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/.gitignore new file mode 100644 index 0000000..f1c4554 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/.gitignore @@ -0,0 +1,19 @@ +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/LICENSE b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/LICENSE new file mode 100644 index 0000000..01f8d07 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/LICENSE @@ -0,0 +1,191 @@ + 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 + + Copyright 2020, Bryan Naegele . + + 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/utilities/opentelemetry_telemetry/_checkouts/test_child_app/README.md b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/README.md new file mode 100644 index 0000000..e8a41a3 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/README.md @@ -0,0 +1,9 @@ +test_child_app +===== + +An OTP application + +Build +----- + + $ rebar3 compile diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.config b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.config new file mode 100644 index 0000000..16cf5d5 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.config @@ -0,0 +1,7 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{shell, [ + % {config, "config/sys.config"}, + {apps, [test_child_app]} +]}. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.lock b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.lock new file mode 100644 index 0000000..57afcca --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/rebar.lock @@ -0,0 +1 @@ +[]. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.app.src b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.app.src new file mode 100644 index 0000000..54d3c13 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.app.src @@ -0,0 +1,15 @@ +{application, test_child_app, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {test_child_app_app, []}}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.erl b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.erl new file mode 100644 index 0000000..73bcf99 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app.erl @@ -0,0 +1,27 @@ +-module(test_child_app). + +-telemetry_event #{ + event => [test_child_app, extra_long, start], + description => <<"Emitted at the start of the handler">>, + measurements => <<"#{system_time => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_child_app, extra_long, stop], + description => <<"Emitted at the end of the handler">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{}">> + }. + +-telemetry_event #{ + event => [test_child_app, cache, hit], + description => <<"Emitted at the start of the handler">>, + measurements => <<"#{system_time => non_neg_integer()}">>, + metadata => <<"#{}">> + }. +-telemetry_event #{ + event => [test_child_app, cache, miss], + description => <<"Emitted at the end of the handler">>, + measurements => <<"#{duration => non_neg_integer()}">>, + metadata => <<"#{}">> + }. diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_app.erl b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_app.erl new file mode 100644 index 0000000..fbc806f --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_app.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%% @doc test_child_app public API +%% @end +%%%------------------------------------------------------------------- + +-module(test_child_app_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + test_child_app_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_sup.erl b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_sup.erl new file mode 100644 index 0000000..b3df391 --- /dev/null +++ b/utilities/opentelemetry_telemetry/_checkouts/test_child_app/src/test_child_app_sup.erl @@ -0,0 +1,38 @@ +%%%------------------------------------------------------------------- +%% @doc test_child_app top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(test_child_app_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== + +%% Child :: #{id => Id, start => {M, F, A}} +%% Optional keys are restart, shutdown, type, modules. +%% Before OTP 18 tuples must be used to specify a child. e.g. +%% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} +init([]) -> + {ok, {{one_for_all, 0, 1}, []}}. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/utilities/opentelemetry_telemetry/lib/opentelemetry_telemetry.ex b/utilities/opentelemetry_telemetry/lib/opentelemetry_telemetry.ex new file mode 100644 index 0000000..f3112d9 --- /dev/null +++ b/utilities/opentelemetry_telemetry/lib/opentelemetry_telemetry.ex @@ -0,0 +1,121 @@ +defmodule OpentelemetryTelemetry do + @moduledoc """ + `OpentelemetryTelemetry` provides conveniences for leveraging `telemetry` + events for `OpenTelemetry` bridge libraries. + + ## OpenTelemetry Contexts + + `opentelemetry` does not automatically set current span context when ending + another span. Since `telemetry` events are executed in separate handlers with + no shared context, correlating individual events requires a mechanism to do so. + Additionally, when ending telemetry-based spans, the user must set the correct + parent context back as the current context. This ensures sibling spans are + correctly correlated to the shared parent span. + + This library provides helper functions to manage contexts automatically with + `start_telemetry_span/4`, `set_current_telemetry_span/2`, and `end_telemetry_span/2` + to give bridge library authors a mechanism for working with these challenges. Once + `start_telemetry_span/4` or `set_current_telemetry_span/2` are called, users + can use all of `OpenTelemetry` as normal. By providing the application tracer id + and the event's metadata, the provided span functions will identify and manage + span contexts automatically. + + ### Example Telemetry Event Handlers + + ``` + def handle_event(_event, + %{system_time: start_time}, + metadata, + %{type: :start, tracer_id: tracer_id, span_name: name}) do + start_opts = %{start_time: start_time} + OpentelemetryTelemetry.start_telemetry_span(tracer_id, name, metadata, start_opts) + :ok + end + + def handle_event(_event, + %{duration: duration}, + metadata, + %{type: :stop, tracer_id: tracer_id}) do + OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, metadata) + OpenTelemetry.Tracer.set_attribute(:duration, duration) + OpentelemetryTelemetry.end_telemetry_span(tracer_id, metadata) + :ok + end + + def handle_event(_event, + %{duration: duration}, + %{kind: kind, reason: reason, stacktrace: stacktrace} = metadata, + %{type: :exception, tracer_id: tracer_id}) do + ctx = OpentelemetryTelemetry.set_current_telemetry_span(tracer_id, metadata), + status = Opentelemetry.status(:error, to_string(reason, :utf8)) + OpenTelemetry.Span.record_exception(ctx, kind, reason, stacktrace, [duration: duration]) + OpenTelemetry.Tracer.set_status(status) + OpentelemetryTelemetry.end_telemetry_span(tracer_id, metadata) + :ok + end + def handle_event(_event, _measurements, _metadata, _config), do: :ok + + ``` + + ### Limitations + + Span contexts are currently stored in the process dictionary, so spans can only + be correlated within a single process at this time. This covers the primary use + case where library authors have implemented `telemetry:with_span` or the pattern + established in said function. Non-library authors should use opentelemetry directly + wherever possible. + + If the `event_metadata` includes a `telemetry_span_context` (introduced in telemetry + `v0.4.3`), contexts are correlated by the `telemetry_span_context` id to guarantee + the correct otel span context. Span events in earlier versions of `telemetry` are stored + in a stack by `tracer_id` to lessen the likelihood of inadvertently closing the wrong + span. + """ + + @typedoc """ + A span ctx for a telemetry-based span. + """ + @type telemetry_span_ctx() :: :opentelemetry.span_ctx() + + @typedoc """ + The parent span ctx for a telemetry-based span. This is what the current span ctx was + at the time of starting a telemetry-based span. + """ + @type parent_span_ctx() :: :opentelemetry.span_ctx() + + @type ctx_set() :: {parent_span_ctx(), telemetry_span_ctx()} + + @typep tracer_id() :: atom() + + @doc """ + Start a telemetry-based span. + """ + @spec start_telemetry_span( + tracer_id(), + :opentelemetry.span_name(), + :telemetry.event_metadata(), + OpenTelemetry.Tracer.start_opts() + ) :: OpenTelemetry.span_ctx() + defdelegate start_telemetry_span(tracer_id, span_name, event_metadata, start_opts), + to: :otel_telemetry + + @doc """ + Set the current span ctx based on the tracer_id and telemetry event metadata. + """ + @spec set_current_telemetry_span(tracer_id(), :telemetry.event_metadata()) :: + OpenTelemetry.span_ctx() + defdelegate set_current_telemetry_span(tracer_id, event_metadata), to: :otel_telemetry + + @doc """ + End a telemetry-based span based on the `tracer_id` and telemetry event metadata + and restore the current ctx to the span's parent ctx. + """ + @spec end_telemetry_span(tracer_id(), :telemetry.event_metadata()) :: :ok + defdelegate end_telemetry_span(tracer_id, event_metadata), to: :otel_telemetry + + @doc false + defdelegate trace_application(app), to: :otel_telemetry + + @doc false + defdelegate trace_application(app, opts), to: :otel_telemetry +end diff --git a/utilities/opentelemetry_telemetry/mix.exs b/utilities/opentelemetry_telemetry/mix.exs new file mode 100644 index 0000000..a91a44c --- /dev/null +++ b/utilities/opentelemetry_telemetry/mix.exs @@ -0,0 +1,101 @@ +defmodule OpentelemetryTelemetry.MixProject do + use Mix.Project + + def project do + {app, desc} = load_app() + config = load_config() + + [ + app: app, + version: version(Keyword.fetch!(desc, :vsn)), + description: to_string(Keyword.fetch!(desc, :description)), + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps(Keyword.fetch!(config, :deps)), + name: "Opentelemetry Telemetry", + source_url: + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_telemetry", + docs: [ + markdown_processor: ExDoc.Markdown.Earmark, + main: "OpentelemetryTelemetry", + # logo: "path/to/logo.png", + # erlang_docs() + extras: [] + ], + aliases: [ + # when build docs first build edocs with rebar3 + docs: ["cmd rebar3 edoc", "docs"] + ], + package: package() + ] + end + + defp version(version) when is_list(version) do + List.to_string(version) + end + + defp version({:file, path}) do + path + |> File.read!() + |> String.trim() + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + defp deps(rebar) do + rebar + |> Enum.map(fn + {dep, version} -> {dep, to_string(version)} + dep when is_atom(dep) -> {dep, ">= 0.0.0"} + end) + |> Enum.concat([ + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.25.3", only: :dev, runtime: false}, + {:opentelemetry, "~> 1.0.0-rc.3", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.0.0-rc.3", only: [:dev, :test]} + ]) + end + + defp package() do + [ + description: "Bridge library between Telemetry events and OpenTelemetry Erlang", + build_tools: ["rebar3", "mix"], + files: ~w(lib mix.exs README.md LICENSE CODEOWNERS rebar.config rebar.lock VERSION src), + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_telemetry", + "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 + + def erlang_docs() do + files = + for file <- Path.wildcard("edoc/*.md"), + file != "edoc/README.md", + do: {String.to_atom(file), [title: Path.basename(file, ".md")]} + + [{:"README.md", [title: "Overview"]} | files] + end + + defp load_config do + {:ok, config} = :file.consult('rebar.config') + + config + end + + defp load_app do + {:ok, [{:application, name, desc}]} = :file.consult('src/opentelemetry_telemetry.app.src') + + {name, desc} + end +end diff --git a/utilities/opentelemetry_telemetry/mix.lock b/utilities/opentelemetry_telemetry/mix.lock new file mode 100644 index 0000000..b40eebf --- /dev/null +++ b/utilities/opentelemetry_telemetry/mix.lock @@ -0,0 +1,24 @@ +%{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "chatterbox": {:hex, :ts_chatterbox, "0.11.0", "b8f372c706023eb0de5bf2976764edb27c70fe67052c88c1f6a66b3a5626847f", [:rebar3], [{:hpack, "~>0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "722fe2bad52913ab7e87d849fc6370375f0c961ffb2f0b5e6d647c9170c382a6"}, + "cmark": {:hex, :cmark, "0.9.0", "c0e673019e125eb5878b6c5610af919d737e881369e0cd2247c89e767f42c029", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0cfb4d8888bc801f2decc23eee3b3b756b682dff976d15a46861a596fdb00079"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "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.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, + "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.25.3", "3edf6a0d70a39d2eafde030b8895501b1c93692effcbd21347296c18e47618ce", [: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", "9ebebc2169ec732a38e9e779fd0418c9189b3ca93f4a676c961be6c1527913f5"}, + "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"}, + "grpcbox": {:hex, :grpcbox, "0.14.0", "3eb321bcd2275baf8b54cf381feb7b0559a50c02544de28fda039c7f2f9d1a7a", [:rebar3], [{:acceptor_pool, "~>1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~>0.11.0", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~>0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~>0.8.0", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "e24159b7b6d3f9869bbe528845c0125fed2259366ba908fd04a1f45fe81d0660"}, + "hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"}, + "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"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "opentelemetry": {:hex, :opentelemetry, "1.0.0-rc.3", "d2698bee882c354274563ee85d097bb736a9adb8d8ed376a4deea0cd3a14bb31", [:rebar3], [{:opentelemetry_api, "~> 1.0.0-rc.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "c9105933df0d783d94cf08d79206eb8d6578abc0bcbd498d0b497ec62a4e30a8"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.0.0-rc.3.1", "d183663c178f317a109a267b3c3664d09db22829a4d4eea8d9af46ed3e5bee05", [:mix, :rebar3], [], "hexpm", "4b836cec1b531080c310fa54afca6e523984a1f6c1aeb5d4da537dad9e309ce9"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.0.0-rc.3", "76f5657d4c94a12003d9ed2c8da1023c815e98f5553184dbb0cdaeec76db676d", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.0.0-rc.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.0.0-rc.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "267f0e4c3f1f5557cc7ad6ac71d66b8eaf7b3b56fde942c21f8a0bc96174fe1e"}, + "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, + "telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"}, +} diff --git a/utilities/opentelemetry_telemetry/rebar.config b/utilities/opentelemetry_telemetry/rebar.config new file mode 100644 index 0000000..f40c109 --- /dev/null +++ b/utilities/opentelemetry_telemetry/rebar.config @@ -0,0 +1,32 @@ +{erl_opts, [debug_info]}. +{deps, [ + {opentelemetry_api, "~> 1.0.0-rc.3"}, + {telemetry, "~> 1.0"}, + {telemetry_registry, "~> 0.3.0"} +]}. + +{project_plugins, [covertool, + erlfmt]}. +{profiles, + [{docs, [{deps, [edown]}, + {edoc_opts, + [{doclet, edown_doclet}, + {preprocess, true}, + {dir, "edoc"}, + {subpackages, true}]}]}, + {test, [{erl_opts, [nowarn_export_all]}, + {deps, [ + {opentelemetry, "~> 1.0.0-rc.3"}, + {opentelemetry_exporter, "~> 1.0.0-rc.3"}, + {test_app, "~> 0.1"} + ]}, + {paths, ["src", "test/support"]}, + {ct_opts, [{ct_hooks, [cth_surefire]}]}]}]}. + +{xref_checks, [undefined_function_calls, undefined_functions, + deprecated_function_calls, deprecated_functions]}. +{xref_ignores, []}. + +{cover_enabled, true}. +{cover_export_enabled, true}. +{covertool, [{coverdata_files, ["ct.coverdata"]}]}. \ No newline at end of file diff --git a/utilities/opentelemetry_telemetry/rebar.lock b/utilities/opentelemetry_telemetry/rebar.lock new file mode 100644 index 0000000..b28f00b --- /dev/null +++ b/utilities/opentelemetry_telemetry/rebar.lock @@ -0,0 +1,14 @@ +{"1.2.0", +[{<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.0.0-rc.3.1">>},0}, + {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.0.0">>},0}, + {<<"telemetry_registry">>,{pkg,<<"telemetry_registry">>,<<"0.3.0">>},0}]}. +[ +{pkg_hash,[ + {<<"opentelemetry_api">>, <<"D183663C178F317A109A267B3C3664D09DB22829A4D4EEA8D9AF46ED3E5BEE05">>}, + {<<"telemetry">>, <<"0F453A102CDF13D506B7C0AB158324C337C41F1CC7548F0BC0E130BBF0AE9452">>}, + {<<"telemetry_registry">>, <<"6768F151EA53FC0FBCA70DBFF5B20A8D663EE4E0C0B2AE589590E08658E76F1E">>}]}, +{pkg_hash_ext,[ + {<<"opentelemetry_api">>, <<"4B836CEC1B531080C310FA54AFCA6E523984A1F6C1AEB5D4DA537DAD9E309CE9">>}, + {<<"telemetry">>, <<"73BC09FA59B4A0284EFB4624335583C528E07EC9AE76ACA96EA0673850AEC57A">>}, + {<<"telemetry_registry">>, <<"492E2ADBC609F3E79ECE7F29FEC363A97A2C484AC78A83098535D6564781E917">>}]} +]. diff --git a/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src b/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src new file mode 100644 index 0000000..5a16aac --- /dev/null +++ b/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src @@ -0,0 +1,18 @@ +{application, opentelemetry_telemetry, + [{description, "Telemetry to OpenTelemetry Bridge"}, + {vsn, {file, "VERSION"}}, + {registered, []}, + {applications, + [kernel, + stdlib, + opentelemetry, + opentelemetry_api, + telemetry, + telemetry_registry + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache-2.0"]}, + {links, [{"GitHub", "https://github.com/opentelemetry-beam/opentelemetry_telemetry"}]} + ]}. diff --git a/utilities/opentelemetry_telemetry/src/otel_telemetry.erl b/utilities/opentelemetry_telemetry/src/otel_telemetry.erl new file mode 100644 index 0000000..58762e6 --- /dev/null +++ b/utilities/opentelemetry_telemetry/src/otel_telemetry.erl @@ -0,0 +1,167 @@ +-module(otel_telemetry). + +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). + +-export([ + init/1, + init/2, + handle_event/4, + start_telemetry_span/4, + set_current_telemetry_span/2, + end_telemetry_span/2, + trace_application/1, + trace_application/2]). + +-type telemetry_span_ctx() :: opentelemetry:span_ctx(). +-type parent_span_ctx() :: opentelemetry:span_ctx(). + +-type ctx_set() :: {parent_span_ctx(), telemetry_span_ctx()}. + +-spec init(atom()) -> ok. +init(Application) -> + init(Application, []). + +-spec init(atom(), []) -> ok. +init(_Application, _Opts) -> + ok. + +trace_application(Application) -> + trace_application(Application, []). + +trace_application(Application, _Opts) -> + _ = telemetry_registry:discover_all([Application]), + AllEvents = telemetry_registry:list_events(), + SpannableEvents = telemetry_registry:spannable_events(), + _ = register_event_handlers(SpannableEvents, AllEvents), + ok. + +-spec start_telemetry_span(atom(), opentelemetry:span_name(), telemetry:event_metadata(), otel_span:start_opts()) -> opentelemetry:span_ctx(). +start_telemetry_span(TracerId, SpanName, EventMetadata, Opts) -> + ParentCtx = otel_tracer:current_span_ctx(), + Tracer = opentelemetry:get_tracer(TracerId), + Ctx = otel_tracer:start_span(Tracer, SpanName, Opts), + otel_tracer:set_current_span(Ctx), + _ = store_ctx({ParentCtx, Ctx}, TracerId, EventMetadata), + Ctx. + +-spec set_current_telemetry_span(atom(), telemetry:event_metadata()) -> opentelemetry:span_ctx() | undefined. +set_current_telemetry_span(TracerId, EventMetadata) -> + case fetch_telemetry_span_ctx(TracerId, EventMetadata) of + {_ParentCtx, Ctx} -> + otel_tracer:set_current_span(Ctx), + Ctx; + undefined -> + undefined + end. + +-spec end_telemetry_span(atom(), telemetry:event_metadata()) -> ok. +end_telemetry_span(TracerId, EventMetadata) -> + {ParentCtx, Ctx} = pop_ctx(TracerId, EventMetadata), + otel_span:end_span(Ctx), + otel_tracer:set_current_span(ParentCtx), + ok. + +-spec store_ctx(ctx_set(), atom(), telemetry:event_metadata()) -> ok. +store_ctx(SpanCtxSet, TracerId, EventMetadata) -> + case maps:get(telemetry_span_context, EventMetadata, undefined) of + undefined -> + push_to_tracer_stack(SpanCtxSet, TracerId); + TelemetryCtx -> + erlang:put({otel_telemetry, TelemetryCtx}, SpanCtxSet) + end, + ok. + +-spec push_to_tracer_stack(ctx_set(), atom()) -> ok. +push_to_tracer_stack(SpanCtxSet, TracerId) -> + case erlang:get({otel_telemetry, TracerId}) of + undefined -> + erlang:put({otel_telemetry, TracerId}, [SpanCtxSet]); + Stack -> + erlang:put({otel_telemetry, TracerId}, [SpanCtxSet | Stack]) + end. + +-spec fetch_telemetry_span_ctx(atom(), telemetry:event_metadata()) -> ctx_set() | undefined. +fetch_telemetry_span_ctx(TracerId, EventMetadata) -> + case maps:get(telemetry_span_context, EventMetadata, undefined) of + undefined -> + peek_from_tracer_stack(TracerId); + TelemetryCtx -> + erlang:get({otel_telemetry, TelemetryCtx}) + end. + +-spec peek_from_tracer_stack(atom()) -> ctx_set() | undefined. +peek_from_tracer_stack(TracerId) -> + case erlang:get({otel_telemetry, TracerId}) of + undefined -> + undefined; + [SpanCtxSet | _Rest] -> + SpanCtxSet + end. + +-spec pop_ctx(atom(), telemetry:event_metadata()) -> ctx_set(). +pop_ctx(TracerId, EventMetadata) -> + case maps:get(telemetry_span_context, EventMetadata, undefined) of + undefined -> + pop_from_tracer_stack(TracerId); + TelemetryCtx -> + erlang:erase({otel_telemetry, TelemetryCtx}) + end. + +pop_from_tracer_stack(TracerId) -> + case erlang:get({otel_telemetry, TracerId}) of + undefined -> + undefined; + [SpanCtxSet | Rest] -> + erlang:put({otel_telemetry, TracerId}, Rest), + SpanCtxSet + end. + +register_event_handlers(SpannableEvents, AllEvents) -> + lists:foldl(fun ({Prefix, Suffixes}, Handlers) -> + TracerId = tracer_id_for_events(Prefix, Suffixes, AllEvents), + NewHandlers = [attach_handler(Prefix, Suffix, TracerId) + || Suffix <- Suffixes], + NewHandlers ++ Handlers + end, + [], + SpannableEvents). + +attach_handler(Prefix, Suffix, TracerId) -> + Event = Prefix ++ [Suffix], + SpanName = list_to_binary(lists:join("_", + [atom_to_binary(Segment, utf8) || Segment <- Prefix])), + Config = #{tracer_id => TracerId, type => Suffix, span_name => SpanName}, + Handler = fun ?MODULE:handle_event/4, + telemetry:attach({?MODULE, Event}, Event, Handler, Config). + +tracer_id_for_events(Prefix, [Suffix | _], AllEvents) -> + Event = Prefix ++ [Suffix], + {Event, Module, _Metadata} = lists:keyfind(Event, 1, AllEvents), + Module. + +handle_event(_Event, + _Measurements, + Metadata, + #{type := start, tracer_id := TracerId, span_name := Name}) -> + _Ctx = start_telemetry_span(TracerId, Name, Metadata, #{}), + ok; +handle_event(_Event, + _Measurements, + Metadata, + #{type := stop, tracer_id := TracerId}) -> + Ctx = set_current_telemetry_span(TracerId, Metadata), + end_telemetry_span(TracerId, Metadata), + ok; +handle_event(_Event, + _Measurements, + #{kind := Kind, reason := Reason, stacktrace := Stacktrace} = Metadata, + #{type := exception, tracer_id := TracerId}) -> + Ctx = set_current_telemetry_span(TracerId, Metadata), + Status = opentelemetry:status(?OTEL_STATUS_ERROR, atom_to_binary(Reason, utf8)), + otel_span:record_exception(Ctx, Kind, Reason, Stacktrace, []), + otel_span:set_status(Ctx, Status), + end_telemetry_span(TracerId, Metadata), + ok; +handle_event(_Event, _Measurements, _Metadata, _Config) -> + ok. + diff --git a/utilities/opentelemetry_telemetry/test/opentelemetry_telemetry_test.exs b/utilities/opentelemetry_telemetry/test/opentelemetry_telemetry_test.exs new file mode 100644 index 0000000..12695d0 --- /dev/null +++ b/utilities/opentelemetry_telemetry/test/opentelemetry_telemetry_test.exs @@ -0,0 +1,3 @@ +defmodule OpentelemetryTelemetryTest do + use ExUnit.Case +end diff --git a/utilities/opentelemetry_telemetry/test/otel_telemetry_SUITE.erl b/utilities/opentelemetry_telemetry/test/otel_telemetry_SUITE.erl new file mode 100644 index 0000000..ec4e414 --- /dev/null +++ b/utilities/opentelemetry_telemetry/test/otel_telemetry_SUITE.erl @@ -0,0 +1,83 @@ +-module(otel_telemetry_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). +-include_lib("opentelemetry/include/otel_span.hrl"). +-include_lib("opentelemetry_api/include/otel_tracer.hrl"). + +all() -> [ + telemetry_span_handling + ]. + +init_per_suite(Config) -> + ok = application:load(opentelemetry_telemetry), + ok = application:load(opentelemetry), + application:set_env(opentelemetry, processors, [{otel_batch_processor, #{scheduled_delay_ms => 1}}]), + Config. + +end_per_suite(_Config) -> + ok = application:unload(opentelemetry), + ok. + +init_per_testcase(_, Config) -> + {ok, _} = application:ensure_all_started(telemetry), + {ok, _} = application:ensure_all_started(telemetry_registry), + {ok, _} = application:ensure_all_started(test_app), + {ok, _} = application:ensure_all_started(opentelemetry_telemetry), + otel_batch_processor:set_exporter(otel_exporter_pid, self()), + otel_telemetry:trace_application(test_app), + opentelemetry:register_tracer(test_tracer, "0.1.0"), + Config. + +end_per_testcase(_, Config) -> + application:stop(telemetry), + application:stop(telemetry_registry), + application:stop(test_app), + application:stop(opentelemetry_telemetry), + application:stop(opentelemetry), + Config. + +telemetry_span_handling(_Config) -> + SpanCtx1 = ?start_span(<<"span-1">>), + ?set_current_span(SpanCtx1), + _Result = test_app:handler(ok), + ?assertMatch(SpanCtx1, ?current_span_ctx), + try test_app:handler(raise_exception) of + _ -> ok + catch + error:badarg -> ok + end, + ?assertMatch(SpanCtx1, ?current_span_ctx), + ?set_attribute(<<"attribute">>, 1), + ?end_span(), + {_, Span3Parent} = successful_span_listener(<<"test_app_nested_span">>), + {Span2, Span2Parent} = successful_span_listener(<<"test_app_handler">>), + {_, ExceptionSpanParent} = exception_span_listener(<<"test_app_handler">>), + {Span1, undefined} = successful_span_listener(<<"span-1">>), + ?assertEqual(Span2Parent, Span1), + ?assertEqual(ExceptionSpanParent, Span1), + ?assertEqual(Span3Parent, Span2), + ok. + +successful_span_listener(Name) -> + receive + {span, #span{name=Name,attributes=Attributes,parent_span_id=ParentId,span_id=Id}} -> + {Id, ParentId} + after + 5000 -> + error(timeout) + end. + +exception_span_listener(Name) -> + receive + {span, #span{name=Name,events=Events,status=Status,parent_span_id=ParentId,span_id=Id}} -> + ?assertEqual({status,error,<<"badarg">>}, Status), + ?assertEqual(1, erlang:length(Events)), + {Id, ParentId} + after + 5000 -> + error(timeout) + end. diff --git a/utilities/opentelemetry_telemetry/test/test_helper.exs b/utilities/opentelemetry_telemetry/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/utilities/opentelemetry_telemetry/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()