diff --git a/.github/elixir-test-matrix.json b/.github/elixir-test-matrix.json index 78fc67db..49fe2133 100644 --- a/.github/elixir-test-matrix.json +++ b/.github/elixir-test-matrix.json @@ -1,14 +1,19 @@ { - "otp_version": ["26.2.5", "25.3.2.12"], - "elixir_version": ["1.16.3", "1.14.5"], - "rebar3_version": ["3.22.1"], - "os": ["ubuntu-22.04"], + "otp_version": ["27.2.4", "25.3.2.16"], + "elixir_version": ["1.18.2", "1.15.8"], + "rebar3_version": ["3.24.0"], + "os": ["ubuntu-24.04"], "include": [ { - "elixir_version": "1.17.1", - "otp_version": "27.0", + "elixir_version": "1.18.2", + "otp_version": "27.2.4", "check_formatted": true } ], - "exclude": [] + "exclude": [ + { + "elixir_version": "1.15.8", + "otp_version": "27.2.4" + } + ] } diff --git a/.github/erlang-test-matrix.json b/.github/erlang-test-matrix.json index 31fbb102..43ce41e7 100644 --- a/.github/erlang-test-matrix.json +++ b/.github/erlang-test-matrix.json @@ -1,5 +1,5 @@ { - "otp_version": ["27.0", "26.2.5", "25.3.2.12"], - "rebar3_version": ["3.23.0"], - "os": ["ubuntu-22.04"] + "otp_version": ["27.2", "25.3.2.16"], + "rebar3_version": ["3.24.0"], + "os": ["ubuntu-24.04"] } diff --git a/.github/hex-packages.json b/.github/hex-packages.json index 23ff6cf8..f261ca66 100644 --- a/.github/hex-packages.json +++ b/.github/hex-packages.json @@ -6,7 +6,25 @@ "tagPrefix": "opentelemetry-aws-xray-v", "buildTool": "rebar3", "language": "erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] + }, + "bandit": { + "workingDirectory": "instrumentation/opentelemetry_bandit", + "name": "Bandit Instrumentation", + "packageName": "opentelemetry_bandit", + "tagPrefix": "opentelemetry-bandit-v", + "buildTool": "mix", + "language": "elixir", + "authorizedUsers": ["bryannaegele", "tsloughter"] + }, + "broadway": { + "workingDirectory": "instrumentation/opentelemetry_broadway", + "name": "Broadway Instrumentation", + "packageName": "opentelemetry_broadway", + "tagPrefix": "opentelemetry-broadway-v", + "buildTool": "mix", + "language": "elixir", + "authorizedUsers": ["bryannaegele", "tomtaylor", "tsloughter", "whatyouhide"] }, "cowboy": { "workingDirectory": "instrumentation/opentelemetry_cowboy", @@ -15,7 +33,7 @@ "tagPrefix": "opentelemetry-cowboy-v", "buildTool": "rebar3", "language": "erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "dataloader": { "workingDirectory": "instrumentation/opentelemetry_dataloader", @@ -24,7 +42,7 @@ "tagPrefix": "opentelemetry-dataloader-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "ecto": { "workingDirectory": "instrumentation/opentelemetry_ecto", @@ -33,7 +51,7 @@ "tagPrefix": "opentelemetry-ecto-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "elli": { "workingDirectory": "instrumentation/opentelemetry_elli", @@ -42,7 +60,7 @@ "tagPrefix": "opentelemetry-elli-v", "buildTool": "rebar3", "language": "erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "finch": { "workingDirectory": "instrumentation/opentelemetry_finch", @@ -51,7 +69,7 @@ "tagPrefix": "opentelemetry-finch-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "grpcbox": { "workingDirectory": "instrumentation/opentelemetry_grpcbox", @@ -60,16 +78,16 @@ "tagPrefix": "opentelemetry-grpcbox-v", "buildTool": "rebar3", "language": "erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, - "http_instrumentation": { - "workingDirectory": "utilities/opentelemetry_instrumentation_http", + "http": { + "workingDirectory": "utilities/otel_http", "name": "HTTP Utilities", - "packageName": "opentelemetry_instrumentation_http", - "tagPrefix": "opentelemetry-instrumentation-http-v", + "packageName": "otel_http", + "tagPrefix": "otel-http-v", "buildTool": "rebar3", "language": "erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "httpoison": { "workingDirectory": "instrumentation/opentelemetry_httpoison", @@ -78,7 +96,7 @@ "tagPrefix": "opentelemetry-httpoison-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "nebulex": { "workingDirectory": "instrumentation/opentelemetry_nebulex", @@ -87,7 +105,7 @@ "tagPrefix": "opentelemetry-nebulex-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "oban": { "workingDirectory": "instrumentation/opentelemetry_oban", @@ -96,7 +114,7 @@ "tagPrefix": "opentelemetry-oban-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "opentelemetry_telemetry": { "workingDirectory": "utilities/opentelemetry_telemetry", @@ -105,7 +123,7 @@ "tagPrefix": "opentelemetry-telemetry-v", "buildTool": "mix", "language": "elixir-erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "phoenix": { "workingDirectory": "instrumentation/opentelemetry_phoenix", @@ -114,7 +132,7 @@ "tagPrefix": "opentelemetry-phoenix-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "redix": { "workingDirectory": "instrumentation/opentelemetry_redix", @@ -123,7 +141,7 @@ "tagPrefix": "opentelemetry-redix-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "req": { "workingDirectory": "instrumentation/opentelemetry_req", @@ -132,7 +150,7 @@ "tagPrefix": "opentelemetry-req-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "tesla": { "workingDirectory": "instrumentation/opentelemetry_tesla", @@ -141,7 +159,7 @@ "tagPrefix": "opentelemetry-tesla-v", "buildTool": "mix", "language": "elixir", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] }, "process_propagator": { "workingDirectory": "propagators/opentelemetry_process_propagator", @@ -150,6 +168,24 @@ "tagPrefix": "opentelemetry-process-propagator-v", "buildTool": "mix", "language": "elixir-erlang", - "authorizedUsers": ["bryannaegele","tsloughter"] + "authorizedUsers": ["bryannaegele", "tsloughter"] + }, + "xandra": { + "workingDirectory": "instrumentation/opentelemetry_xandra", + "name": "OpenTelemetry Xandra", + "packageName": "opentelemetry_xandra", + "tagPrefix": "opentelemetry-xandra-v", + "buildTool": "mix", + "language": "elixir", + "authorizedUsers": ["whatyouhide"] + }, + "commanded": { + "workingDirectory": "instrumentation/opentelemetry_commanded", + "name": "Commanded Instrumentation", + "packageName": "opentelemetry_commanded", + "tagPrefix": "opentelemetry-commanded-v", + "buildTool": "mix", + "language": "elixir", + "authorizedUsers": ["bryannaegele", "tsloughter"] } } diff --git a/.github/labeler.yml b/.github/labeler.yml index 3832379b..02ac00ef 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -52,6 +52,9 @@ scope-ci: opentelemetry_aws_xray: - utilities/opentelemetry_aws_xray/**/* +opentelemetry_broadway: + - instrumentation/opentelemetry_broadway/**/* + opentelemetry_cowboy: - instrumentation/opentelemetry_cowboy/**/* @@ -73,9 +76,6 @@ opentelemetry_grpcbox: opentelemetry_httpoison: - instrumentation/opentelemetry_httpoison/**/* -opentelemetry_instrumentation_http: - - utilities/opentelemetry_instrumentation_http/**/* - opentelemetry_nebulex: - instrumentation/opentelemetry_nebulex/**/* @@ -102,3 +102,12 @@ opentelemetry_telemetry: opentelemetry_tesla: - instrumentation/opentelemetry_tesla/**/* + +opentelemetry_xandra: + - instrumentation/opentelemetry_xandra/**/* + +opentelemetry_commanded: + - instrumentation/opentelemetry_commanded/**/* + +otel_http: + - utilities/otel_http/**/* diff --git a/.github/release-drafter-templates/opentelemetry-broadway.yml b/.github/release-drafter-templates/opentelemetry-broadway.yml new file mode 100644 index 00000000..10800981 --- /dev/null +++ b/.github/release-drafter-templates/opentelemetry-broadway.yml @@ -0,0 +1,11 @@ +_extends: opentelemetry-erlang-contrib:.github/release-drafter.yml +name-template: 'Opentelemetry Broadway - v$RESOLVED_VERSION' +tag-template: 'opentelemetry-broadway-v$RESOLVED_VERSION' +tag-prefix: opentelemetry-broadway-v +include-paths: + - instrumentation/opentelemetry_broadway/ + +footer: | + + --- + [Changelog](https://$REPOSITORY/blob/main/instrumentation/opentelemetry_broadway/CHANGELOG.MD) diff --git a/.github/release-drafter-templates/opentelemetry-commanded.yml b/.github/release-drafter-templates/opentelemetry-commanded.yml new file mode 100644 index 00000000..7c0a8ae5 --- /dev/null +++ b/.github/release-drafter-templates/opentelemetry-commanded.yml @@ -0,0 +1,6 @@ +_extends: opentelemetry-erlang-contrib:.github/release-drafter.yml +name-template: "Opentelemetry Commanded - v$RESOLVED_VERSION" +tag-template: "opentelemetry-commanded-v$RESOLVED_VERSION" +tag-prefix: opentelemetry-commanded-v +include-paths: + - instrumentation/opentelemetry_commanded/ diff --git a/.github/release-drafter-templates/opentelemetry-instrumentation-http.yml b/.github/release-drafter-templates/opentelemetry-instrumentation-http.yml deleted file mode 100644 index ec28e84b..00000000 --- a/.github/release-drafter-templates/opentelemetry-instrumentation-http.yml +++ /dev/null @@ -1,6 +0,0 @@ -_extends: opentelemetry-erlang-contrib:.github/release-drafter.yml -name-template: 'Opentelemetry Instrumentation HTTP - v$RESOLVED_VERSION' -tag-template: 'opentelemetry-instrumentation-http-v$RESOLVED_VERSION' -tag-prefix: opentelemetry-instrumentation-http-v -include-paths: - - utilities/opentelemetry_instrumentation-http/ diff --git a/.github/release-drafter-templates/opentelemetry-xandra.yml b/.github/release-drafter-templates/opentelemetry-xandra.yml new file mode 100644 index 00000000..9f562045 --- /dev/null +++ b/.github/release-drafter-templates/opentelemetry-xandra.yml @@ -0,0 +1,6 @@ +_extends: opentelemetry-erlang-contrib:.github/release-drafter.yml +name-template: "Opentelemetry Xandra - v$RESOLVED_VERSION" +tag-template: "opentelemetry-xandra-v$RESOLVED_VERSION" +tag-prefix: opentelemetry-xandra-v +include-paths: + - instrumentation/opentelemetry_xandra/ diff --git a/.github/release-drafter-templates/otel-http.yml b/.github/release-drafter-templates/otel-http.yml new file mode 100644 index 00000000..0b4a19fa --- /dev/null +++ b/.github/release-drafter-templates/otel-http.yml @@ -0,0 +1,6 @@ +_extends: opentelemetry-erlang-contrib:.github/release-drafter.yml +name-template: 'Otel HTTP - v$RESOLVED_VERSION' +tag-template: 'otel-http-v$RESOLVED_VERSION' +tag-prefix: otel-http-v +include-paths: + - utilities/otel_http/ diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 87c62314..4bcf6d82 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -10,6 +10,9 @@ on: branches: - "main" +permissions: + contents: read + concurrency: group: ci-${{ github.head_ref || github.run_id }}-elixir cancel-in-progress: true @@ -27,6 +30,86 @@ jobs: matrixStringifiedObject="$(jq -c . .github/elixir-test-matrix.json)" echo "matrix=$matrixStringifiedObject" >> $GITHUB_OUTPUT + opentelemetry-bandit: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_bandit')) + env: + app: "opentelemetry_bandit" + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-24.04 + name: Opentelemetry Bandit 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@v4 + - uses: erlef/setup-beam@v1 + with: + version-type: strict + otp-version: ${{ matrix.otp_version }} + elixir-version: ${{ matrix.elixir_version }} + rebar3-version: ${{ matrix.rebar3_version }} + - name: Cache + uses: actions/cache@v4 + 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 + if: matrix.check_formatted + - name: dialyzer + run: mix dialyzer + - name: Test + run: mix test + + opentelemetry-broadway: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_broadway')) + env: + app: "opentelemetry_broadway" + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-24.04 + name: Opentelemetry Broadway 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@v4 + - uses: erlef/setup-beam@v1 + with: + version-type: strict + otp-version: ${{ matrix.otp_version }} + elixir-version: ${{ matrix.elixir_version }} + rebar3-version: ${{ matrix.rebar3_version }} + - name: Cache + uses: actions/cache@v4 + 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 + - name: Check formatting + run: mix format --check-formatted + if: matrix.check_formatted + - name: Test + run: mix test + opentelemetry-dataloader: needs: [test-matrix] if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_dataloader')) @@ -35,7 +118,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Dataloader test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -84,20 +167,45 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Ecto test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} services: postgres: - image: circleci/postgres:13.5-ram - ports: ["5432:5432"] + image: postgres:17.5 + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: opentelemetry_ecto_test + postgres-r1: + image: postgres:17.5 + ports: + - 5433:5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: opentelemetry_ecto_test + mysql: + image: mysql:9.3 + env: + MYSQL_USER: mysql + MYSQL_PASSWORD: mysql + MYSQL_ROOT_PASSWORD: mysql + ports: + - 3306:3306 + mssql: + image: mcr.microsoft.com/azure-sql-edge + env: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: MSSQLpass1! + ports: + - 1433:1433 steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 @@ -132,7 +240,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Finch test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -156,7 +264,7 @@ jobs: if: steps.deps-cache.outputs.cache-hit != 'true' run: mix deps.get - name: Compile project - run: mix compile --warnings-as-errors + run: mix compile - name: Check formatting run: mix format --check-formatted if: matrix.check_formatted @@ -171,7 +279,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry HTTPoison test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -210,7 +318,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Nebulex test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -249,7 +357,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Oban test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -297,7 +405,7 @@ jobs: defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Opentelemetry Phoenix test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false @@ -328,19 +436,23 @@ jobs: - name: Test run: mix test - opentelemetry-bandit: + opentelemetry-redix: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_bandit')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_redix')) env: - app: "opentelemetry_bandit" + app: "opentelemetry_redix" defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Bandit test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + runs-on: ubuntu-24.04 + name: Opentelemetry Redix test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} + services: + redis: + image: redis:alpine + ports: ["6379:6379"] steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 @@ -360,30 +472,26 @@ jobs: if: steps.deps-cache.outputs.cache-hit != 'true' run: mix deps.get - name: Compile project - run: mix compile --warnings-as-errors + run: mix compile - name: Check formatting run: mix format --check-formatted if: matrix.check_formatted - name: Test run: mix test - opentelemetry-redix: + opentelemetry-req: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_redix')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_req')) env: - app: "opentelemetry_redix" + app: "opentelemetry_req" defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Redix test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + runs-on: ubuntu-24.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) }} - services: - redis: - image: redis:alpine - ports: ["6379:6379"] steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 @@ -410,16 +518,16 @@ jobs: - name: Test run: mix test - opentelemetry-req: + opentelemetry-telemetry: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_req')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) env: - app: "opentelemetry_req" + app: "opentelemetry_telemetry" defaults: run: - working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Req test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + working-directory: utilities/${{ env.app }} + runs-on: ubuntu-24.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) }} @@ -449,16 +557,16 @@ jobs: - name: Test run: mix test - opentelemetry-telemetry: + opentelemetry-process-propagator: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_process_propagator')) env: - app: "opentelemetry_telemetry" + app: "opentelemetry_process_propagator" defaults: run: - working-directory: utilities/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Telemetry test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + working-directory: propagators/${{ env.app }} + runs-on: ubuntu-24.04 + name: Opentelemetry Process Propagator test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} @@ -488,16 +596,16 @@ jobs: - name: Test run: mix test - opentelemetry-process-propagator: + opentelemetry-tesla: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_process_propagator')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_tesla')) env: - app: "opentelemetry_process_propagator" + app: "opentelemetry_tesla" defaults: run: - working-directory: propagators/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Process Propagator test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-24.04 + name: Opentelemetry Tesla test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} @@ -527,16 +635,61 @@ jobs: - name: Test run: mix test - opentelemetry-tesla: + opentelemetry-xandra: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_tesla')) + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_xandra')) env: - app: "opentelemetry_tesla" + app: "opentelemetry_xandra" defaults: run: working-directory: instrumentation/${{ env.app }} - runs-on: ubuntu-22.04 - name: Opentelemetry Tesla test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + runs-on: ubuntu-24.04 + name: OpenTelemetry Xandra test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} + services: + cassandra: + image: cassandra + ports: + - 9042:9042 + options: --health-cmd "cqlsh --debug" --health-interval 5s --health-retries 10 + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + version-type: strict + otp-version: ${{ matrix.otp_version }} + elixir-version: ${{ matrix.elixir_version }} + rebar3-version: ${{ matrix.rebar3_version }} + - name: Cache + uses: actions/cache@v4 + 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 + if: matrix.check_formatted + - name: Test + run: mix test + + opentelemetry-commanded: + needs: [test-matrix] + if: (contains(github.event.pull_request.labels.*.name, 'elixir') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_commanded')) + env: + app: "opentelemetry_commanded" + defaults: + run: + working-directory: instrumentation/${{ env.app }} + runs-on: ubuntu-24.04 + name: Opentelemetry Commanded test on Elixir ${{ matrix.elixir_version }} (OTP ${{ matrix.otp_version }}) strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 9719545a..cb5c7738 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -10,6 +10,9 @@ on: branches: - "main" +permissions: + contents: read + concurrency: group: ci-${{ github.head_ref || github.run_id }}-erlang cancel-in-progress: true @@ -155,16 +158,16 @@ jobs: - name: Test run: rebar3 ct - opentelemetry-instrumentation-http: + opentelemetry-telemetry: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'erlang') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_instrumentation_http')) + if: (contains(github.event.pull_request.labels.*.name, 'erlang') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) env: - app: "opentelemetry_instrumentation_http" + app: "opentelemetry_telemetry" defaults: run: working-directory: utilities/${{ env.app }} runs-on: ${{ matrix.os }} - name: OpenTelemetry Instrumentation HTTP test on OTP ${{ matrix.otp_version }} with Rebar3 ${{ matrix.rebar3_version }} + 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) }} @@ -185,18 +188,18 @@ jobs: if: steps.deps-cache.outputs.cache-hit != 'true' run: rebar3 get-deps - name: Test - run: rebar3 eunit + run: rebar3 ct - opentelemetry-telemetry: + otel-http: needs: [test-matrix] - if: (contains(github.event.pull_request.labels.*.name, 'erlang') && contains(github.event.pull_request.labels.*.name, 'opentelemetry_telemetry')) + if: (contains(github.event.pull_request.labels.*.name, 'erlang') && contains(github.event.pull_request.labels.*.name, 'otel_http')) env: - app: "opentelemetry_telemetry" + app: "otel_http" defaults: run: working-directory: utilities/${{ env.app }} runs-on: ${{ matrix.os }} - name: Opentelemetry Telemetry test on OTP ${{ matrix.otp_version }} with Rebar3 ${{ matrix.rebar3_version }} + name: Otel HTTP test on OTP ${{ matrix.otp_version }} with Rebar3 ${{ matrix.rebar3_version }} strategy: fail-fast: false matrix: ${{ fromJson(needs.test-matrix.outputs.matrix) }} @@ -217,4 +220,4 @@ jobs: if: steps.deps-cache.outputs.cache-hit != 'true' run: rebar3 get-deps - name: Test - run: rebar3 ct + run: rebar3 eunit diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml new file mode 100644 index 00000000..61f63d7d --- /dev/null +++ b/.github/workflows/fossa.yml @@ -0,0 +1,27 @@ +name: FOSSA scanning + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + fossa: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: erlef/setup-beam@v1 + with: + otp-version: "27.2.4" + rebar3-version: "3.24.0" + elixir-version: "1.18.2" + version-type: "strict" + + - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 + with: + api-key: ${{secrets.FOSSA_API_KEY}} + team: OpenTelemetry diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 6e2f46ab..984a0972 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,9 +1,14 @@ name: "Pull Request Labeler" on: [pull_request_target] +permissions: + contents: read + jobs: triage: - runs-on: ubuntu-latest + permissions: + pull-requests: write # required for labeling pull requests + runs-on: ubuntu-24.04 steps: - uses: actions/labeler@v4 with: diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml new file mode 100644 index 00000000..7066467a --- /dev/null +++ b/.github/workflows/ossf-scorecard.yml @@ -0,0 +1,47 @@ +name: OSSF Scorecard + +on: + push: + branches: + - main + schedule: + - cron: "56 12 * * 4" # once a week + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + runs-on: ubuntu-latest + permissions: + # Needed for Code scanning upload + security-events: write + # Needed for GitHub OIDC token if publish_results is true + id-token: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable + # uploads of run results in SARIF format to the repository Actions tab. + # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts + - name: "Upload artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + with: + sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/publish-mix-hex-release.yml b/.github/workflows/publish-mix-hex-release.yml index fd9d8508..5cd26631 100644 --- a/.github/workflows/publish-mix-hex-release.yml +++ b/.github/workflows/publish-mix-hex-release.yml @@ -8,13 +8,15 @@ on: type: choice options: - "aws_xray" + - "bandit" + - "broadway" - "cowboy" - "dataloader" - "ecto" - "elli" - "finch" - "grpcbox" - - "http_instrumentation" + - "http" - "httpoison" - "nebulex" - "oban" @@ -24,7 +26,21 @@ on: - "redix" - "req" - "tesla" + - "xandra" + - "commanded" required: true + otp-version: + description: "OTP version" + type: string + default: "27.1" + elixir-version: + description: "Elixir version" + type: string + default: "1.17.3" + rebar3-version: + description: "Rebar3 version" + type: string + default: "3.24.0" action: description: "Publish release" required: true @@ -33,6 +49,9 @@ on: - prep - publish +permissions: + contents: read + jobs: config: runs-on: ubuntu-latest @@ -45,7 +64,7 @@ jobs: tag_prefix: ${{ steps.set-config.outputs.tag_prefix}} working_directory: ${{ steps.set-config.outputs.working_directory }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Read file id: set-config uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 @@ -101,7 +120,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - run: npm install semver - name: "Update Files" id: update-files @@ -136,26 +155,26 @@ jobs: case 'elixir': srcFilePath = `${needs.config.outputs.working_directory}/mix.exs`; core.debug(`Source file path: ${srcFilePath}`) - + srcVersionRegex = /@version\s+"[^"]+"/; core.debug(`Source version regex: ${srcVersionRegex}`) - + vsnLineTemplate = `@version "${version}"`; core.debug(`Version line template: ${vsnLineTemplate}`) - + core.setOutput('srcFilePath', srcFilePath); break; case 'elixir-erlang': case 'erlang': srcFilePath = `${needs.config.outputs.working_directory}/src/${needs.config.outputs.package_name}.app.src`; core.debug(`Source file path: ${srcFilePath}`) - + srcVersionRegex = /{vsn,\s+"[^"]+"},/; core.debug(`Source version regex: ${srcVersionRegex}`) - + vsnLineTemplate = `{vsn, "${version}"},`; core.debug(`Version line template: ${vsnLineTemplate}`) - + core.setOutput('srcFilePath', srcFilePath); break; default: @@ -190,12 +209,12 @@ jobs: } } - - uses: erlef/setup-beam@a6e26b22319003294c58386b6f25edbc7336819a # v1.18.0 + - uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 # v1.20.4 with: version-type: strict - otp-version: "25.3.2.5" - elixir-version: "1.14.5" - rebar3-version: "3.22.1" + otp-version: ${{ inputs.otp-version }} + elixir-version: ${{ inputs.elixir-version }} + rebar3-version: ${{ inputs.rebar3-version }} - name: "Mix Hex Publish Dry-run" if: ${{ needs.config.outputs.build_tool == 'mix' }} @@ -212,13 +231,13 @@ jobs: env: HEX_API_KEY: ${{ secrets.OTEL_HEX_KEY }} run: | - rebar3 upgrade + rebar3 update rebar3 hex publish --dry-run --yes - name: "Open a Version Update PR" if: ${{ env.releasePrepped == 'false' }} id: version-update-pr - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: add-paths: | ${{ steps.update-files.outputs.srcFilePath }} @@ -259,6 +278,6 @@ jobs: env: HEX_API_KEY: ${{ secrets.OTEL_HEX_KEY }} run: | - rebar3 upgrade + rebar3 update rebar3 hex publish --yes echo "published=true" >> $GITHUB_ENV diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 8c925b43..ab7a07ee 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -1,14 +1,20 @@ name: Release Drafter on: + workflow_dispatch: push: branches: - main +permissions: + contents: read + jobs: opentelemetry-aws-xray-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-aws-xray-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -16,9 +22,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + opentelemetry-broadway-release: + permissions: + contents: write # required for creating draft releases + name: '[opentelemetry-broadway-release] Draft release' + runs-on: ubuntu-24.04 + steps: + - uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter-templates/opentelemetry-broadway.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + opentelemetry-cowboy-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-cowboy-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -27,8 +47,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-dataloader-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-dataloader-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -37,8 +59,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-ecto-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-ecto-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -47,8 +71,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-elli-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-elli-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -57,8 +83,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-finch-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-finch-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -67,8 +95,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-grpcbox-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-grpcbox-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -77,8 +107,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-httpoison-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-httpoison-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -86,19 +118,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - opentelemetry-instrumentation-http-release: - name: '[opentelemetry-instrument-http-release] Draft release' - runs-on: ubuntu-22.04 - steps: - - uses: release-drafter/release-drafter@v6 - with: - config-name: release-drafter-templates/opentelemetry-instrumentation-http.yml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - opentelemetry-nebulex-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-nebulex-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -107,8 +131,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-oban-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-oban-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -117,8 +143,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-phoenix-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-phoenix-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -127,8 +155,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-bandit-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-bandit-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -137,8 +167,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-process-propagator-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-process-propagator-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -147,8 +179,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-redix-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-redix-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -157,8 +191,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-req-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-req-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -167,8 +203,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-telemetry-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-telemetry-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: @@ -177,11 +215,49 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} opentelemetry-tesla-release: + permissions: + contents: write # required for creating draft releases name: '[opentelemetry-tesla-release] Draft release' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: release-drafter/release-drafter@v6 with: config-name: release-drafter-templates/opentelemetry-tesla.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + opentelemetry-xandra-release: + permissions: + contents: write # required for creating draft releases + name: '[opentelemetry-xandra-release] Draft release' + runs-on: ubuntu-24.04 + steps: + - uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter-templates/opentelemetry-xandra.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + otel-http-release: + permissions: + contents: write # required for creating draft releases + name: '[otel-http-release] Draft release' + runs-on: ubuntu-24.04 + steps: + - uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter-templates/otel-http.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + opentelemetry-commanded-release: + permissions: + contents: write # required for creating draft releases + name: '[opentelemetry-commanded-release] Draft release' + runs-on: ubuntu-24.04 + steps: + - uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter-templates/opentelemetry-commanded.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CODEOWNERS b/CODEOWNERS index d64227de..2cc3a63d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,20 +6,22 @@ # # Learn about membership in OpenTelemetry community: # https://github.com/open-telemetry/community/blob/main/community-membership.md -# # -# Learn about CODEOWNERS file format: +# +# Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # @open-telemetry/erlang-approvers +/instrumentation/opentelemetry_broadway @whatyouhide @tomtaylor /instrumentation/opentelemetry_cowboy @bryannaegele @tsloughter /instrumentation/opentelemetry_ecto @bryannaegele @tsloughter /instrumentation/opentelemetry_nebulex @andrewhr /instrumentation/opentelemetry_oban @indrekj /instrumentation/opentelemetry_phoenix @bryannaegele @tsloughter /instrumentation/opentelemetry_redix @andrewhr +/instrumentation/opentelemetry_xandra @whatyouhide /utilities/opentelemetry_telemetry @bryannaegele @tsloughter -/utilities/opentelemetry_instrumentation_http @tsloughter +/utilities/otel_http @bryannaegele @tsloughter /instrumentation/opentelemetry_tesla @ricardoccpaiva diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7de5fd87..f544f659 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,18 @@ ## Contributing +### Library Inclusion in the Erlang Contrib Repository Policy + +Maintaining libraries can be a demanding task. Due to the limited number of maintainers, any library added to the +contrib repository must have an OpenTelemetry CNCF Erlang Contrib Approver or Maintainer as a sponsor. [Becoming a member](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md) and approver is a simple +process. Following approval, the library can be merged and you will be added as a codeowner for it. + +This policy has been enacted to be respectful of the maintainers' time and to ensure users get timely responses +and regular updates. We want as many libraries to be under the official umbrella but need your commmitment to +make that happen. + ### Instrumenting a library -When instrumenting a library, it is important to follow the [Trace Semantic Conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/README.md). +When instrumenting a library, it is important to follow the [Trace Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/trace.md) ### CI diff --git a/examples/roll_dice/otel-collector-config.yaml b/examples/roll_dice/otel-collector-config.yaml index d4605163..18cea374 100644 --- a/examples/roll_dice/otel-collector-config.yaml +++ b/examples/roll_dice/otel-collector-config.yaml @@ -19,8 +19,8 @@ exporters: tls: insecure: true - logging: - loglevel: debug + debug: + verbosity: detailed sampling_initial: 1 sampling_thereafter: 1 @@ -36,12 +36,12 @@ service: traces: receivers: [otlp] processors: [batch] - exporters: [logging, otlp] + exporters: [debug, otlp] metrics: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] logs: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] diff --git a/examples/roll_dice_elli/otel-collector-config.yaml b/examples/roll_dice_elli/otel-collector-config.yaml index f69b3c67..7ad337b5 100644 --- a/examples/roll_dice_elli/otel-collector-config.yaml +++ b/examples/roll_dice_elli/otel-collector-config.yaml @@ -19,8 +19,8 @@ exporters: tls: insecure: true - logging: - loglevel: debug + debug: + verbosity: detailed sampling_initial: 1 sampling_thereafter: 1 @@ -33,12 +33,12 @@ service: traces: receivers: [otlp] processors: [batch] - exporters: [logging, otlp] + exporters: [debug, otlp] metrics: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] logs: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] diff --git a/instrumentation/opentelemetry_bandit/.gitignore b/instrumentation/opentelemetry_bandit/.gitignore index 96dbe8d8..1bb97eee 100644 --- a/instrumentation/opentelemetry_bandit/.gitignore +++ b/instrumentation/opentelemetry_bandit/.gitignore @@ -25,3 +25,5 @@ erl_crash.dump # Temporary files, for example, from tests. /tmp/ .DS_Store + +/priv/plts/* \ No newline at end of file diff --git a/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex b/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex index 7dec6d7c..ff378746 100644 --- a/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex +++ b/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex @@ -1,177 +1,503 @@ defmodule OpentelemetryBandit do - @moduledoc """ - OpentelemetryBandit uses [telemetry](https://hexdocs.pm/telemetry/) handlers to create `OpenTelemetry` spans. + # TODOS + # * add attr stability lookups in semconv with maps + # * store shadow attrs in conn priv from semconv maps? + # * perf test: + # * map with shadow attrs + # * map with shadow attrs with multiple attr ops (memory in particular) - Supported: - 1. :bandit, :request, :stop - 2. :bandit, :request, :exception - 3. :bandit, :websocket, :stop - """ + alias OpenTelemetry.Ctx - alias OpenTelemetry.SemanticConventions.Trace - require Trace + alias OpenTelemetry.SemConv.ClientAttributes + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.NetworkAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpenTelemetry.SemConv.URLAttributes + alias OpenTelemetry.SemConv.UserAgentAttributes + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + + alias OpenTelemetry.Tracer require OpenTelemetry.Tracer + opt_ins = [ + ClientAttributes.client_port(), + HTTPAttributes.http_request_body_size(), + HTTPAttributes.http_response_body_size(), + NetworkAttributes.network_local_address(), + NetworkAttributes.network_local_port(), + NetworkAttributes.network_transport() + ] + + @options_schema NimbleOptions.new!( + opt_in_attrs: [ + type: {:list, {:in, opt_ins}}, + default: [], + type_spec: quote(do: opt_in_attrs()), + doc: """ + Use semantic conventions library to ensure compatibility, e.g. `[HTTPAttributes.http_request_body_size()]` + + #{Enum.map_join(opt_ins, "\n\n", &" * `#{inspect(&1)}`")} + """ + ], + handler_id: [ + type: :atom, + default: :otel_bandit, + doc: "Only set when running multiple instances on different endpoints" + ], + client_address_headers: [ + type: {:list, :string}, + default: ["forwarded", "x-forwarded-for"], + doc: "Headers to use for extracting original client request address info" + ], + client_headers_sort_fn: [ + type: {:fun, 2}, + doc: "Custom client header sort fn. See `otel_http` for more info" + ], + public_endpoint: [ + type: :boolean, + default: false, + doc: "Endpoint is public. Propagated traces will be added as a link." + ], + public_endpoint_fn: [ + type: :mfa, + default: {__MODULE__, :default_public_endpoint_fn, []}, + doc: "Default function returns `false`. See docs for more info" + ], + request_headers: [ + type: {:list, :string}, + default: [], + doc: "List of request headers to add as attributes. (lowercase)" + ], + response_headers: [ + type: {:list, :string}, + default: [], + doc: "List of response headers to add as attributes. (lowercase)" + ], + scheme_headers: [ + type: {:list, :string}, + default: ["forwarded", "x-forwarded-proto"], + doc: "Headers to use for extracting original client request scheme" + ], + scheme_headers_sort_fn: [ + type: {:fun, 2}, + doc: "Custom scheme header sort fn. See `otel_http` for more info" + ], + server_address_headers: [ + type: {:list, :string}, + default: ["forwarded", "x-forwarded-host", "host"], + doc: "Headers to use for extracting original server address info" + ], + server_headers_sort_fn: [ + type: {:fun, 2}, + doc: "Custom server header sort fn. See `otel_http` for more info" + ] + ) + + @typedoc "Use semantic conventions library to ensure compatibility, e.g. `HTTPAttributes.http_request_body_size()`" + @type opt_in_attr() :: + unquote(ClientAttributes.client_port()) + | unquote(HTTPAttributes.http_request_body_size()) + | unquote(HTTPAttributes.http_response_body_size()) + | unquote(NetworkAttributes.network_local_address()) + | unquote(NetworkAttributes.network_local_port()) + | unquote(NetworkAttributes.network_transport()) + + @type opt_in_attrs() :: [opt_in_attr()] + + @type options() :: [unquote(NimbleOptions.option_typespec(@options_schema))] + + @moduledoc """ + OpenTelemetry instrumentation for Bandit. + + ## Semantic Conventions + + All required and recommended Server HTTP Span semantic conventions are implemented. + Supported opt-in attributes can be configured using the `opt_in_attrs` option. + + ## Options + + ### Opt-in Semantic Convention Attributes + + Otel SemConv requires users to explicitly opt in for any attribute with a + requirement level of `opt-in`. To ensure compatibility, always use the + SemConv attribute. + + Example: + ``` + opt_ins = [SemConv.HTTPAttributes.http_request_body_size()]` + OpentelemetryBandit.setup(opt_in_attrs: opt_ins) + ``` + + #### Request and Response Headers as Opt-in Attributes + + Request and response header attributes are opt-in and can be set with the + `request_headers` and `response_headers` options. Values should be lower-case. + + ``` + OpentelemetryBandit.setup(request_headers: ["x-customer-id"]) + ``` + + ### Public Endpoint + + Setting an endpoint as public will result in any propagated trace to be added as a link, + rather than a continuation of an existing trace. The `public_endpoint` option should be set + to `true` if an endpoint only accepts public traffic to prevent missing root spans. By default, + the endpoint is handled as non-public, resulting in traces being continued rather than linked. + + In a mixed traffic environment, an MFA can be supplied to determine whether to + treat a request as public. This function is executed on every request, so refrain + from expensive operations such as lookups to external systems. The function must + be a predicate function of arity-2, accepting the `conn` from the request as + the first argument and user-supplied options as the second. Any dynamic + information used in comparisons should be supplied at setup time for efficiency. + + Example: + ``` + defmodule PublicEndpoint do + def is_public_request?(conn, opts) do + # return true if request was public + end + end + + OpentelemetryBandit.setup(public_endpoint_fn: {PublicEndpoint, :is_public_request?, []}) + ``` + """ + @doc """ Initializes and configures the telemetry handlers. + + Supported options:\n#{NimbleOptions.docs(@options_schema)} """ @spec setup(any) :: :ok - def setup(_opts \\ []) do - :telemetry.attach( - {__MODULE__, :request_stop}, - [:bandit, :request, :stop], - &__MODULE__.handle_request_stop/4, - %{} - ) + def setup(opts \\ []) do + config = + opts + |> NimbleOptions.validate!(@options_schema) + |> Enum.into(%{}) + |> Map.update!(:scheme_headers, &Enum.reverse/1) + |> Map.update!(:client_address_headers, &Enum.reverse/1) - :telemetry.attach( - {__MODULE__, :request_exception}, - [:bandit, :request, :exception], - &__MODULE__.handle_request_exception/4, - %{} + :telemetry.attach_many( + {__MODULE__, config.handler_id}, + [ + [:bandit, :request, :start], + [:bandit, :request, :stop], + [:bandit, :request, :exception] + ], + &__MODULE__.handle_request/4, + config ) + end - :telemetry.attach( - {__MODULE__, :websocket_stop}, - [:bandit, :websocket, :stop], - &__MODULE__.handle_websocket_stop/4, - %{} - ) + @doc false + def handle_request([:bandit, :request, :start], _measurements, meta, config) do + handle_request_start(meta, config) end - def handle_request_stop(_event, measurements, meta, _config) do - conn = Map.get(meta, :conn) - duration = measurements.duration - end_time = :opentelemetry.timestamp() - start_time = end_time - duration - - url = extract_url(meta, conn) - request_path = extract_request_path(meta, conn) - - attributes = - if Map.has_key?(meta, :error) do - %{ - Trace.http_url() => url, - Trace.http_method() => meta.method, - Trace.net_transport() => :"IP.TCP", - Trace.http_response_content_length() => measurements.resp_body_bytes, - Trace.http_status_code() => meta.status - } - else - %{ - Trace.http_url() => url, - Trace.http_client_ip() => client_ip(conn), - Trace.http_scheme() => conn.scheme, - Trace.net_peer_name() => conn.host, - Trace.net_peer_port() => conn.port, - Trace.http_target() => conn.request_path, - Trace.http_method() => meta.method, - Trace.http_status_code() => meta.status, - Trace.http_response_content_length() => measurements.resp_body_bytes, - Trace.net_transport() => :"IP.TCP", - Trace.http_user_agent() => user_agent(conn) - } - end - - span_kind = if Map.has_key?(meta, :error), do: :error, else: :server - - span_id = "HTTP #{meta.method} #{request_path}" |> String.trim() - - OpenTelemetry.Tracer.start_span(span_id, %{ - attributes: attributes, - start_time: start_time, - end_time: end_time, - kind: span_kind - }) - |> set_span_status(meta, Map.get(meta, :error, "")) - |> OpenTelemetry.Span.end_span() + @doc false + def handle_request([:bandit, :request, :stop], measurements, meta, config) do + handle_request_stop(measurements, meta, config) + end - OpenTelemetry.Ctx.clear() + @doc false + def handle_request([:bandit, :request, :exception], _measurements, meta, config) do + handle_request_exception(meta, config) end - def handle_request_exception(_event, _measurements, meta, _config) do - OpenTelemetry.Tracer.start_span("HTTP exception #{inspect(meta.exception.__struct__)}", %{ - kind: :error, - status: :error - }) - |> set_span_status(meta, inspect(meta.stacktrace)) - |> OpenTelemetry.Span.end_span() + @doc false + def handle_request_start(%{conn: conn}, config) do + peer_data = Plug.Conn.get_peer_data(conn) + request_method = parse_method(conn.method) + client_address = extract_client_address(conn, config) - OpenTelemetry.Ctx.clear() + opt_in = %{ + ClientAttributes.client_port() => client_address.port, + NetworkAttributes.network_local_address() => conn.host, + NetworkAttributes.network_local_port() => conn.port, + NetworkAttributes.network_transport() => :tcp + } + + attrs = + %{ + ClientAttributes.client_address() => client_address.ip, + HTTPAttributes.http_request_method() => request_method, + NetworkAttributes.network_peer_address() => ip_to_string(peer_data.address), + NetworkAttributes.network_peer_port() => peer_data.port, + URLAttributes.url_path() => conn.request_path, + URLAttributes.url_scheme() => extract_scheme(conn, config), + UserAgentAttributes.user_agent_original() => header_value(conn, "user-agent") + } + |> set_network_protocol_attrs(conn) + |> set_query_string_attr(conn) + |> set_server_address_attrs(conn, config) + |> set_req_header_attrs(conn, config) + |> then(fn attrs -> + opt_in + |> Map.take(config.opt_in_attrs) + |> Map.merge(attrs) + end) + + name = + if request_method == HTTPAttributes.http_request_method_values().other, + do: :HTTP, + else: request_method + + if public_endpoint?(conn, config) do + propagated_ctx = + :otel_propagator_text_map.extract_to(Ctx.new(), conn.req_headers) + |> Tracer.current_span_ctx() + + # test public endpoint and public endpoint fn + Tracer.start_span(name, %{ + kind: :server, + attributes: attrs, + links: OpenTelemetry.links([propagated_ctx]) + }) + |> Tracer.set_current_span() + else + :otel_propagator_text_map.extract(conn.req_headers) + + Tracer.start_span(name, %{kind: :server, attributes: attrs}) + |> Tracer.set_current_span() + end + end + + # error with no conn + def handle_request_start(_meta, _config) do + Tracer.start_span(:HTTP, %{kind: :server}) + |> Tracer.set_current_span() end - def handle_websocket_stop(_event, measurements, meta, _config) do - duration = measurements.duration - end_time = :opentelemetry.timestamp() - start_time = end_time - duration + defp public_endpoint?(_conn, %{public_endpoint: true}), do: true - attributes = %{ - :"websocket.recv.binary.frame.bytes" => Map.get(measurements, :send_binary_frame_bytes, 0), - :"websocket.send.binary.frame.bytes" => Map.get(measurements, :recv_binary_frame_bytes, 0), - Trace.net_transport() => :websocket - } + defp public_endpoint?(conn, %{public_endpoint_fn: {m, f, a}}) do + apply(m, f, [conn, a]) + end - span_kind = if Map.has_key?(meta, :error), do: :error, else: :server + @doc false + def default_public_endpoint_fn(_, _), do: false - OpenTelemetry.Tracer.start_span("Websocket", %{ - attributes: attributes, - start_time: start_time, - end_time: end_time, - kind: span_kind - }) - |> set_span_status(meta, Map.get(meta, :error, "")) - |> OpenTelemetry.Span.end_span() + defp set_req_header_attrs(attrs, _conn, %{request_headers: []}), do: attrs - OpenTelemetry.Ctx.clear() + defp set_req_header_attrs(attrs, conn, %{request_headers: headers}) do + Map.merge( + attrs, + :otel_http.extract_headers_attributes( + :request, + conn.req_headers, + headers + ) + ) end - defp set_span_status(span, meta, message) do - status = if Map.has_key?(meta, :error) || message != "", do: :error, else: :ok + defp set_resp_header_attrs(attrs, _conn, %{response_headers: []}), do: attrs - OpenTelemetry.Span.set_status(span, OpenTelemetry.status(status, message)) - span + defp set_resp_header_attrs(attrs, conn, %{response_headers: headers}) do + Map.merge( + attrs, + :otel_http.extract_headers_attributes( + :response, + conn.resp_headers, + headers + ) + ) end - defp extract_url(%{error: _} = meta, _conn) do - case Map.get(meta, :request_target) do - nil -> "" - {scheme, host, port, path} -> build_url(scheme, host, port, path) + defp header_value(conn, header) do + case Plug.Conn.get_req_header(conn, header) do + [] -> + "" + + [value | _] -> + value + end + end + + defp set_network_protocol_attrs(attrs, conn) do + case Plug.Conn.get_http_protocol(conn) do + :"HTTP/1.0" -> Map.put(attrs, NetworkAttributes.network_protocol_version(), :"1.0") + :"HTTP/1.1" -> Map.put(attrs, NetworkAttributes.network_protocol_version(), :"1.1") + :"HTTP/2" -> Map.put(attrs, NetworkAttributes.network_protocol_version(), :"2") + _ -> attrs end end - defp extract_url(_meta, conn) do - build_url(conn.scheme, conn.host, conn.port, conn.request_path) + defp set_query_string_attr(attrs, %{query_string: ""}), do: attrs + + defp set_query_string_attr(attrs, %{query_string: query}), + do: Map.put(attrs, URLAttributes.url_query(), query) + + # server attributes are only set from headers + # https://opentelemetry.io/docs/specs/semconv/http/http-spans/#setting-serveraddress-and-serverport-attributes + defp set_server_address_attrs(attrs, conn, config) do + case extract_server_address(conn, config) do + %{address: :undefined} -> + attrs + + %{address: address, port: :undefined} -> + Map.put(attrs, ServerAttributes.server_address(), address) + + %{address: address, port: port} -> + Map.merge( + attrs, + %{ + ServerAttributes.server_address() => address, + ServerAttributes.server_port() => port + } + ) + end end - defp extract_request_path(%{error: _} = meta, _conn) do - case Map.get(meta, :request_target) do - nil -> "" - {_, _, _, path} -> path || "" + defp parse_method(method) do + case method do + "CONNECT" -> HTTPAttributes.http_request_method_values().connect + "DELETE" -> HTTPAttributes.http_request_method_values().delete + "GET" -> HTTPAttributes.http_request_method_values().get + "HEAD" -> HTTPAttributes.http_request_method_values().head + "OPTIONS" -> HTTPAttributes.http_request_method_values().options + "PATCH" -> HTTPAttributes.http_request_method_values().patch + "POST" -> HTTPAttributes.http_request_method_values().post + "PUT" -> HTTPAttributes.http_request_method_values().put + "TRACE" -> HTTPAttributes.http_request_method_values().trace + _ -> HTTPAttributes.http_request_method_values().other end end - defp extract_request_path(_meta, conn) do - conn.request_path + defp extract_headers_by_sort(headers, keys) do + Enum.filter(headers, fn {key, _} -> key in keys end) end - defp build_url(scheme, host, port, path), do: "#{scheme}://#{host}:#{port}#{path}" + defp extract_scheme(conn, config) do + scheme_headers = + conn.req_headers + |> extract_headers_by_sort(config.scheme_headers) - defp user_agent(conn) do - case Plug.Conn.get_req_header(conn, "user-agent") do - [] -> "" - [head | _] -> head + case otel_http_extract_scheme(scheme_headers, config[:scheme_headers_sort_fn]) do + :undefined -> conn.scheme + scheme -> scheme end end - defp client_ip(%{remote_ip: remote_ip} = conn) do - case Plug.Conn.get_req_header(conn, "x-forwarded-for") do - [] -> - remote_ip - |> :inet_parse.ntoa() - |> to_string() + defp otel_http_extract_scheme(headers, nil) do + :otel_http.extract_scheme(headers) + end + + defp otel_http_extract_scheme(headers, sort_fn) do + :otel_http.extract_scheme(headers, sort_fn) + end + + # client + defp extract_client_address(conn, config) do + client_headers = + conn.req_headers + |> extract_headers_by_sort(config.client_address_headers) + + case otel_http_extract_client_info(client_headers, config[:client_headers_sort_fn]) do + %{ip: :undefined} -> + %{address: peer_address, port: peer_port} = Plug.Conn.get_peer_data(conn) + %{ip: ip_to_string(peer_address), port: peer_port} + + client_address -> + client_address + end + end + + defp otel_http_extract_client_info(headers, nil) do + :otel_http.extract_client_info(headers) + end + + defp otel_http_extract_client_info(headers, sort_fn) do + :otel_http.extract_client_info(headers, sort_fn) + end + + # Note: bandit parses host/port but not in the required order and isn't + # limited to only headers per the spec + # https://github.com/open-telemetry/semantic-conventions/blob/v1.26.0/docs/http/http-spans.md#setting-serveraddress-and-serverport-attributes + # http2/3 pseudo headers unsupported at this time https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1 + defp extract_server_address(conn, config) do + conn.req_headers + |> extract_headers_by_sort(config.server_address_headers) + |> otel_http_extract_server_info(config[:server_headers_sort_fn]) + end + + defp otel_http_extract_server_info(headers, nil) do + :otel_http.extract_server_info(headers) + end + + defp otel_http_extract_server_info(headers, sort_fn) do + :otel_http.extract_server_info(headers, sort_fn) + end + + defp ip_to_string(ip) do + ip |> :inet.ntoa() |> to_string() + end + + @doc false + def handle_request_stop(_measurements, %{error: error_message}, _config) do + Tracer.set_status(OpenTelemetry.status(:error, "")) + Tracer.set_attribute(ErrorAttributes.error_type(), error_message) + Tracer.end_span() + Ctx.clear() + end - [ip_address | _] -> - ip_address + @doc false + def handle_request_stop(measurements, %{conn: conn}, config) do + opt_in = + %{ + HTTPAttributes.http_request_body_size() => Map.get(measurements, :req_body_bytes, 0), + HTTPAttributes.http_response_body_size() => Map.get(measurements, :resp_body_bytes, 0) + } + |> Map.take(config.opt_in_attrs) + + if conn.status >= 500 do + Tracer.set_status(OpenTelemetry.status(:error, "")) + + %{ + HTTPAttributes.http_response_status_code() => conn.status, + ErrorAttributes.error_type() => to_string(conn.status) + } + |> set_resp_header_attrs(conn, config) + |> Map.merge(opt_in) + |> Tracer.set_attributes() + else + %{ + HTTPAttributes.http_response_status_code() => conn.status + } + |> set_resp_header_attrs(conn, config) + |> Map.merge(opt_in) + |> Tracer.set_attributes() end + + Tracer.end_span() + Ctx.clear() + end + + @doc false + def handle_request_exception(meta, config) do + Tracer.set_status(OpenTelemetry.status(:error, "")) + + Tracer.record_exception(meta.exception, meta.stacktrace) + + # bandit does not set this on the meta but extracts this after the exception + # telemetry is emitted + status_code = meta.exception |> Plug.Exception.status() |> Plug.Conn.Status.code() + + %{ + HTTPAttributes.http_response_status_code() => status_code, + ErrorAttributes.error_type() => error_type(meta.exception) + } + |> set_resp_header_attrs(meta.conn, config) + |> Tracer.set_attributes() + + Tracer.end_span() + OpenTelemetry.Ctx.clear() + end + + defp error_type(%struct_name{} = reason) when is_exception(reason) do + struct_name + end + + defp error_type(reason) do + reason end end diff --git a/instrumentation/opentelemetry_bandit/mix.exs b/instrumentation/opentelemetry_bandit/mix.exs index 3c58e40f..05fc1024 100644 --- a/instrumentation/opentelemetry_bandit/mix.exs +++ b/instrumentation/opentelemetry_bandit/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryBandit.MixProject do use Mix.Project - @version "0.1.4" + @version "0.2.0" def project do [ @@ -13,6 +13,11 @@ defmodule OpentelemetryBandit.MixProject do description: description(), package: package(), elixirc_paths: elixirc_path(Mix.env()), + dialyzer: [ + plt_add_apps: [:ex_unit, :mix], + plt_core_path: "priv/plts", + plt_local_path: "priv/plts" + ], deps: deps(), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ @@ -23,8 +28,10 @@ defmodule OpentelemetryBandit.MixProject do "coveralls.cobertura": :test ], docs: [ - main: "readme", - extras: ["README.md"] + main: "OpentelemetryBandit", + source_url_pattern: + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/blob/main/instrumentation/opentelemetry_bandit/%{path}#L%{line}", + extras: [] ] ] end @@ -45,7 +52,7 @@ defmodule OpentelemetryBandit.MixProject do defp package do [ files: ~w(lib .formatter.exs mix.exs LICENSE* README* CHANGELOG*), - maintainers: ["Artem Solomatin"], + maintainers: ["Artem Solomatin", "Bryan Naegele"], licenses: ["Apache-2.0"], links: %{ "GitHub" => @@ -64,19 +71,20 @@ defmodule OpentelemetryBandit.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:opentelemetry_api, "~> 1.2"}, - {:opentelemetry_semantic_conventions, "~> 0.2"}, - {:opentelemetry_telemetry, "~> 1.0"}, + {:nimble_options, "~> 1.1"}, + {:opentelemetry_api, "~> 1.3"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:otel_http, "~> 0.2"}, {:plug, ">= 1.15.0"}, {:telemetry, "~> 1.2"}, # dev dependencies - {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:excoveralls, "~> 0.18", only: :test}, - {:bandit, "~> 1.0", only: [:dev, :test], runtime: false}, + {:bandit, "~> 1.5", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, + {:opentelemetry, "~> 1.4", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.7", only: [:dev, :test]}, {:req, "~> 0.5", only: [:dev, :test]} ] end diff --git a/instrumentation/opentelemetry_bandit/mix.lock b/instrumentation/opentelemetry_bandit/mix.lock index cf9de6aa..1940d8f7 100644 --- a/instrumentation/opentelemetry_bandit/mix.lock +++ b/instrumentation/opentelemetry_bandit/mix.lock @@ -1,41 +1,38 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, - "bandit": {:hex, :bandit, "1.0.0", "2bd87bbf713d0eed0090f2fa162cd1676198122e6c2b68a201c706e354a6d5e5", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "32acf6ac030fee1f99fd9c3fcf81671911ae8637e0a61c98111861b466efafdb"}, - "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, - "chatterbox": {:hex, :ts_chatterbox, "0.13.0", "6f059d97bcaa758b8ea6fffe2b3b81362bd06b639d3ea2bb088335511d691ebf", [:rebar3], [{:hpack, "~> 0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "b93d19104d86af0b3f2566c4cba2a57d2e06d103728246ba1ac6c3c0ff010aa7"}, + "bandit": {:hex, :bandit, "1.6.11", "2fbadd60c95310eefb4ba7f1e58810aa8956e18c664a3b2029d57edb7d28d410", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "543f3f06b4721619a1220bed743aa77bf7ecc9c093ba9fab9229ff6b99eacc65"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, - "excoveralls": {:hex, :excoveralls, "0.18.0", "b92497e69465dc51bc37a6422226ee690ab437e4c06877e836f1c18daeb35da9", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1109bb911f3cb583401760be49c02cbbd16aed66ea9509fc5479335d284da60b"}, - "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [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 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"}, - "grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.13.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", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"}, - "hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, - "nimble_ownership": {:hex, :nimble_ownership, "0.2.1", "3e44c72ebe8dd213db4e13aff4090aaa331d158e72ce1891d02e0ffb05a1eb2d", [:mix], [], "hexpm", "bf38d2ef4fb990521a4ecf112843063c1f58a5c602484af4c7977324042badee"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.0.0", "d5982a319e725fcd2305b306b65c18a86afdcf7d96821473cf0649ff88877615", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.3.0", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "3401d13a1d4b7aa941a77e6b3ec074f0ae77f83b5b2206766ce630123a9291a9"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "req": {:hex, :req, "0.5.0", "6d8a77c25cfc03e06a439fb12ffb51beade53e3fe0e2c5e362899a18b50298b3", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [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", "dda04878c1396eebbfdec6db6f3d4ca609e5c8846b7ee88cc56eb9891406f7a3"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "otel_http": {:hex, :otel_http, "0.2.0", "b17385986c7f1b862f5d577f72614ecaa29de40392b7618869999326b9a61d8a", [:rebar3], [], "hexpm", "f2beadf922c8cfeb0965488dd736c95cc6ea8b9efce89466b3904d317d7cc717"}, + "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [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", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"}, - "thousand_island": {:hex, :thousand_island, "1.0.0", "63fc8807d8607c9d74fa670996897c8c8a1f2022c8c68d024182e45249acd756", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "996320c72ba8f34d7be9b02900622e44341649f24359e0f67643e4dda8f23995"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.20.0", "1ac0c53f95e201feb8d398ef9d764ae74175231289d89f166ba88a7f50cd8e73", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "ab57b74b1a63dc5775650699a3ec032ec0065005eff1f020818742b7312a8426"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "thousand_island": {:hex, :thousand_island, "1.3.13", "d598c609172275f7b1648c9f6eddf900e42312b09bfc2f2020358f926ee00d39", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5a34bdf24ae2f965ddf7ba1a416f3111cfe7df50de8d66f6310e01fc2e80b02a"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, } diff --git a/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs b/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs index b00533d7..86b0f002 100644 --- a/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs +++ b/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs @@ -1,245 +1,546 @@ defmodule OpentelemetryBanditTest do - use ExUnit.Case, async: true + use ExUnit.Case, async: false require OpenTelemetry.Tracer require OpenTelemetry.Span require Record + alias OpenTelemetry.SemConv.ClientAttributes + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.ExceptionAttributes + alias OpenTelemetry.SemConv.NetworkAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpenTelemetry.SemConv.URLAttributes + alias OpenTelemetry.SemConv.UserAgentAttributes + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + + import ExUnit.CaptureLog, only: [capture_log: 1] + use ServerHelper for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do Record.defrecord(name, spec) end - describe "http integration" do - test "default span generation for 200" do - Req.get("http://localhost:4000/hello") + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do + Record.defrecord(name, spec) + end + + setup do + :otel_simple_processor.set_exporter(:otel_exporter_pid, self()) + + on_exit(fn -> :telemetry.detach({OpentelemetryBandit, :otel_bandit}) end) + :ok + end + + def start_server(port \\ Enum.random(4000..10_000)) do + {:ok, _} = start_supervised({Bandit, plug: __MODULE__, port: port}) + port + end + + test "validates opt-ins" do + err = + catch_error( + OpentelemetryBandit.setup( + opt_in_attrs: [ + ClientAttributes.client_port(), + :unsupported, + HTTPAttributes.http_request_body_size() + ] + ) + ) + + assert is_struct(err, NimbleOptions.ValidationError) + + assert String.starts_with?( + err.message, + "invalid list in :opt_in_attrs option: invalid value for list element at position" + ) + end + + describe "GET" do + test "basic request with default options" do + OpentelemetryBandit.setup() + port = start_server() + + Req.get("http://localhost:#{port}/hello", + params: [a: 1, b: "abc"], + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + } + ) assert_receive {:span, span( - name: "HTTP GET /hello", + name: :GET, kind: :server, - status: {:status, :ok, _}, - attributes: attributes + attributes: span_attrs, + parent_span_id: 13_235_353_014_750_950_193 )} - assert %{ - "net.peer.name": "localhost", - "http.method": "GET", - "http.target": "/hello", - "http.scheme": :http, - "http.status_code": 200 - } = :otel_attributes.map(attributes) + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/hello"}, + {URLAttributes.url_query(), "a=1&b=abc"}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") end - test "default span generation for 200 without user-agent" do - {:ok, {{_, 200, _}, _, _}} = - :httpc.request(:get, {~c"http://localhost:4000/hello", []}, [], []) + test "public endpoint true" do + OpentelemetryBandit.setup(public_endpoint: true) + port = start_server() + + Req.get("http://localhost:#{port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + } + ) + + refute_receive {:span, + span( + name: :GET, + kind: :server, + parent_span_id: 13_235_353_014_750_950_193 + )} assert_receive {:span, span( - name: "HTTP GET /hello", + name: :GET, kind: :server, - status: {:status, :ok, _}, - attributes: attributes + links: links, + parent_span_id: :undefined )} - assert %{ - "net.peer.name": "localhost", - "http.method": "GET", - "http.target": "/hello", - "http.scheme": :http, - "http.status_code": 200, - "http.client_ip": "127.0.0.1" - } = :otel_attributes.map(attributes) + assert length(:otel_links.list(links)) == 1 + end + + def public_endpoint_fn(_conn, _opts) do + System.get_env("TENANT") != "internal" end - test "default span generation for 200 with x-forwarded-for" do - Req.get("http://localhost:4000/hello", headers: %{x_forwarded_for: "127.0.0.1"}) + test "public endpoint fn" do + OpentelemetryBandit.setup(public_endpoint_fn: {__MODULE__, :public_endpoint_fn, []}) + port = start_server() + + System.put_env("TENANT", "customer") + + Req.get("http://localhost:#{port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + } + ) + + refute_receive {:span, + span( + name: :GET, + kind: :server, + parent_span_id: 13_235_353_014_750_950_193 + )} assert_receive {:span, span( - name: "HTTP GET /hello", + name: :GET, kind: :server, - status: {:status, :ok, _}, - attributes: attributes + links: links, + parent_span_id: :undefined )} - assert %{ - "net.peer.name": "localhost", - "http.method": "GET", - "http.target": "/hello", - "http.scheme": :http, - "http.status_code": 200, - "http.client_ip": "127.0.0.1" - } = :otel_attributes.map(attributes) - end + assert length(:otel_links.list(links)) == 1 - test "default span generation for halted connection" do - Req.get("http://localhost:4000/fail", retry: false) + System.put_env("TENANT", "internal") + + Req.get("http://localhost:#{port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + } + ) assert_receive {:span, span( - name: "HTTP GET /fail", + name: :GET, kind: :server, - status: {:status, :ok, _}, - attributes: attributes + parent_span_id: 13_235_353_014_750_950_193 )} - assert %{ - "net.peer.name": "localhost", - "http.method": "GET", - "http.target": "/fail", - "http.scheme": :http, - "http.status_code": 500 - } = :otel_attributes.map(attributes) - end - - test "default span generation for 500 response" do - :telemetry.execute( - [:bandit, :request, :stop], - %{duration: 444, resp_body_bytes: 10}, - %{ - conn: nil, - status: 500, - error: "Internal Server Error", - method: "GET", - request_target: {nil, nil, nil, "/not_existing_route"} - } + refute_receive {:span, + span( + name: :GET, + kind: :server, + parent_span_id: :undefined + )} + + System.delete_env("PUBLIC") + end + + test "with all opt-ins" do + OpentelemetryBandit.setup( + opt_in_attrs: [ + ClientAttributes.client_port(), + HTTPAttributes.http_request_body_size(), + HTTPAttributes.http_response_body_size(), + NetworkAttributes.network_local_address(), + NetworkAttributes.network_local_port(), + NetworkAttributes.network_transport() + ], + request_headers: ["test-header"], + response_headers: ["content-type"] + ) + + port = start_server() + + Req.get!("http://localhost:#{port}/with_body", + headers: %{"test-header" => "request header"} ) assert_receive {:span, span( - name: "HTTP GET /not_existing_route", - kind: :error, - status: {:status, :error, "Internal Server Error"}, - attributes: attributes + name: :GET, + attributes: span_attrs )} - assert %{ - "http.url": _, - "http.method": "GET", - "http.status_code": 500, - "http.response_content_length": 10, - "net.transport": :"IP.TCP" - } = :otel_attributes.map(attributes) - end - - test "span when request_target is empty" do - :telemetry.execute( - [:bandit, :request, :stop], - %{duration: 444, resp_body_bytes: 10}, - %{ - conn: nil, - status: 500, - error: "Internal Server Error", - method: "GET", - request_target: nil + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {HTTPAttributes.http_request_body_size(), 0}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_body_size(), 29}, + {HTTPAttributes.http_response_status_code(), 200}, + {String.to_atom("#{HTTPAttributes.http_request_header()}.test-header"), + ["request header"]}, + {String.to_atom("#{HTTPAttributes.http_response_header()}.content-type"), + ["application/json; charset=utf-8"]}, + {NetworkAttributes.network_local_address(), "localhost"}, + {NetworkAttributes.network_local_port(), port}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/with_body"}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") + + client_port = Map.get(attrs, ClientAttributes.client_port()) + assert is_integer(client_port) + end + + def custom_client_header_sort(h1, h2) do + h1_priority = custom_client_header_priority(h1) + h2_priority = custom_client_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_client_header_priority({header_name, _value}) do + case header_name do + "custom-client" -> 1 + "x-forwarded-for" -> 2 + end + end + + def custom_server_header_sort(h1, h2) do + h1_priority = custom_server_header_priority(h1) + h2_priority = custom_server_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_server_header_priority({header_name, _value}) do + case header_name do + "custom-host" -> 1 + "x-forwarded-host" -> 2 + "forwarded" -> 3 + _ -> 4 + end + end + + def custom_scheme_header_sort(h1, h2) do + h1_priority = custom_scheme_header_priority(h1) + h2_priority = custom_scheme_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_scheme_header_priority({header_name, _value}) do + case header_name do + "custom-scheme" -> 1 + "x-forwarded-proto" -> 2 + end + end + + test "with custom header settings" do + opts = [ + client_address_headers: ["x-forwarded-for", "custom-client"], + client_headers_sort_fn: &__MODULE__.custom_client_header_sort/2, + scheme_headers: ["custom-scheme", "x-forwarded-proto"], + scheme_headers_sort_fn: &__MODULE__.custom_scheme_header_sort/2, + server_address_headers: ["custom-host", "forwarded", "host"], + server_headers_sort_fn: &__MODULE__.custom_server_header_sort/2 + ] + + OpentelemetryBandit.setup(opts) + port = start_server() + + Req.get("http://localhost:#{port}/hello", + headers: %{ + "forwarded" => + ~S(host=developer.mozilla.org:4321; for=192.0.2.60, for="[2001:db8:cafe::17]";proto=http;by=203.0.113.43), + "x-forwarded-proto" => "http", + "custom-scheme" => "https", + "custom-client" => "23.23.23.23", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" } ) assert_receive {:span, span( - name: "HTTP GET", - kind: :error, - status: {:status, :error, "Internal Server Error"}, - attributes: attributes + name: :GET, + kind: :server, + attributes: span_attrs )} - assert %{ - "http.url": _, - "http.method": "GET", - "http.status_code": 500, - "http.response_content_length": 10, - "net.transport": :"IP.TCP" - } = :otel_attributes.map(attributes) + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "23.23.23.23"}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {ServerAttributes.server_address(), "developer.mozilla.org"}, + {ServerAttributes.server_port(), 4321}, + {URLAttributes.url_scheme(), :https} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") end - test "exception catch span" do - Req.get("http://localhost:4000/exception", retry: false) + test "with missing user-agent" do + OpentelemetryBandit.setup() + port = start_server() + + {:ok, {{_, 200, _}, _, _}} = + :httpc.request(:get, {~c"http://localhost:#{port}/hello", []}, [], []) + + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + + assert Map.get(attrs, UserAgentAttributes.user_agent_original()) == "" + end + + test "with exception" do + OpentelemetryBandit.setup() + port = start_server() + + capture_log(fn -> + Req.get("http://localhost:#{port}/arithmetic_error", retry: false) + end) + + expected_status = OpenTelemetry.status(:error, "") assert_receive {:span, span( - name: "HTTP exception RuntimeError", - kind: :error, - status: {:status, :error, _} + name: :GET, + attributes: span_attrs, + events: events, + status: ^expected_status )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {ErrorAttributes.error_type(), ArithmeticError}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 500}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/arithmetic_error"}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + + [ + event( + name: :exception, + attributes: event_attributes + ) + ] = :otel_events.list(events) + + assert [ + ExceptionAttributes.exception_message(), + ExceptionAttributes.exception_stacktrace(), + ExceptionAttributes.exception_type() + ] == + Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) end - end - describe "websocket integration" do - test "span when request finished successfully" do - :telemetry.execute( - [:bandit, :websocket, :stop], - %{ - duration: 444, - send_binary_frame_bytes: 10, - recv_binary_frame_bytes: 15 - }, - %{} - ) + test "with throw" do + OpentelemetryBandit.setup() + port = start_server() + + capture_log(fn -> + Req.get("http://localhost:#{port}/throw_error", retry: false) + end) + + expected_status = OpenTelemetry.status(:error, "") assert_receive {:span, span( - name: "Websocket", - kind: :server, - status: {:status, :ok, _}, - attributes: attributes + name: :GET, + attributes: span_attributes, + events: events, + status: ^expected_status )} - assert %{ - "net.transport": :websocket, - "websocket.recv.binary.frame.bytes": 10, - "websocket.send.binary.frame.bytes": 15 - } = :otel_attributes.map(attributes) - end - - test "span when error is set" do - :telemetry.execute( - [:bandit, :websocket, :stop], - %{ - duration: 444, - send_binary_frame_bytes: 10, - recv_binary_frame_bytes: 15 - }, - %{error: "Internal Server Error"} - ) + for {attribute, expected_value} <- [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {ErrorAttributes.error_type(), "something"}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 500}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/throw_error"}, + {URLAttributes.url_scheme(), :http} + ] do + assert Map.get(:otel_attributes.map(span_attributes), attribute) == expected_value + end + + assert [] = :otel_events.list(events) + end + + test "with exit" do + OpentelemetryBandit.setup() + port = start_server() + + capture_log(fn -> + Req.get("http://localhost:#{port}/exit_error", retry: false) + end) + + expected_status = OpenTelemetry.status(:error, "") assert_receive {:span, span( - name: "Websocket", - kind: :error, - status: {:status, :error, _}, - attributes: attributes + name: :GET, + attributes: span_attributes, + events: events, + status: ^expected_status )} - assert %{ - "net.transport": :websocket, - "websocket.recv.binary.frame.bytes": 10, - "websocket.send.binary.frame.bytes": 15 - } = :otel_attributes.map(attributes) + for {attribute, expected_value} <- [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {ErrorAttributes.error_type(), :abnormal_reason}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 500}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/exit_error"}, + {URLAttributes.url_scheme(), :http} + ] do + assert Map.get(:otel_attributes.map(span_attributes), attribute) == expected_value + end + + assert [] = :otel_events.list(events) end - end - setup do - :otel_simple_processor.set_exporter(:otel_exporter_pid, self()) + test "with halted request" do + OpentelemetryBandit.setup() + port = start_server() - {:ok, _} = start_supervised({Bandit, plug: __MODULE__, port: 4000, startup_log: false}) + Req.get("http://localhost:#{port}/halted", retry: false) - OpentelemetryBandit.setup() + expected_status = OpenTelemetry.status(:error, "") - :ok + assert_receive {:span, + span( + name: :GET, + kind: :server, + attributes: span_attrs, + status: ^expected_status + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ErrorAttributes.error_type(), "500"}, + {HTTPAttributes.http_response_status_code(), 500}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + end end def hello(conn) do conn |> send_resp(200, "OK") end - def fail(conn) do + def with_body(conn) do + conn + |> put_resp_content_type("application/json") + |> send_resp(200, Jason.encode!(%{"a" => "b"})) + end + + def halted(conn) do conn |> send_resp(500, "Internal Server Error") |> halt() end - def exception(_conn) do - raise "boom" + def arithmetic_error(_conn) do + apply(:erlang, :+, [1, self()]) + end + + def throw_error(_conn) do + throw("something") + end + + def exit_error(_conn) do + exit(:abnormal_reason) end end diff --git a/instrumentation/opentelemetry_broadway/.formatter.exs b/instrumentation/opentelemetry_broadway/.formatter.exs new file mode 100644 index 00000000..0a70dc0f --- /dev/null +++ b/instrumentation/opentelemetry_broadway/.formatter.exs @@ -0,0 +1,5 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + line_length: 120 +] diff --git a/instrumentation/opentelemetry_broadway/.gitignore b/instrumentation/opentelemetry_broadway/.gitignore new file mode 100644 index 00000000..3e85c35b --- /dev/null +++ b/instrumentation/opentelemetry_broadway/.gitignore @@ -0,0 +1,29 @@ +# 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_broadway-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +# Elixir Language Server +/.elixir_ls/ \ No newline at end of file diff --git a/instrumentation/opentelemetry_broadway/CHANGELOG.md b/instrumentation/opentelemetry_broadway/CHANGELOG.md new file mode 100644 index 00000000..8d9f10f9 --- /dev/null +++ b/instrumentation/opentelemetry_broadway/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +## 0.3.0 + +### Changed + + * Update OpenTelemetry API and Semantic Conventions + +## 0.1.1 + + * Fix issue with Broadway messages with non-binary data in them. + +## 0.1.0 + + * Initial release diff --git a/instrumentation/opentelemetry_broadway/LICENSE b/instrumentation/opentelemetry_broadway/LICENSE new file mode 100644 index 00000000..91fab862 --- /dev/null +++ b/instrumentation/opentelemetry_broadway/LICENSE @@ -0,0 +1,190 @@ + 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 2021, 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/instrumentation/opentelemetry_broadway/README.md b/instrumentation/opentelemetry_broadway/README.md new file mode 100644 index 00000000..73c1783a --- /dev/null +++ b/instrumentation/opentelemetry_broadway/README.md @@ -0,0 +1,35 @@ +# opentelemetry_broadway + +[![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_cowboy)](https://hex.pm/packages/opentelemetry_cowboy) +![Build Status](https://github.com/open-telemetry/opentelemetry-erlang-contrib/workflows/Erlang/badge.svg) + +OpenTelemetry tracing for [Broadway](https://elixir-broadway.org/) pipelines. + +## Usage + +After installing, set up the handler in your application's `Application.start/2` callback, before your top-level supervisor starts: + +```elixir +def start(_type, _args) do + OpentelemetryBroadway.setup() + + Supervisor.start_link(...) +end +``` + +## Installation + +This library is available on Hex: + +```elixir +defp deps do + [ + {:opentelemetry_broadway, "~> 0.3"} + ] +end +``` + +## Credit + +This repository was [originally created](https://github.com/breakroom/opentelemetry_broadway) by [Tom Taylor](https://github.com/tomtaylor). diff --git a/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway.ex b/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway.ex new file mode 100644 index 00000000..7aefad1c --- /dev/null +++ b/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway.ex @@ -0,0 +1,142 @@ +defmodule OpentelemetryBroadway do + @moduledoc """ + OpenTelemetry tracing for [Broadway](https://elixir-broadway.org/) pipelines. + + It supports job start, stop, and exception events. + + ## Usage + + In your application's `c:Application.start/2` callback: + + def start(_type, _args) do + :ok = OpentelemetryBroadway.setup() + + # ... + end + + """ + + alias OpenTelemetry.SemanticConventions + alias OpenTelemetry.Span + alias OpenTelemetry.SemanticConventions.Trace + + require Trace + + @tracer_id __MODULE__ + + @doc """ + Attaches the Telemetry handlers, returning `:ok` if successful. + """ + @spec setup :: :ok + def setup do + :ok = + :telemetry.attach( + "#{__MODULE__}.message_start", + [:broadway, :processor, :message, :start], + &__MODULE__.handle_message_start/4, + [] + ) + + :ok = + :telemetry.attach( + "#{__MODULE__}.message_stop", + [:broadway, :processor, :message, :stop], + &__MODULE__.handle_message_stop/4, + [] + ) + + :ok = + :telemetry.attach( + "#{__MODULE__}.job_exception", + [:broadway, :processor, :message, :exception], + &__MODULE__.handle_message_exception/4, + [] + ) + + :ok + end + + @doc false + def handle_message_start( + _event, + _measurements, + %{ + processor_key: processor_key, + topology_name: topology_name, + name: name, + message: %Broadway.Message{} = message + } = metadata, + _config + ) do + span_name = "#{inspect(topology_name)}/#{Atom.to_string(processor_key)} process" + client_id = inspect(name) + + attributes = %{ + SemanticConventions.Trace.messaging_system() => :broadway, + SemanticConventions.Trace.messaging_operation() => :process, + SemanticConventions.Trace.messaging_consumer_id() => client_id + } + + attributes = + if is_binary(message.data) do + Map.put( + attributes, + SemanticConventions.Trace.messaging_message_payload_size_bytes(), + byte_size(message.data) + ) + else + attributes + end + + OpentelemetryTelemetry.start_telemetry_span(@tracer_id, span_name, metadata, %{ + kind: :consumer, + attributes: attributes + }) + end + + @doc false + def handle_message_stop( + _event, + _measurements, + %{message: %Broadway.Message{} = message} = metadata, + _config + ) do + status = + case message.status do + :ok -> OpenTelemetry.status(:ok) + {:failed, err} -> OpenTelemetry.status(:error, format_error(err)) + end + + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + OpenTelemetry.Span.set_status(ctx, status) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + @doc false + def handle_message_exception( + _event, + _measurements, + %{ + kind: kind, + reason: reason, + stacktrace: stacktrace + } = metadata, + _config + ) do + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + + # Record exception and mark the span as errored + Span.record_exception(ctx, reason, stacktrace) + + Span.set_status( + ctx, + OpenTelemetry.status(:error, Exception.format_banner(kind, reason, stacktrace)) + ) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + defp format_error(err) when is_binary(err), do: err + defp format_error(err), do: inspect(err) +end diff --git a/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway/job_handler.ex b/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway/job_handler.ex new file mode 100644 index 00000000..ba6f8019 --- /dev/null +++ b/instrumentation/opentelemetry_broadway/lib/opentelemetry_broadway/job_handler.ex @@ -0,0 +1,118 @@ +defmodule OpentelemetryBroadway.JobHandler do + alias OpenTelemetry.SemanticConventions + alias OpenTelemetry.Span + alias OpenTelemetry.SemanticConventions.Trace + + require Trace + + @tracer_id __MODULE__ + + def attach() do + :ok = + :telemetry.attach( + "#{__MODULE__}.message_start", + [:broadway, :processor, :message, :start], + &__MODULE__.handle_message_start/4, + [] + ) + + :ok = + :telemetry.attach( + "#{__MODULE__}.message_stop", + [:broadway, :processor, :message, :stop], + &__MODULE__.handle_message_stop/4, + [] + ) + + :ok = + :telemetry.attach( + "#{__MODULE__}.job_exception", + [:broadway, :processor, :message, :exception], + &__MODULE__.handle_message_exception/4, + [] + ) + + :ok + end + + def handle_message_start( + _event, + _measurements, + %{ + processor_key: processor_key, + topology_name: topology_name, + name: name, + message: %Broadway.Message{} = message + } = metadata, + _config + ) do + span_name = "#{inspect(topology_name)}/#{Atom.to_string(processor_key)} process" + client_id = inspect(name) + + attributes = %{ + SemanticConventions.Trace.messaging_system() => :broadway, + SemanticConventions.Trace.messaging_operation() => :process, + SemanticConventions.Trace.messaging_consumer_id() => client_id + } + + attributes = + if is_binary(message.data) do + Map.put( + attributes, + SemanticConventions.Trace.messaging_message_payload_size_bytes(), + byte_size(message.data) + ) + else + attributes + end + + OpentelemetryTelemetry.start_telemetry_span(@tracer_id, span_name, metadata, %{ + kind: :consumer, + attributes: attributes + }) + end + + def handle_message_stop( + _event, + _measurements, + %{message: %Broadway.Message{} = message} = metadata, + _config + ) do + status = + case message.status do + :ok -> OpenTelemetry.status(:ok) + {:failed, err} -> OpenTelemetry.status(:error, format_error(err)) + end + + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + OpenTelemetry.Span.set_status(ctx, status) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + def handle_message_exception( + _event, + _measurements, + %{ + kind: kind, + reason: reason, + stacktrace: stacktrace + } = metadata, + _config + ) do + ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + + # Record exception and mark the span as errored + Span.record_exception(ctx, reason, stacktrace) + + Span.set_status( + ctx, + OpenTelemetry.status(:error, Exception.format_banner(kind, reason, stacktrace)) + ) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + defp format_error(err) when is_binary(err), do: err + defp format_error(err), do: inspect(err) +end diff --git a/instrumentation/opentelemetry_broadway/mix.exs b/instrumentation/opentelemetry_broadway/mix.exs new file mode 100644 index 00000000..a5d8c8e6 --- /dev/null +++ b/instrumentation/opentelemetry_broadway/mix.exs @@ -0,0 +1,59 @@ +defmodule OpentelemetryBroadway.MixProject do + use Mix.Project + + @version "0.3.0" + + def project do + [ + app: :opentelemetry_broadway, + version: @version, + elixir: "~> 1.12", + start_permanent: Mix.env() == :prod, + docs: [ + source_url_pattern: + "https://github.com/opentelemetry/opentelemetry-erlang-contrib/blob/main/instrumentation/opentelemetry_broadway/%{path}#L%{line}", + main: "OpentelemetryBroadway", + extras: ["README.md"] + ], + deps: deps(), + elixirc_paths: elixirc_paths(Mix.env()), + package: [ + name: "opentelemetry_broadway", + description: "OpenTelemetry tracing for Broadway", + maintainers: ["Tom Taylor"], + licenses: ["Apache-2.0"], + files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*), + source_url: + "https://github.com/opentelemetry/opentelemetry-erlang-contrib/blob/main/instrumentation/opentelemetry_broadway", + links: %{ + "GitHub" => + "https://github.com/opentelemetry/opentelemetry-erlang-contrib/blob/main/instrumentation/opentelemetry_broadway" + } + ] + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:broadway, "~> 1.0"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_telemetry, "~> 1.1"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:telemetry, "~> 1.0"}, + {:opentelemetry, "~> 1.5", only: [:test]}, + {:opentelemetry_exporter, "~> 1.8", only: [:test]}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false} + ] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] +end diff --git a/instrumentation/opentelemetry_broadway/mix.lock b/instrumentation/opentelemetry_broadway/mix.lock new file mode 100644 index 00000000..8a33a1cb --- /dev/null +++ b/instrumentation/opentelemetry_broadway/mix.lock @@ -0,0 +1,25 @@ +%{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "broadway": {:hex, :broadway, "1.2.1", "83a1567423c26885e15f6cd8670ca790370af2fcff2ede7fa88c5ea793087a67", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.7 or ~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68ae63d83b55bdca0f95cd49feee5fb74c5a6bec557caf940860fe07dbc8a4fb"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, +} diff --git a/instrumentation/opentelemetry_broadway/test/opentelemetry_broadway_test.exs b/instrumentation/opentelemetry_broadway/test/opentelemetry_broadway_test.exs new file mode 100644 index 00000000..8c6b48be --- /dev/null +++ b/instrumentation/opentelemetry_broadway/test/opentelemetry_broadway_test.exs @@ -0,0 +1,90 @@ +defmodule OpentelemetryBroadwayTest do + use ExUnit.Case + doctest OpentelemetryBroadway + + require OpenTelemetry.Tracer + require OpenTelemetry.Span + require Record + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do + Record.defrecord(name, spec) + end + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do + Record.defrecord(name, spec) + end + + setup do + :application.stop(:opentelemetry) + :application.set_env(:opentelemetry, :tracer, :otel_tracer_default) + + :application.set_env(:opentelemetry, :processors, [ + {:otel_batch_processor, %{scheduled_delay_ms: 1, exporter: {:otel_exporter_pid, self()}}} + ]) + + :application.start(:opentelemetry) + + TestHelpers.remove_handlers() + :ok = OpentelemetryBroadway.setup() + + :ok + end + + test "records span on succesful message" do + ref = Broadway.test_message(TestBroadway, "success") + + #  Confirm the message was processed + assert_receive {:ack, ^ref, [%{data: "success"}], []} + + expected_status = OpenTelemetry.status(:ok) + + assert_receive {:span, + span( + name: "TestBroadway/default process", + attributes: attributes, + parent_span_id: :undefined, + kind: :consumer, + status: ^expected_status + )} + + assert %{ + "messaging.system": :broadway, + "messaging.operation": :process, + "messaging.message_payload_size_bytes": 7 + } = :otel_attributes.map(attributes) + end + + test "records span on message which fails" do + ref = Broadway.test_message(TestBroadway, "error") + + #  Confirm the message was processed + assert_receive {:ack, ^ref, [], [%{data: "error"}]} + + expected_status = OpenTelemetry.status(:error, "something went wrong") + + assert_receive {:span, + span( + name: "TestBroadway/default process", + parent_span_id: :undefined, + kind: :consumer, + status: ^expected_status + )} + end + + test "records span on an exception being thrown" do + ref = Broadway.test_message(TestBroadway, "exception") + + #  Confirm the message was processed + assert_receive {:ack, ^ref, [], [%{data: "exception"}]} + + expected_status = OpenTelemetry.status(:error, "** (RuntimeError) an exception occurred") + + assert_receive {:span, + span( + name: "TestBroadway/default process", + parent_span_id: :undefined, + kind: :consumer, + status: ^expected_status + )} + end +end diff --git a/instrumentation/opentelemetry_broadway/test/support/test_broadway.ex b/instrumentation/opentelemetry_broadway/test/support/test_broadway.ex new file mode 100644 index 00000000..2f1d32ed --- /dev/null +++ b/instrumentation/opentelemetry_broadway/test/support/test_broadway.ex @@ -0,0 +1,32 @@ +defmodule TestBroadway do + use Broadway + + def start_link() do + Broadway.start_link(__MODULE__, + name: __MODULE__, + producer: [ + module: {Broadway.DummyProducer, []} + ], + processors: [ + default: [] + ], + batchers: [ + default: [] + ] + ) + end + + @impl true + def handle_message(_processor, %Broadway.Message{} = message, _context) do + case message.data do + "success" -> message + "error" -> Broadway.Message.failed(message, "something went wrong") + "exception" -> raise RuntimeError, "an exception occurred" + end + end + + @impl true + def handle_batch(_batcher, messages, _batch_info, _context) do + messages + end +end diff --git a/instrumentation/opentelemetry_broadway/test/support/test_helpers.ex b/instrumentation/opentelemetry_broadway/test/support/test_helpers.ex new file mode 100644 index 00000000..4068c19c --- /dev/null +++ b/instrumentation/opentelemetry_broadway/test/support/test_helpers.ex @@ -0,0 +1,7 @@ +defmodule TestHelpers do + def remove_handlers do + Enum.each(:telemetry.list_handlers([:broadway]), fn handler -> + :telemetry.detach(handler[:id]) + end) + end +end diff --git a/instrumentation/opentelemetry_broadway/test/test_helper.exs b/instrumentation/opentelemetry_broadway/test/test_helper.exs new file mode 100644 index 00000000..15caaf40 --- /dev/null +++ b/instrumentation/opentelemetry_broadway/test/test_helper.exs @@ -0,0 +1,3 @@ +ExUnit.start() + +{:ok, _} = TestBroadway.start_link() diff --git a/instrumentation/opentelemetry_cowboy/CHANGELOG.md b/instrumentation/opentelemetry_cowboy/CHANGELOG.md index 9508d0ea..f969232d 100644 --- a/instrumentation/opentelemetry_cowboy/CHANGELOG.md +++ b/instrumentation/opentelemetry_cowboy/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +# v1.0.0 + +NOTE: This release includes numerous breaking changes with implementation +of Semantic Conventions introduced in v1.20. The full list of changes +are enumerated in the [HTTP Stability Migration Guide](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/). + +### Changed + +* Semantic Conventions v1.26.0 compliance +* Added public endpoint settings for determining whether to continue a trace or create a link + ## v0.3.0 ### Changed diff --git a/instrumentation/opentelemetry_cowboy/README.md b/instrumentation/opentelemetry_cowboy/README.md index 035bb868..b0d9c15b 100644 --- a/instrumentation/opentelemetry_cowboy/README.md +++ b/instrumentation/opentelemetry_cowboy/README.md @@ -21,13 +21,13 @@ There is no additional prerequisite setup for [plug_cowboy](https://hex.pm/packa ```erlang {deps, [ - {opentelemetry_cowboy, "~> 0.3"} + {opentelemetry_cowboy, "~> 1.0"} ]} ``` ```elixir def deps do [ - {:opentelemetry_cowboy, "~> 0.3"} + {:opentelemetry_cowboy, "~> 1.0"} ] end ``` diff --git a/instrumentation/opentelemetry_cowboy/rebar.config b/instrumentation/opentelemetry_cowboy/rebar.config index f7f18b92..a994913a 100644 --- a/instrumentation/opentelemetry_cowboy/rebar.config +++ b/instrumentation/opentelemetry_cowboy/rebar.config @@ -1,9 +1,11 @@ {erl_opts, [debug_info]}. {deps, [ {cowboy_telemetry, "~> 0.4"}, - {opentelemetry_api, "~> 1.3"}, - {opentelemetry_telemetry, "~> 1.0"}, - {telemetry, "~> 1.0"} + {opentelemetry_api, "~> 1.4"}, + {opentelemetry_semantic_conventions, "~> 1.27"}, + {opentelemetry_telemetry, "~> 1.1"}, + {otel_http, "~> 0.2"}, + {telemetry, "~> 1.1"} ]}. {project_plugins, [covertool, @@ -23,8 +25,8 @@ {profiles, [{test, [{erl_opts, [nowarn_export_all]}, {deps, [ - {opentelemetry, "~> 1.4"}, - {opentelemetry_exporter, "~> 1.7"}, + {opentelemetry, "~> 1.5"}, + {opentelemetry_exporter, "~> 1.8"}, {cowboy, "~> 2.10"} ]}, {paths, ["src", "test/support"]}, diff --git a/instrumentation/opentelemetry_cowboy/rebar.lock b/instrumentation/opentelemetry_cowboy/rebar.lock index 5d5f06a6..1ec82aab 100644 --- a/instrumentation/opentelemetry_cowboy/rebar.lock +++ b/instrumentation/opentelemetry_cowboy/rebar.lock @@ -2,32 +2,35 @@ [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.12.0">>},1}, {<<"cowboy_telemetry">>,{pkg,<<"cowboy_telemetry">>,<<"0.4.0">>},0}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},2}, - {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.3.0">>},0}, + {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.4.0">>},0}, {<<"opentelemetry_semantic_conventions">>, - {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, - 1}, + {pkg,<<"opentelemetry_semantic_conventions">>,<<"1.27.0">>}, + 0}, {<<"opentelemetry_telemetry">>, - {pkg,<<"opentelemetry_telemetry">>,<<"1.1.1">>}, + {pkg,<<"opentelemetry_telemetry">>,<<"1.1.2">>}, 0}, + {<<"otel_http">>,{pkg,<<"otel_http">>,<<"0.2.0">>},0}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, - {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.2.1">>},0}]}. + {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.3.0">>},0}]}. [ {pkg_hash,[ {<<"cowboy">>, <<"F276D521A1FF88B2B9B4C54D0E753DA6C66DD7BE6C9FCA3D9418B561828A3731">>}, {<<"cowboy_telemetry">>, <<"F239F68B588EFA7707ABCE16A84D0D2ACF3A0F50571F8BB7F56A15865AAE820C">>}, {<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>}, - {<<"opentelemetry_api">>, <<"03E2177F28DD8D11AAA88E8522C81C2F6A788170FE52F7A65262340961E663F9">>}, - {<<"opentelemetry_semantic_conventions">>, <<"B67FE459C2938FCAB341CB0951C44860C62347C005ACE1B50F8402576F241435">>}, - {<<"opentelemetry_telemetry">>, <<"4A73BFA29D7780FFE33DB345465919CEF875034854649C37AC789EB8E8F38B21">>}, + {<<"opentelemetry_api">>, <<"63CA1742F92F00059298F478048DFB826F4B20D49534493D6919A0DB39B6DB04">>}, + {<<"opentelemetry_semantic_conventions">>, <<"ACD0194A94A1E57D63DA982EE9F4A9F88834AE0B31B0BD850815FE9BE4BBB45F">>}, + {<<"opentelemetry_telemetry">>, <<"410AB4D76B0921F42DBCCBE5A7C831B8125282850BE649EE1F70050D3961118A">>}, + {<<"otel_http">>, <<"B17385986C7F1B862F5D577F72614ECAA29DE40392B7618869999326B9A61D8A">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, - {<<"telemetry">>, <<"68FDFE8D8F05A8428483A97D7AAB2F268AAFF24B49E0F599FAA091F1D4E7F61C">>}]}, + {<<"telemetry">>, <<"FEDEBBAE410D715CF8E7062C96A1EF32EC22E764197F70CDA73D82778D61E7A2">>}]}, {pkg_hash_ext,[ {<<"cowboy">>, <<"8A7ABE6D183372CEB21CAA2709BEC928AB2B72E18A3911AA1771639BEF82651E">>}, {<<"cowboy_telemetry">>, <<"7D98BAC1EE4565D31B62D59F8823DFD8356A169E7FCBB83831B8A5397404C9DE">>}, {<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>}, - {<<"opentelemetry_api">>, <<"B9E5FF775FD064FA098DBA3C398490B77649A352B40B0B730A6B7DC0BDD68858">>}, - {<<"opentelemetry_semantic_conventions">>, <<"D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895">>}, - {<<"opentelemetry_telemetry">>, <<"EE43B14E6866123A3EE1344E3C0D3D7591F4537542C2A925FCDBF46249C9B50B">>}, + {<<"opentelemetry_api">>, <<"3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58">>}, + {<<"opentelemetry_semantic_conventions">>, <<"9681CCAA24FD3D810B4461581717661FD85FF7019B082C2DFF89C7D5B1FC2864">>}, + {<<"opentelemetry_telemetry">>, <<"641AB469DEB181957AC6D59BCE6E1321D5FE2A56DF444FC9C19AFCAD623AB253">>}, + {<<"otel_http">>, <<"F2BEADF922C8CFEB0965488DD736C95CC6EA8B9EFCE89466B3904D317D7CC717">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, - {<<"telemetry">>, <<"DAD9CE9D8EFFC621708F99EAC538EF1CBE05D6A874DD741DE2E689C47FEAFED5">>}]} + {<<"telemetry">>, <<"7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6">>}]} ]. diff --git a/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.app.src b/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.app.src index 0dac1d31..82e79f49 100644 --- a/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.app.src +++ b/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.app.src @@ -1,6 +1,6 @@ {application, opentelemetry_cowboy, [{description, "OpenTelemetry Cowboy Instrumentation"}, - {vsn, "0.3.0"}, + {vsn, "1.0.0"}, {registered, []}, {applications, [kernel, diff --git a/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.erl b/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.erl index 6cb361a4..8c31ce4f 100644 --- a/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.erl +++ b/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.erl @@ -1,63 +1,350 @@ -module(opentelemetry_cowboy). +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + -export([ setup/0, setup/1, - handle_event/4]). + handle_event/4, + default_public_endpoint_fn/2]). -include_lib("opentelemetry_api/include/opentelemetry.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/client_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/error_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/network_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/server_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/url_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/user_agent_attributes.hrl"). + +-include_lib("opentelemetry_semantic_conventions/include/incubating/attributes/http_attributes.hrl"). -define(TRACER_ID, ?MODULE). +?MODULEDOC(""" +OpenTelemetry instrumentation for `cowboy`. + +## Semantic Conventions + +All required and recommended Server HTTP Span semantic conventions are implemented. +Supported opt-in attributes can be configured using the `opt_in_attrs` option. + +## Options + +### Opt-in Semantic Convention Attributes + +Otel SemConv requires users to explicitly opt in for any attribute with a +requirement level of `opt-in`. To ensure compatibility, always use the +SemConv attribute. + +Example: +``` +OptInAttrs = [{?HTTP_REQUEST_BODY_SIZE, true}]` +opentelemetry_cowboy:setup(#{opt_in_attrs => OptInAttrs) +``` + +#### Request and Response Headers as Opt-in Attributes + +Request and response header attributes are opt-in and can be set with the +`request_headers` and `response_headers` options. Values should be lower-case. + +``` +opentelemetry_cowboy:setup(#{request_headers => ["x-customer-id"]}) +``` + +### Public Endpoint + +Setting an endpoint as public will result in any propagated trace to be added as a link, +rather than a continuation of an existing trace. The `public_endpoint` option should be set +to `true` if an endpoint only accepts public traffic to prevent missing root spans. By default, +the endpoint is handled as non-public, resulting in traces being continued rather than linked. + +In a mixed traffic environment, an MFA can be supplied to determine whether to +treat a request as public. This function is executed on every request, so refrain +from expensive operations such as lookups to external systems. The function must +be a predicate function of arity-2, accepting the `Req` from the request as +the first argument and user-supplied options as the second. Any dynamic +information used in comparisons should be supplied at setup time for efficiency. + +Example: +``` +-module(public_endpoint). + +is_public_request(Req, Opts) -> + # return true if request was public + +opentelemetry_cowboy:setup(#{public_endpoint_fn => {public_endpoint, fun is_public_request/2, []}}). +``` +"""). + +default_opts() -> + #{ + client_address_headers => [<<"forwarded">>, <<"x-forwarded-for">>], + client_headers_sort_fn => undefined, + handler_id => otel_cowboy, + opt_in_attrs => [], + public_endpoint => false, + public_endpoint_fn => {?MODULE, default_public_endpoint_fn, []}, + request_headers => [], + response_headers => [], + scheme_headers => [<<"forwarded">>, <<"x-forwarded-proto">>], + scheme_headers_sort_fn => undefined, + server_address_headers => [<<"forwarded">>, <<"x-forwarded-host">>, <<"host">>], + server_address_headers_sort_fn => undefined + }. + +?DOC(""" +Initializes and configures the telemetry handlers. + +Supported options: +* `client_address_headers` - Headers to use for extracting original client request address info. Default: `[<<"forwarded">>, <<"x-forwarded-for">>]` +* `client_headers_sort_fn` - Custom client header sort fn. See `otel_http` for more info. Default: `undefined` +* `handler_id` - Only set when running multiple instances on different endpoints. Default: `otel_cowboy` +* `opt_in_attrs` - Use semantic conventions library to ensure compatibility, e.g. `[{?HTTP_REQUEST_BODY_SIZE, true}]`. Default: `[]` +* `public_endpoint` - Endpoint is public. Propagated traces will be added as a link. Default: `false` +* `public_endpoint_fn` - Default function returns `false`. See docs for more info. +* `request_headers` - List of request headers to add as attributes. (lowercase). Default: `[]` +* `response_headers` - List of response headers to add as attributes. (lowercase). Default: `[]` +* `scheme_headers` - Headers to use for extracting original client request scheme. Default: `[<<"forwarded">>, <<"x-forwarded-proto">>]` +* `scheme_headers_sort_fn` - Custom scheme header sort fn. See `otel_http` for more info. Default: `undefined` +* `server_address_headers` - Headers to use for extracting original server address info. Default: `[<<"forwarded">>, <<"x-forwarded-host">>, <<"host">>]` +* `server_address_headers_sort_fn` - Custom server header sort fn. See `otel_http` for more info. Default: `undefined` +"""). -spec setup() -> ok. setup() -> - setup([]). + setup(default_opts()). --spec setup([]) -> ok. -setup(_Opts) -> - attach_event_handlers(), +-spec setup([] | map()) -> ok. +setup(Opts) when is_list(Opts) -> + setup(maps:from_list(Opts)); +setup(Opts) -> + InitialConfig = maps:merge(default_opts(), Opts), + OptInAttrs = maps:get(opt_in_attrs, InitialConfig), + ReversedClientAddressHeaders = lists:reverse(maps:get(client_address_headers, InitialConfig)), + ReversedSchemeHeaders = lists:reverse(maps:get(scheme_headers, InitialConfig)), + FinalOpts = maps:merge(InitialConfig, #{ + client_address_headers => ReversedClientAddressHeaders, + opt_in_attrs => OptInAttrs, + scheme_headers => ReversedSchemeHeaders + }), + attach_event_handlers(FinalOpts), ok. -attach_event_handlers() -> +attach_event_handlers(Config) -> Events = [ [cowboy, request, early_error], [cowboy, request, start], [cowboy, request, stop], [cowboy, request, exception] ], - telemetry:attach_many(opentelemetry_cowboy_handlers, Events, fun ?MODULE:handle_event/4, #{}). - -handle_event([cowboy, request, start], _Measurements, #{req := Req} = Meta, _Config) -> - Headers = maps:get(headers, Req), - otel_propagator_text_map:extract(maps:to_list(Headers)), - {RemoteIP, _Port} = maps:get(peer, Req), - Method = maps:get(method, Req), - - Attributes = #{ - 'http.client_ip' => client_ip(Headers, RemoteIP), - 'http.flavor' => http_flavor(Req), - 'http.host' => maps:get(host, Req), - 'http.host.port' => maps:get(port, Req), - 'http.method' => Method, - 'http.scheme' => maps:get(scheme, Req), - 'http.target' => maps:get(path, Req), - 'http.user_agent' => maps:get(<<"user-agent">>, Headers, <<"">>), - 'net.host.ip' => iolist_to_binary(inet:ntoa(RemoteIP)), - 'net.transport' => 'IP.TCP' - }, - SpanName = iolist_to_binary([<<"HTTP ">>, Method]), - Opts = #{attributes => Attributes, kind => ?SPAN_KIND_SERVER}, - otel_telemetry:start_telemetry_span(?TRACER_ID, SpanName, Meta, Opts); + telemetry:attach_many({?MODULE, maps:get(handler_id, Config)}, Events, fun ?MODULE:handle_event/4, Config). + +parse_method(Method) -> + case Method of + <<"CONNECT">> -> ?HTTP_REQUEST_METHOD_VALUES_CONNECT; + <<"DELETE">> -> ?HTTP_REQUEST_METHOD_VALUES_DELETE; + <<"GET">> -> ?HTTP_REQUEST_METHOD_VALUES_GET; + <<"HEAD">> -> ?HTTP_REQUEST_METHOD_VALUES_HEAD; + <<"OPTIONS">> -> ?HTTP_REQUEST_METHOD_VALUES_OPTIONS; + <<"PATCH">> -> ?HTTP_REQUEST_METHOD_VALUES_PATCH; + <<"POST">> -> ?HTTP_REQUEST_METHOD_VALUES_POST; + <<"PUT">> -> ?HTTP_REQUEST_METHOD_VALUES_PUT; + <<"TRACE">> -> ?HTTP_REQUEST_METHOD_VALUES_TRACE; + _ -> ?HTTP_REQUEST_METHOD_VALUES_OTHER + end. + +extract_headers(Headers, Keys) -> + maps:filter(fun(K,_V) -> lists:member(K, Keys) end, Headers). + +set_req_header_attrs(Attrs, _ReqHeaders, #{request_headers := []}) -> Attrs; +set_req_header_attrs(Attrs, ReqHeaders, #{request_headers := HeadersAttrs}) -> + maps:merge(Attrs, otel_http:extract_headers_attributes(request, ReqHeaders, HeadersAttrs)). + +set_resp_header_attrs(Attrs, _RespHeaders, #{response_headers := []}) -> Attrs; +set_resp_header_attrs(Attrs, RespHeaders, #{response_headers := HeadersAttrs}) -> + maps:merge(Attrs, otel_http:extract_headers_attributes(response, RespHeaders, HeadersAttrs)). + +otel_http_extract_client_info(Headers, undefined) -> + otel_http:extract_client_info(Headers); +otel_http_extract_client_info(Headers, SortFn) -> + % sort expects a list - need to convert + otel_http:extract_client_info(maps:to_list(Headers), SortFn). + +extract_client_address(Req, Config) -> + #{ + client_address_headers := ClientAddrHeaders, + client_headers_sort_fn := SortFn + } = Config, + #{ + headers := Headers, + peer := {PeerRemoteIP, PeerPort} + } = Req, + ClientHeaders = extract_headers(Headers, ClientAddrHeaders), + case otel_http_extract_client_info(ClientHeaders, SortFn) of + #{ip := undefined} -> + #{ip => ip_to_binary(PeerRemoteIP), port => PeerPort}; + ClientAddress -> + ClientAddress + end. + + +extract_server_address(ReqHeaders, Config) -> + #{ + server_address_headers := ServerAddrHeaders, + server_address_headers_sort_fn := SortFn + } = Config, + ServerHeaders = extract_headers(ReqHeaders, ServerAddrHeaders), + otel_http_extract_server_info(ServerHeaders, SortFn). + +otel_http_extract_server_info(Headers, undefined) -> + otel_http:extract_server_info(Headers); +otel_http_extract_server_info(Headers, SortFn) -> + otel_http:extract_server_info(maps:to_list(Headers), SortFn). -handle_event([cowboy, request, stop], Measurements, Meta, _Config) -> +set_server_address_attrs(Attrs, ReqHeaders, Config) -> + case extract_server_address(ReqHeaders, Config) of + #{address := undefined} -> + Attrs; + #{address := Address, port := undefined} -> + maps:put(?SERVER_ADDRESS, Address, Attrs); + #{address := Address, port := Port} -> + maps:merge(Attrs, #{ + ?SERVER_ADDRESS => Address, + ?SERVER_PORT => Port + }) + end. + +otel_http_extract_scheme(Headers, undefined) -> + otel_http:extract_scheme(Headers); +otel_http_extract_scheme(Headers, SortFn) -> + otel_http:extract_scheme(maps:to_list(Headers), SortFn). + +extract_scheme(Req, Config) -> + #{ + scheme_headers := SchemeHeaders, + scheme_headers_sort_fn := SortFn + } = Config, + #{ + headers := Headers, + scheme := ReqScheme + } = Req, + SchemeHeaders1 = extract_headers(Headers, SchemeHeaders), + case otel_http_extract_scheme(SchemeHeaders1, SortFn) of + undefined -> + case ReqScheme of + <<"http">> -> + http; + <<"https">> -> + https + end; + ParsedScheme -> + ParsedScheme + end. + + +ip_to_binary(IP) -> + iolist_to_binary(inet:ntoa(IP)). + +is_public_endpoint(_Req, #{public_endpoint := true}) -> true; +is_public_endpoint(Req, #{public_endpoint_fn := {M, F, A}}) -> + apply(M, F, [Req, A]). + +default_public_endpoint_fn(_, _) -> false. + +opt_in_attrs() -> + #{ + ?CLIENT_PORT => undefined, + ?NETWORK_LOCAL_ADDRESS => undefined, + ?NETWORK_LOCAL_PORT => undefined, + ?NETWORK_TRANSPORT => ?NETWORK_TRANSPORT_VALUES_TCP + }. + +handle_event([cowboy, request, start], _Measurements, #{req := Req} = Meta, Config) -> + #{ + headers := ReqHeaders, + host := LocalHost, + method := ReqMethod, + path := Path, + peer := {PeerRemoteIP, PeerPort}, + port := LocalPort, + qs := QueryString, + version := Version + } = Req, + #{opt_in_attrs := OptedInAttrs} = Config, + #{ip := ClientIP, port := ClientPort} = extract_client_address(Req, Config), + DefaultOptInAttrs = opt_in_attrs(), + OptInAttrs = DefaultOptInAttrs#{ + ?CLIENT_PORT := ClientPort, + ?NETWORK_LOCAL_ADDRESS := LocalHost, + ?NETWORK_LOCAL_PORT := LocalPort + }, + + Method = parse_method(ReqMethod), + + Attrs1 = #{ + ?CLIENT_ADDRESS => ClientIP, + ?HTTP_REQUEST_METHOD => Method, + ?NETWORK_PEER_ADDRESS => ip_to_binary(PeerRemoteIP), + ?NETWORK_PEER_PORT => PeerPort, + ?URL_PATH => Path, + ?URL_SCHEME => extract_scheme(Req, Config), + ?USER_AGENT_ORIGINAL => maps:get(<<"user-agent">>, ReqHeaders, <<"">>)}, + Attrs2 = set_network_protocol_attrs(Attrs1, Version), + Attrs3 = set_query_string_attr(Attrs2, QueryString), + Attrs4 = set_server_address_attrs(Attrs3, ReqHeaders, Config), + Attrs5 = set_req_header_attrs(Attrs4, ReqHeaders, Config), + AttrsFinal = maps:merge(Attrs5, maps:filter(fun(K,_V) -> lists:member(K, OptedInAttrs) end, OptInAttrs)), + + SpanName = + case Method of + ?HTTP_REQUEST_METHOD_VALUES_OTHER -> + 'HTTP'; + _ -> + Method + end, + case is_public_endpoint(Req, Config) of + false -> + otel_propagator_text_map:extract(maps:to_list(ReqHeaders)), + otel_telemetry:start_telemetry_span(?TRACER_ID, SpanName, Meta, #{ + attributes => AttrsFinal, + kind => ?SPAN_KIND_SERVER + }); + true -> + PropagatedCtx = otel_propagator_text_map:extract_to(otel_ctx:new(), maps:to_list(ReqHeaders)), + SpanCtx = otel_tracer:current_span_ctx(PropagatedCtx), + otel_telemetry:start_telemetry_span(?TRACER_ID, SpanName, Meta, #{ + attributes => AttrsFinal, + kind => ?SPAN_KIND_SERVER, + links => opentelemetry:links([SpanCtx]) + }) + end; + + +handle_event([cowboy, request, stop], Measurements, Meta, Config) -> Ctx = otel_telemetry:set_current_telemetry_span(?TRACER_ID, Meta), - Status = maps:get(resp_status, Meta), - Attributes = #{ - 'http.request_content_length' => maps:get(req_body_length, Measurements), - 'http.response_content_length' => maps:get(resp_body_length, Measurements) + #{ + resp_headers := RespHeaders, + resp_status := Status + } = Meta, + #{ + opt_in_attrs := OptedInAttrs + } = Config, + #{ + req_body_length := ReqBodyLength, + resp_body_length := RespBodyLength + } = Measurements, + % these are opt-in + OptInAttrs = #{ + ?HTTP_REQUEST_BODY_SIZE => ReqBodyLength, + ?HTTP_RESPONSE_BODY_SIZE => RespBodyLength }, - otel_span:set_attributes(Ctx, Attributes), StatusCode = transform_status_to_code(Status), + case StatusCode of undefined -> case maps:get(error, Meta, undefined) of @@ -69,47 +356,100 @@ handle_event([cowboy, request, stop], Measurements, Meta, _Config) -> ok end; StatusCode when StatusCode >= 500 -> - otel_span:set_attribute(Ctx, 'http.status_code', StatusCode), + Attrs = set_resp_header_attrs(#{ + ?HTTP_RESPONSE_STATUS_CODE => StatusCode, + ?ERROR_TYPE => integer_to_binary(StatusCode) + }, RespHeaders, Config), + FinalAttrs = + maps:merge(Attrs, maps:filter(fun(K,_V) -> lists:member(K, OptedInAttrs) end, OptInAttrs)), + otel_span:set_attributes(Ctx, FinalAttrs), + otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, <<"">>)); - StatusCode when StatusCode >= 400 -> - otel_span:set_attribute(Ctx, 'http.status_code', StatusCode); - StatusCode when StatusCode < 400 -> - otel_span:set_attribute(Ctx, 'http.status_code', StatusCode) + _ -> + Attrs = set_resp_header_attrs(#{ + ?HTTP_RESPONSE_STATUS_CODE => StatusCode + }, RespHeaders, Config), + FinalAttrs = + maps:merge(Attrs, maps:filter(fun(K,_V) -> lists:member(K, OptedInAttrs) end, OptInAttrs)), + otel_span:set_attributes(Ctx, FinalAttrs), + ok end, otel_telemetry:end_telemetry_span(?TRACER_ID, Meta), otel_ctx:clear(); -handle_event([cowboy, request, exception], Measurements, Meta, _Config) -> +handle_event([cowboy, request, exception], Measurements, Meta, Config) -> Ctx = otel_telemetry:set_current_telemetry_span(?TRACER_ID, Meta), + #{ + opt_in_attrs := OptedInAttrs + } = Config, + #{ + req_body_length := ReqBodyLength, + resp_body_length := RespBodyLength + } = Measurements, #{ kind := Kind, reason := Reason, stacktrace := Stacktrace, + resp_headers := RespHeaders, resp_status := Status } = Meta, - otel_span:record_exception(Ctx, Kind, Reason, Stacktrace, []), - otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, <<"">>)), + + % these are opt-in + OptInAttrs = #{ + ?HTTP_REQUEST_BODY_SIZE => ReqBodyLength, + ?HTTP_RESPONSE_BODY_SIZE => RespBodyLength + }, StatusCode = transform_status_to_code(Status), - otel_span:set_attributes(Ctx, #{ - 'http.status_code' => StatusCode, - 'http.request_content_length' => maps:get(req_body_length, Measurements), - 'http.response_content_length' => maps:get(resp_body_length, Measurements) - }), + ErrorType = + case Reason of + R when is_atom(R) -> + otel_span:record_exception(Ctx, Kind, Reason, Stacktrace, []), + R; + {{#{message := ElixirExceptionMessage, '__struct__' := ElixirException, '__exception__' := true}, ElixirStacktrace}, _} -> + otel_span:record_exception(Ctx, Kind, ElixirException, ElixirExceptionMessage, ElixirStacktrace, []), + ElixirException; + _ -> + Reason + end, + Attrs = set_resp_header_attrs(#{ + ?HTTP_RESPONSE_STATUS_CODE => StatusCode, + ?ERROR_TYPE => ErrorType + }, RespHeaders, Config), + FinalAttrs = + maps:merge(Attrs, maps:filter(fun(K,_V) -> lists:member(K, OptedInAttrs) end, OptInAttrs)), + + otel_span:set_attributes(Ctx, FinalAttrs), + + otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, <<"">>)), + otel_telemetry:end_telemetry_span(?TRACER_ID, Meta), otel_ctx:clear(); -handle_event([cowboy, request, early_error], Measurements, Meta, _Config) -> +handle_event([cowboy, request, early_error], Measurements, Meta, Config) -> + #{ + opt_in_attrs := OptedInAttrs + } = Config, + #{ + resp_body_length := RespBodyLength + } = Measurements, #{ reason := {ErrorType, Error, Reason}, + resp_headers := RespHeaders, resp_status := Status } = Meta, + + OptInAttrs = #{?HTTP_RESPONSE_BODY_SIZE => RespBodyLength}, + StatusCode = transform_status_to_code(Status), - Attributes = #{ - 'http.status_code' => StatusCode, - 'http.response_content_length' => maps:get(resp_body_length, Measurements) - }, - Opts = #{attributes => Attributes, kind => ?SPAN_KIND_SERVER}, - Ctx = otel_telemetry:start_telemetry_span(?TRACER_ID, <<"HTTP Error">>, Meta, Opts), + + Attrs = set_resp_header_attrs(#{ + ?HTTP_RESPONSE_STATUS_CODE => StatusCode, + ?ERROR_TYPE => integer_to_binary(StatusCode) + }, RespHeaders, Config), + FinalAttrs = + maps:merge(Attrs, maps:filter(fun(K,_V) -> lists:member(K, OptedInAttrs) end, OptInAttrs)), + + Ctx = otel_telemetry:start_telemetry_span(?TRACER_ID, 'HTTP', Meta, #{attributes => FinalAttrs, kind => ?SPAN_KIND_SERVER}), otel_span:add_events(Ctx, [opentelemetry:event(ErrorType, #{error => Error, reason => Reason})]), otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, Reason)), otel_telemetry:end_telemetry_span(?TRACER_ID, Meta), @@ -122,20 +462,32 @@ transform_status_to_code(Status) when is_binary(Status) -> transform_status_to_code(Status) -> Status. -http_flavor(Req) -> - case maps:get(version, Req, undefined) of - 'HTTP/1.0' -> '1.0'; - 'HTTP/1.1' -> '1.1'; - 'HTTP/2' -> '2.0'; - 'SPDY' -> 'SPDY'; - 'QUIC' -> 'QUIC'; - _ -> <<"">> +set_query_string_attr(Attrs, <<"">>) -> Attrs; +set_query_string_attr(Attrs, QueryString) -> + maps:put(?URL_QUERY, QueryString, Attrs). + +set_network_protocol_attrs(Attrs, ReqVersion) -> + case extract_network_protocol(ReqVersion) of + {error, _Reason} -> + Attrs; + + {http, Version} -> + maps:merge(Attrs, #{?NETWORK_PROTOCOL_VERSION => Version}); + + {Protocol, Version} -> + maps:merge(Attrs, #{ + ?NETWORK_PROTOCOL_NAME => Protocol, + ?NETWORK_PROTOCOL_VERSION => Version + }) end. -client_ip(Headers, RemoteIP) -> - case maps:get(<<"x-forwarded-for">>, Headers, undefined) of - undefined -> - iolist_to_binary(inet:ntoa(RemoteIP)); - Addresses -> - hd(binary:split(Addresses, <<",">>)) - end. +extract_network_protocol(Version) -> + case Version of + 'HTTP/1.0' -> {http, '1.0'}; + 'HTTP/1.1' -> {http, '1.1'}; + 'HTTP/2.0' -> {http, '2'}; + 'HTTP/2' -> {http, '2'}; + 'SPDY' -> {'SPDY', '2'}; + 'QUIC' -> {'QUIC', '3'}; + _ -> {error, <<"Invalid protocol">>} + end. diff --git a/instrumentation/opentelemetry_cowboy/test/opentelemetry_cowboy_SUITE.erl b/instrumentation/opentelemetry_cowboy/test/opentelemetry_cowboy_SUITE.erl index a1b629a9..cda7cf64 100644 --- a/instrumentation/opentelemetry_cowboy/test/opentelemetry_cowboy_SUITE.erl +++ b/instrumentation/opentelemetry_cowboy/test/opentelemetry_cowboy_SUITE.erl @@ -9,17 +9,30 @@ -include_lib("opentelemetry/include/otel_span.hrl"). -include_lib("opentelemetry_api/include/otel_tracer.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/client_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/error_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/network_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/server_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/url_attributes.hrl"). +-include_lib("opentelemetry_semantic_conventions/include/attributes/user_agent_attributes.hrl"). + +-include_lib("opentelemetry_semantic_conventions/include/incubating/attributes/http_attributes.hrl"). + all() -> [ - successful_request, - chunked_request, - failed_request, - client_timeout_request, - idle_timeout_request, - chunk_timeout_request, - bad_request, - binary_status_code_request - ]. + successful_request_with_default_config, + public_endpoint_fun, + public_endpoint_true, + with_all_optins, + successful_request_with_custom_header_setting, + chunked_request, + failed_request, + client_timeout_request, + idle_timeout_request, + chunk_timeout_request, + bad_request, + binary_status_code_request + ]. init_per_suite(Config) -> ok = application:load(opentelemetry), @@ -53,104 +66,305 @@ init_per_testcase(_, Config) -> {ok, _} = application:ensure_all_started(opentelemetry), {ok, _} = application:ensure_all_started(opentelemetry_telemetry), {ok, _} = application:ensure_all_started(opentelemetry_cowboy), - opentelemetry_cowboy:setup(), otel_batch_processor:set_exporter(otel_exporter_pid, self()), Config. end_per_testcase(_, Config) -> + telemetry:detach({opentelemetry_cowboy, otel_cowboy}), Config. -successful_request(_Config) -> +successful_request_with_default_config(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62650, Headers = [ {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, {"tracestate", "congo=t61rcWkgMzE"}, + {"forwarded", "host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=https;by=203.0.113.43"}, {"x-forwarded-for", "203.0.133.195, 70.41.3.18, 150.172.238.178"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"}], {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = - httpc:request(get, {"http://localhost:8080/success", Headers}, [], []), + httpc:request(get, {"http://localhost:8080/success?a=b", Headers}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,attributes=Attributes,parent_span_id=ParentSpanId,kind=Kind}} -> - ?assertEqual(<<"HTTP GET">>, Name), + ?assertEqual('GET', Name), ?assertEqual(13235353014750950193, ParentSpanId), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"203.0.133.195">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"GET">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/success">>, - 'http.user_agent' => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.status_code' => 200, - 'http.request_content_length' => 0, - 'http.response_content_length' => 12}, + ?CLIENT_ADDRESS => <<"192.0.2.60">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"developer.mozilla.org">>, + ?SERVER_PORT => 4321, + ?URL_PATH => <<"/success">>, + ?URL_QUERY => <<"a=b">>, + ?URL_SCHEME => https, + ?USER_AGENT_ORIGINAL => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, + ?HTTP_RESPONSE_STATUS_CODE => 200}, + ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) + after + 1000 -> ct:fail(successful_request) + end. + +public_endpoint_true(_Config) -> + OptIns = #{ + public_endpoint => true + }, + opentelemetry_cowboy:setup(OptIns), + Port = 62658, + Headers = [ + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}], + {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = + httpc:request(get, {"http://localhost:8080/success?a=b", Headers}, [], [{socket_opts, [{port, Port}]}]), + receive + {span, #span{parent_span_id=ParentSpanId}} -> + ?assertEqual(undefined, ParentSpanId) + after + 1000 -> ct:fail(successful_request) + end. + +public_endpoint_fun(_Config) -> + OptIns = #{ + public_endpoint_fn => {?MODULE, public_endpoint_true_fn, []} + }, + opentelemetry_cowboy:setup(OptIns), + os:putenv("TENANT", "customer"), + Port = 62659, + Headers = [ + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}], + {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = + httpc:request(get, {"http://localhost:8080/success?a=b", Headers}, [], [{socket_opts, [{port, Port}]}]), + receive + {span, #span{parent_span_id=ParentSpanId}} -> + ?assertEqual(undefined, ParentSpanId) + after + 1000 -> ct:fail(successful_request) + end, + os:unsetenv("TENANT"). + +public_endpoint_true_fn(_Req, _Opts) -> + os:getenv("TENANT") /= "internal". + +with_all_optins(_Config) -> + Opts = #{ + opt_in_attrs => [ + ?CLIENT_PORT, + ?HTTP_REQUEST_BODY_SIZE, + ?HTTP_RESPONSE_BODY_SIZE, + ?NETWORK_LOCAL_ADDRESS, + ?NETWORK_LOCAL_PORT, + ?NETWORK_TRANSPORT + ], + request_headers => [<<"test-header">>], + response_headers => [<<"content-type">>] + }, + opentelemetry_cowboy:setup(Opts), + Port = 62660, + Headers = [ + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"test-header", "request header"}, + {"forwarded", "host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=https;by=203.0.113.43"}, + {"x-forwarded-for", "203.0.133.195, 70.41.3.18, 150.172.238.178"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"}], + {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = + httpc:request(get, {"http://localhost:8080/success?a=b", Headers}, [], [{socket_opts, [{port, Port}]}]), + receive + {span, #span{name=Name,attributes=Attributes,parent_span_id=ParentSpanId,kind=Kind}} -> + ?assertEqual('GET', Name), + ?assertEqual(13235353014750950193, ParentSpanId), + ?assertEqual(?SPAN_KIND_SERVER, Kind), + ExpectedAttrs = #{ + ?CLIENT_ADDRESS => <<"192.0.2.60">>, + ?CLIENT_PORT => undefined, + ?HTTP_REQUEST_BODY_SIZE => 0, + ?HTTP_REQUEST_METHOD => 'GET', + ?HTTP_RESPONSE_BODY_SIZE => 12, + ?NETWORK_LOCAL_ADDRESS => <<"localhost">>, + ?NETWORK_LOCAL_PORT => 8080, + ?NETWORK_TRANSPORT => tcp, + 'http.request.header.test-header' => [<<"request header">>], + 'http.response.header.content-type' => [<<"text/plain">>], + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"developer.mozilla.org">>, + ?SERVER_PORT => 4321, + ?URL_PATH => <<"/success">>, + ?URL_QUERY => <<"a=b">>, + ?URL_SCHEME => https, + ?USER_AGENT_ORIGINAL => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, + ?HTTP_RESPONSE_STATUS_CODE => 200}, + ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) + after + 1000 -> ct:fail(successful_request) + end. + +custom_client_header_sort(H1, H2) -> + H1Priority = custom_client_header_priority(H1), + H2Priority = custom_client_header_priority(H2), + + case {H1Priority, H2Priority} of + {H1P, H2P} when H1P =< H2P -> + true; + + {H1P, H2P} when H1P > H2P -> + false + end. + +custom_client_header_priority({HeaderName, _Value}) -> + case HeaderName of + <<"custom-client">> -> 1; + <<"x-forwarded-for">> -> 2 + end. + +custom_server_header_sort(H1, H2) -> + H1Priority = custom_server_header_priority(H1), + H2Priority = custom_server_header_priority(H2), + + case {H1Priority, H2Priority} of + {H1P, H2P} when H1P =< H2P -> + true; + + {H1P, H2P} when H1P > H2P -> + false + end. + +custom_server_header_priority({HeaderName, _Value}) -> + case HeaderName of + <<"custom-host">> -> 1; + <<"x-forwarded-host">> -> 2; + <<"forwarded">> -> 3; + _ -> 4 + end. + +custom_scheme_header_sort(H1, H2) -> + H1Priority = custom_scheme_header_priority(H1), + H2Priority = custom_scheme_header_priority(H2), + + case {H1Priority, H2Priority} of + {H1P, H2P} when H1P =< H2P -> + true; + + {H1P, H2P} when H1P > H2P -> + false + end. + +custom_scheme_header_priority({HeaderName, _Value}) -> + case HeaderName of + <<"custom-scheme">> -> 1; + <<"x-forwarded-proto">> -> 2 + end. + +successful_request_with_custom_header_setting(_Config) -> + Opts = #{ + client_address_headers => [<<"x-forwarded-for">>, <<"custom-client">>], + client_headers_sort_fn => fun ?MODULE:custom_client_header_sort/2, + scheme_headers => [<<"custom-scheme">>, <<"x-forwarded-proto">>], + scheme_headers_sort_fn => fun ?MODULE:custom_scheme_header_sort/2, + server_address_headers => [<<"custom-host">>, <<"forwarded">>, <<"host">>], + server_headers_sort_fn => fun ?MODULE:custom_server_header_sort/2 + }, + opentelemetry_cowboy:setup(Opts), + Port = 62661, + Headers = [ + {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, + {"tracestate", "congo=t61rcWkgMzE"}, + {"x-forwarded-proto", "http"}, + {"custom-scheme", "https"}, + {"custom-client", "23.23.23.23"}, + {"forwarded", "host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=https;by=203.0.113.43"}, + {"x-forwarded-for", "203.0.133.195, 70.41.3.18, 150.172.238.178"}, + {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"}], + {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = + httpc:request(get, {"http://localhost:8080/success", Headers}, [], [{socket_opts, [{port, Port}]}]), + receive + {span, #span{name=Name,attributes=Attributes,parent_span_id=ParentSpanId,kind=Kind}} -> + ?assertEqual('GET', Name), + ?assertEqual(13235353014750950193, ParentSpanId), + ?assertEqual(?SPAN_KIND_SERVER, Kind), + ExpectedAttrs = #{ + ?CLIENT_ADDRESS => <<"23.23.23.23">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"developer.mozilla.org">>, + ?SERVER_PORT => 4321, + ?URL_PATH => <<"/success">>, + ?URL_SCHEME => https, + ?USER_AGENT_ORIGINAL => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, + ?HTTP_RESPONSE_STATUS_CODE => 200}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(successful_request) end. chunked_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62651, {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = - httpc:request(get, {"http://localhost:8080/chunked", []}, [], []), + httpc:request(get, {"http://localhost:8080/chunked", []}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> - ?assertEqual(<<"HTTP GET">>, Name), + ?assertEqual('GET', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"127.0.0.1">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"GET">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/chunked">>, - 'http.user_agent' => <<>>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.status_code' => 200, - 'http.request_content_length' => 0, - 'http.response_content_length' => 14}, + ?CLIENT_ADDRESS => <<"127.0.0.1">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/chunked">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<>>, + ?HTTP_RESPONSE_STATUS_CODE => 200}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(chunked_request) end. failed_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62652, {ok, {{_Version, 500, _ReasonPhrase}, _Headers, _Body}} = - httpc:request(get, {"http://localhost:8080/failure", []}, [], []), + httpc:request(get, {"http://localhost:8080/failure", []}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,events=Events,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> [Event] = otel_events:list(Events), #event{name=exception} = Event, - ?assertEqual(<<"HTTP GET">>, Name), + ?assertEqual('GET', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"127.0.0.1">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"GET">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/failure">>, - 'http.user_agent' => <<>>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.status_code' => 500, - 'http.request_content_length' => 0, - 'http.response_content_length' => 0}, + ?CLIENT_ADDRESS => <<"127.0.0.1">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/failure">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<>>, + ?HTTP_RESPONSE_STATUS_CODE => 500, + ?ERROR_TYPE => failure}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(failed_request) end. client_timeout_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62653, {error, timeout} = - httpc:request(get, {"http://localhost:8080/slow", []}, [{timeout, 50}], []), + httpc:request(get, {"http://localhost:8080/slow", []}, [{timeout, 50}], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,events=Events,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> [Event] = otel_events:list(Events), @@ -160,29 +374,29 @@ client_timeout_request(_Config) -> reason => 'The socket has been closed.' }, ?assertMatch(ExpectedEventAttrs, otel_attributes:map(EventAttributes)), - ?assertEqual(<<"HTTP GET">>, Name), + ?assertEqual('GET', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"127.0.0.1">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"GET">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/slow">>, - 'http.user_agent' => <<>>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.request_content_length' => 0, - 'http.response_content_length' => 0}, + ?CLIENT_ADDRESS => <<"127.0.0.1">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/slow">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<>>}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(client_timeout_request) end. idle_timeout_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62654, {error, socket_closed_remotely} = - httpc:request(head, {"http://localhost:8080/slow", []}, [], []), + httpc:request(head, {"http://localhost:8080/slow", []}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,events=Events,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> [Event] = otel_events:list(Events), @@ -192,58 +406,58 @@ idle_timeout_request(_Config) -> reason => 'Connection idle longer than configuration allows.' }, ?assertMatch(ExpectedEventAttrs, otel_attributes:map(EventAttributes)), - ?assertEqual(<<"HTTP HEAD">>, Name), + ?assertEqual('HEAD', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"127.0.0.1">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"HEAD">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/slow">>, - 'http.user_agent' => <<>>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.request_content_length' => 0, - 'http.response_content_length' => 0}, + ?CLIENT_ADDRESS => <<"127.0.0.1">>, + ?HTTP_REQUEST_METHOD => 'HEAD', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/slow">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<>>}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(idle_timeout_request) end. chunk_timeout_request(_Config) -> - httpc:request(head, {"http://localhost:8080/chunked_slow", []}, [], []), + opentelemetry_cowboy:setup(), + Port = 62655, + httpc:request(head, {"http://localhost:8080/chunked_slow", []}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> - ?assertEqual(<<"HTTP HEAD">>, Name), + ?assertEqual('HEAD', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ExpectedAttrs = #{ - 'http.client_ip' => <<"127.0.0.1">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"HEAD">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/chunked_slow">>, - 'http.user_agent' => <<>>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.status_code' => 200, - 'http.request_content_length' => 0, - 'http.response_content_length' => 0}, + ?CLIENT_ADDRESS => <<"127.0.0.1">>, + ?HTTP_REQUEST_METHOD => 'HEAD', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/chunked_slow">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<>>, + ?HTTP_RESPONSE_STATUS_CODE => 200}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(chunk_timeout_request) end. bad_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62656, Headers = [ {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, {"tracestate", "congo=t61rcWkgMzE"}, {"x-forwarded-for", "203.0.133.195, 70.41.3.18, 150.172.238.178"}], {ok, {{_Version, 501, _ReasonPhrase}, _Headers, _Body}} = - httpc:request(trace, {"http://localhost:8080/", Headers}, [], []), + httpc:request(trace, {"http://localhost:8080/", Headers}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,events=Events,attributes=Attributes,parent_span_id=undefined,kind=Kind}} -> [Event] = otel_events:list(Events), @@ -253,43 +467,41 @@ bad_request(_Config) -> reason => 'The TRACE method is currently not implemented. (RFC7231 4.3.8)' }, ?assertMatch(ExpectedEventAttrs, otel_attributes:map(EventAttributes)), - ?assertEqual(<<"HTTP Error">>, Name), + ?assertEqual('HTTP', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), - ExpectedAttrs = #{ - 'http.status_code' => 501, - 'http.response_content_length' => 0}, + ExpectedAttrs = #{?ERROR_TYPE => <<"501">>, ?HTTP_RESPONSE_STATUS_CODE => 501}, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after 1000 -> ct:fail(bad_request) end. binary_status_code_request(_Config) -> + opentelemetry_cowboy:setup(), + Port = 62657, Headers = [ {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, {"tracestate", "congo=t61rcWkgMzE"}, {"x-forwarded-for", "203.0.133.195, 70.41.3.18, 150.172.238.178"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"}], {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = - httpc:request(get, {"http://localhost:8080/binary_status_code", Headers}, [], []), + httpc:request(get, {"http://localhost:8080/binary_status_code", Headers}, [], [{socket_opts, [{port, Port}]}]), receive {span, #span{name=Name,attributes=Attributes,parent_span_id=ParentSpanId,kind=Kind}} -> - ?assertEqual(<<"HTTP GET">>, Name), + ?assertEqual('GET', Name), ?assertEqual(?SPAN_KIND_SERVER, Kind), ?assertEqual(13235353014750950193, ParentSpanId), ExpectedAttrs = #{ - 'http.client_ip' => <<"203.0.133.195">>, - 'http.flavor' => '1.1', - 'http.host' => <<"localhost">>, - 'http.host.port' => 8080, - 'http.method' => <<"GET">>, - 'http.scheme' => <<"http">>, - 'http.target' => <<"/binary_status_code">>, - 'http.user_agent' => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, - 'net.host.ip' => <<"127.0.0.1">>, - 'net.transport' => 'IP.TCP', - 'http.status_code' => 200, - 'http.request_content_length' => 0, - 'http.response_content_length' => 12 + ?CLIENT_ADDRESS => <<"203.0.133.195">>, + ?HTTP_REQUEST_METHOD => 'GET', + ?NETWORK_PEER_ADDRESS => <<"127.0.0.1">>, + ?NETWORK_PEER_PORT => Port, + ?NETWORK_PROTOCOL_VERSION => '1.1', + ?SERVER_ADDRESS => <<"localhost">>, + ?SERVER_PORT => 8080, + ?URL_PATH => <<"/binary_status_code">>, + ?URL_SCHEME => http, + ?USER_AGENT_ORIGINAL => <<"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0">>, + ?HTTP_RESPONSE_STATUS_CODE => 200 }, ?assertMatch(ExpectedAttrs, otel_attributes:map(Attributes)) after diff --git a/instrumentation/opentelemetry_cowboy/test/test_h.erl b/instrumentation/opentelemetry_cowboy/test/test_h.erl index 7e7e8946..788d481f 100644 --- a/instrumentation/opentelemetry_cowboy/test/test_h.erl +++ b/instrumentation/opentelemetry_cowboy/test/test_h.erl @@ -6,12 +6,18 @@ init(_, failure) -> error(failure); init(Req, success = Opts) -> - {ok, cowboy_req:reply(200, #{}, <<"Hello world!">>, Req), Opts}; + {ok, cowboy_req:reply(200, #{ + <<"content-type">> => <<"text/plain">> + }, <<"Hello world!">>, Req), Opts}; init(Req, binary_status_code = Opts) -> - {ok, cowboy_req:reply(<<"200 OK">>, #{}, <<"Hello world!">>, Req), Opts}; + {ok, cowboy_req:reply(<<"200 OK">>, #{ + <<"content-type">> => <<"text/plain">> + }, <<"Hello world!">>, Req), Opts}; init(Req, slow = Opts) -> timer:sleep(200), - {ok, cowboy_req:reply(200, #{}, <<"I'm slow">>, Req), Opts}; + {ok, cowboy_req:reply(200, #{ + <<"content-type">> => <<"text/plain">> + }, <<"I'm slow">>, Req), Opts}; init(Req0, chunked = Opts) -> Req = cowboy_req:stream_reply(200, Req0), cowboy_req:stream_body("Hello\r\n", nofin, Req), diff --git a/instrumentation/opentelemetry_dataloader/CHANGELOG.md b/instrumentation/opentelemetry_dataloader/CHANGELOG.md new file mode 100644 index 00000000..61fd4343 --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.0 + +### Features + +- Initial release diff --git a/instrumentation/opentelemetry_dataloader/LICENSE b/instrumentation/opentelemetry_dataloader/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/instrumentation/opentelemetry_dataloader/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_dataloader/README.md b/instrumentation/opentelemetry_dataloader/README.md index 5e79bac8..99660814 100644 --- a/instrumentation/opentelemetry_dataloader/README.md +++ b/instrumentation/opentelemetry_dataloader/README.md @@ -17,7 +17,7 @@ by adding `opentelemetry_dataloader` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:opentelemetry_dataloader, "~> 1.0.0"} + {:opentelemetry_dataloader, "~> 0.1"} ] end ``` diff --git a/instrumentation/opentelemetry_dataloader/docker-compose.yml b/instrumentation/opentelemetry_dataloader/docker-compose.yml index dbd11bc2..d01ac3b3 100644 --- a/instrumentation/opentelemetry_dataloader/docker-compose.yml +++ b/instrumentation/opentelemetry_dataloader/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.7" services: postgres: - image: postgres:16.3 + image: postgres:17.5 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/instrumentation/opentelemetry_dataloader/mix.exs b/instrumentation/opentelemetry_dataloader/mix.exs index fda19ecf..8264cd45 100644 --- a/instrumentation/opentelemetry_dataloader/mix.exs +++ b/instrumentation/opentelemetry_dataloader/mix.exs @@ -1,14 +1,14 @@ defmodule OpentelemetryDataloader.MixProject do use Mix.Project - @version "1.0.0" + @version "0.1.0" def project do [ app: :opentelemetry_dataloader, description: "Trace Dataloader with OpenTelemetry.", version: @version, - elixir: "~> 1.11", + elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), aliases: aliases(), @@ -55,16 +55,16 @@ defmodule OpentelemetryDataloader.MixProject do defp deps do [ {:telemetry, "~> 1.0"}, - {:opentelemetry_api, "~> 1.2"}, - {:opentelemetry_telemetry, "~> 1.0"}, + {:opentelemetry_api, "~> 1.3"}, + {:opentelemetry_telemetry, "~> 1.1"}, {:dataloader, "~> 2.0", only: [:dev, :test]}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:ex_doc, "~> 0.34", only: [:dev], runtime: false}, + {:opentelemetry_exporter, "~> 1.7", only: [:dev, :test]}, + {:opentelemetry, "~> 1.4", only: [:dev, :test]}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, {:ecto_sql, ">= 3.0.0", only: [:dev, :test]}, - {:postgrex, ">= 0.15.0", only: [:dev, :test]}, - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:opentelemetry_process_propagator, "~> 0.2.1"} + {:postgrex, ">= 0.19.0", only: [:dev, :test]}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, + {:opentelemetry_process_propagator, "~> 0.3"} ] end end diff --git a/instrumentation/opentelemetry_dataloader/mix.lock b/instrumentation/opentelemetry_dataloader/mix.lock index a281762b..7be08d7c 100644 --- a/instrumentation/opentelemetry_dataloader/mix.lock +++ b/instrumentation/opentelemetry_dataloader/mix.lock @@ -2,30 +2,29 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, - "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, + "dataloader": {:hex, :dataloader, "2.0.2", "c45075e0692e68638a315e14f747bd8d7065fb5f38705cf980f62d4cd344401f", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c6cabc0b55e96e7de74d14bf37f4a5786f0ab69aa06764a1f39dda40079b098"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.4.0", "f928923ed80adb5eb7894bac22e9a198478e6a8f04020ae1d6f289fdcad0b498", [:rebar3], [{:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "50b32ce127413e5d87b092b4d210a3449ea80cd8224090fe68d73d576a3faa15"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.3.0", "03e2177f28dd8d11aaa88e8522c81c2f6a788170fe52f7a65262340961e663f9", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "b9e5ff775fd064fa098dba3c398490b77649a352b40b0b730a6b7dc0bdd68858"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.7.0", "dec4e90c0667cf11a3642f7fe71982dbc0c6bfbb8725a0b13766830718cf0d98", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.4.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "d0f25f6439ec43f2561537c3fabbe177b38547cddaa3a692cbb8f4770dbefc1e"}, - "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.2.2", "85244a49f0c32ae1e2f3d58c477c265bd6125ee3480ade82b0fa9324b85ed3f0", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "04db13302a34bea8350a13ed9d49c22dfd32c4bc590d8aa88b6b4b7e4f346c61"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"}, - "postgrex": {:hex, :postgrex, "0.18.0", "f34664101eaca11ff24481ed4c378492fed2ff416cd9b06c399e90f321867d7e", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a042989ba1bc1cca7383ebb9e461398e3f89f868c92ce6671feb7ef132a252d1"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.22.1", "0f450cc1568a67a65ce5e15df53c53f9a098c3da081c5f126199a72505858dc1", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3092be0babdc0e14c2e900542351e066c0fa5a9cf4b3597559ad1e67f07938c0"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.24.0", "d00e2887551ff8cdae4d0340d90d9fcbc4943c7b5f49d32ed4bc23aff4db9a44", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "90b25a58ee433d91c17f036d4d354bf8859a089bfda60e68a86f8eecae45ef1b"}, } diff --git a/instrumentation/opentelemetry_ecto/.gitignore b/instrumentation/opentelemetry_ecto/.gitignore index db33682d..fd594430 100644 --- a/instrumentation/opentelemetry_ecto/.gitignore +++ b/instrumentation/opentelemetry_ecto/.gitignore @@ -9,3 +9,7 @@ erl_crash.dump /config/*.secret.exs .elixir_ls/ .rebar3 + +plts +opentelemetry_ecto_test +opentelemetry_ecto_test.db* diff --git a/instrumentation/opentelemetry_ecto/CHANGELOG.md b/instrumentation/opentelemetry_ecto/CHANGELOG.md index ef8efce2..061ab48e 100644 --- a/instrumentation/opentelemetry_ecto/CHANGELOG.md +++ b/instrumentation/opentelemetry_ecto/CHANGELOG.md @@ -1,61 +1,77 @@ # Changelog +## 2.0.0-beta.1 + +## Features + +- Semantic Conventions v1.27 support +- Support added for MySQL, MSSQL, and Sqlite +- Per-query options now available +- Connection settings now taken from init events for source of truth + +### Breaking Changes + +- Time measurement attributes are now namespaced to `ecto.*` +- Span names conform to SemConv 1.27 specification +- Several API changes. See the docs for more details + +Special thank you to Dan Shultzer for his contributions. + ## 1.2.0 ### Breaking Changes -* `db.statement` attribute is now marked as optional. Add `db_statement: enabled` when calling `setup` +- `db.statement` attribute is now marked as optional. Add `db_statement: enabled` when calling `setup` ### Fixes -* Don't record DB statements without sanitizaiton +- Don't record DB statements without sanitizaiton ### Changed -* Add support for Elixir 1.15 and OTP 26 -* Add required `db.system` attribute +- Add support for Elixir 1.15 and OTP 26 +- Add required `db.system` attribute ## 1.1.1 ### Changed -* Add db.name to ecto spans +- Add db.name to ecto spans ## 1.1.0 ### Changed -* Allow setting additional attributes +- Allow setting additional attributes ### Fixes -* Fix span linking in additional task-spawned use cases +- Fix span linking in additional task-spawned use cases ## 1.0.0 ### Changed -* Add idle time as an attribute +- Add idle time as an attribute ### Fixes -* Fix Ecto preload spans not being linked to the root parent query +- Fix Ecto preload spans not being linked to the root parent query ## 1.0.0-rc.5 ### Changed -* Opentelemetry 1.0 support +- Opentelemetry 1.0 support ## 1.0.0-rc.4 ### Changed -* Opentelemetry 1.0.0-rc.4 support +- Opentelemetry 1.0.0-rc.4 support ## 1.0.0-rc.2 ### Changed -* Update dependencies to allow telemetry 1.0.0 - +- Update dependencies to allow telemetry 1.0.0 diff --git a/instrumentation/opentelemetry_ecto/config/test.exs b/instrumentation/opentelemetry_ecto/config/test.exs index 7a781d01..6aa89b6b 100644 --- a/instrumentation/opentelemetry_ecto/config/test.exs +++ b/instrumentation/opentelemetry_ecto/config/test.exs @@ -1,14 +1,56 @@ import Config -config :opentelemetry_ecto, - ecto_repos: [OpentelemetryEcto.TestRepo] +postgres_repos = %{ + OpentelemetryEcto.TestRepo => %{port: 5432, hostname: "localhost"}, + OpentelemetryEcto.TestRepo.Replica1 => %{port: 5433, hostname: "127.0.0.1"} +} + +for {repo, %{hostname: hostname, port: port}} <- postgres_repos do + config :opentelemetry_ecto, repo, + username: "postgres", + password: "postgres", + database: "opentelemetry_ecto_test", + hostname: hostname, + port: port, + pool: Ecto.Adapters.SQL.Sandbox +end -config :opentelemetry_ecto, OpentelemetryEcto.TestRepo, +config :opentelemetry_ecto, OpentelemetryEcto.MyXQLTestRepo, + username: "root", + password: "mysql", + database: "opentelemetry_ecto_test", hostname: "localhost", - username: "postgres", - password: "postgres", + port: 3306, + pool: Ecto.Adapters.SQL.Sandbox, + priv: "priv/test_repo" + +config :opentelemetry_ecto, OpentelemetryEcto.TdsTestRepo, + username: "sa", + password: "MSSQLpass1!", database: "opentelemetry_ecto_test", - pool: Ecto.Adapters.SQL.Sandbox + hostname: "localhost", + port: 1433, + pool: Ecto.Adapters.SQL.Sandbox, + priv: "priv/test_repo" + +config :opentelemetry_ecto, OpentelemetryEcto.Sqlite3TestRepo, + # username: "sa", + # password: "MSSQLpass1!", + database: "opentelemetry_ecto_test.db", + + # hostname: "localhost", + # port: 1433, + pool: Ecto.Adapters.SQL.Sandbox, + priv: "priv/test_repo" + +config :opentelemetry_ecto, + ecto_repos: [ + OpentelemetryEcto.TestRepo, + OpentelemetryEcto.TestRepo.Replica1, + OpentelemetryEcto.MyXQLTestRepo, + OpentelemetryEcto.TdsTestRepo, + OpentelemetryEcto.Sqlite3TestRepo + ] config :opentelemetry, processors: [{:otel_batch_processor, %{scheduled_delay_ms: 1}}] diff --git a/instrumentation/opentelemetry_ecto/docker-compose.yml b/instrumentation/opentelemetry_ecto/docker-compose.yml index 5ac4e37a..b1590a27 100644 --- a/instrumentation/opentelemetry_ecto/docker-compose.yml +++ b/instrumentation/opentelemetry_ecto/docker-compose.yml @@ -2,10 +2,34 @@ version: "3.7" services: postgres: - image: postgres:16.3 + image: postgres:17.5 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - - POSTGRES_DB=opentelemetry_ecto_test ports: - 5432:5432 + + postgres-r1: + image: postgres:17.5 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + ports: + - 5433:5432 + + mysql: + image: mysql:9.3 + environment: + - MYSQL_USER=mysql + - MYSQL_PASSWORD=mysql + - MYSQL_ROOT_PASSWORD=mysql + ports: + - 3306:3306 + + mssql: + image: mcr.microsoft.com/azure-sql-edge + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=MSSQLpass1! + ports: + - 1433:1433 diff --git a/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex index 28f2321a..a40558de 100644 --- a/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex +++ b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex @@ -1,6 +1,152 @@ defmodule OpentelemetryEcto do + require OpenTelemetry.Tracer + + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.Incubating.DBAttributes + alias OpenTelemetry.SemConv.ServerAttributes + + alias OpentelemetryEcto.EctoAttributes + # how to handle db.query.parameter.? + # not parseable from current meta + opt_outs = [ + DBAttributes.db_query_text() + ] + + @options_schema NimbleOptions.new!( + event_prefix: [ + type: {:list, :atom}, + required: true, + type_spec: quote(do: :telemetry.event_name()), + doc: """ + Must be the prefix configured in the `Ecto.Repo` Telemetry configuration. + + By default, it's the camel_case name of the repository module. For `MyApp.Repo`, it would + be `[:my_app, :repo]`. + """ + ], + opt_out_attrs: [ + type: {:list, {:in, opt_outs}}, + default: [], + type_spec: quote(do: opt_out_attrs()), + doc: """ + List of attributes with a `Requirement Level` of `Recommended` to opt out of. + + By default, instrumentation libraries implement all `Recommended` attributes in addition + to `Required` attributes. + + Use semantic conventions library to ensure compatability, e.g. `[DBAttributes.db_query_text()]` + + Recommended Attributes: + #{Enum.map_join(opt_outs, "\n\n", &" * `#{inspect(&1)}`")} + """ + ], + telemetry_metadata_preprocessor: [ + type: {:fun, 1}, + default: &__MODULE__.default_metadata_preprocessor/1, + doc: """ + Preprocessor for the telemetry metadata used for instrumentation. + + This can be used to sanitize adapter-specific metadata such as + a query or error's text. + + No keys may be deleted from the map, only modified. Refer to the + [Ecto Repo Telemetry Information](`m:Ecto.Repo#module-adapter-specific-events`) page + for more information. + """ + ], + additional_span_attributes: [ + type: :map, + type_spec: quote(do: OpenTelemetry.attributes_map()), + doc: """ + Additional attributes to include on all spans. Instrumented + attributes take precedence over anything supplied here. + """, + default: %{} + ], + repo_metadata_storage: [ + type: {:in, [:ets, :persistent_term]}, + default: :persistent_term, + type_spec: quote(do: :ets | :persistent_term), + doc: """ + Storage mechanism for repo metadata used for attributes. + + This should only be set to `:ets` when very large numbers + of dynamic repositories are used. Additionally, this setting + may only be set once with subsequent settings being ignored. + """, + type_doc: ":atom" + ] + ) + @moduledoc """ - Telemetry handler for creating OpenTelemetry Spans from Ecto query events. + OpenTelemetry instrumentation for Ecto. + + ## Setup + + This should be called from your application's `c:Application.start/2` callback on startup, + before starting the application's top-level supervisor. + + ## Semantic Conventions + + All required and recommended DB Client Call Span semantic conventions are implemented. + Supported opt-in attributes can be configured using the `opt_in_attrs` option. + + > #### Note {: .info} + > + > DB attribute Semantic Conventions are still mostly experimental and subject + > to change. + + ### Opt-out Attributes + + By default, instrumentation libraries implement all `Recommended` attributes in addition + to `Required` attributes. + + See [SemConv DB Span Common Attributes](`e:opentelemetry_semantic_conventions:database-spans.md#common-attributes`) for attribute requirement levels. + + ### Query and Error Sanitization + + Ecto SQL queries are parameterized for all major adapters, requiring no further sanitization. + In some rare cases, you may still need further processing. + + Some exceptions emitted may contain sensitive information in the exception message and + are not aware of `redacted` field settings in Ecto schemas. In these cases, you may + wish to sanitize these error messages. + + For these use cases, `telemetry_metadata_preprocessor` may be utilized to preprocess these + fields. + + ### Per-Query Options + + Additional attributes can be provided and a span name override on a per-query basis. This can be + useful for providing more tailored information where the instrumentation cannot provide such. + For instance, when calling a stored procedure, the span name could be updated with the name + of the procedure. + + Note that attributes can only be additive. Existing attributes will not be overridden. In + several cases, semantic attributes cannot be reliably set. Where these are not set by + this instrumentation, you may still set those attribtutes. + + ``` + Repo.all(User, + telemetry_options: [ + otel: %{ + span_name: "custom span name", + attributes: %{ + "config.attribute": "special value overwritten", + "db.system": "my_system", + extra: "should add" + } + } + ] + ) + ``` + + ## Preloads + + > #### Note {: .neutral} + > + > Due to limitations with how Ecto emits its telemetry, nested preloads are not + > represented as nested spans within a trace. Any relation preloads, which are executed in parallel in separate tasks, will be linked to the span of the process that initiated the call. For example: @@ -9,137 +155,151 @@ defmodule OpentelemetryEcto do Repo.all(Query.from(User, preload: [:posts, :comments])) end - This will create a span called `"parent span:"` with three child spans for each + This will create a span called `"parent span"` with three child spans for each query: users, posts, and comments. - > #### Note {: .neutral} + ## Dynamic Repositories, Replicas, and Telemetry Prefixes + + To accurately report host connection information, OpentelemetryEcto uses information + contained in the `[:ecto, :repo, :init]` telemetry event since the + + > #### Warning {: .warning} > - > Due to limitations with how Ecto emits its telemetry, nested preloads are not - > represented as nested spans within a trace. + > Telemetry prefixes should not be reused between repos to ensure connection information + > is accurate. Subsequent `:init` events for a given prefix will override previously + > seen connection info. + """ - require OpenTelemetry.Tracer + @typedoc "Use semantic conventions library to ensure compatability, e.g. `DBAttributes.db_query_text()`" + @type opt_out_attr() :: + unquote(DBAttributes.db_query_text()) - @typedoc """ - Option that you can pass to `setup/2`. - """ - @typedoc since: "1.3.0" - @type setup_option() :: - {:time_unit, System.time_unit()} - | {:span_prefix, String.t()} - | {:additional_attributes, %{String.t() => term()}} - | {:db_statement, :enabled | :disabled | (String.t() -> String.t())} + @type opt_out_attrs() :: [opt_out_attr()] + + @type options() :: [unquote(NimbleOptions.option_typespec(@options_schema))] @doc """ - Attaches the `OpentelemetryEcto` handler to your repo events. + Initializes and configures the telemetry handlers. - This should be called from your application's `c:Application.start/2` callback on startup, - before starting the application's top-level supervisor. + Supported options:\n#{NimbleOptions.docs(@options_schema)} + """ + @spec setup(options()) :: :ok | {:error, :already_exists} + def setup(opts) do + config = + opts + |> NimbleOptions.validate!(@options_schema) + |> Enum.into(%{}) - `event_prefix` must be the prefix configured in the `Ecto.Repo` Telemetry configuration. - By default, it's the camel_case name of the repository module. For `MyApp.Repo`, it would - be `[:my_app, :repo]`. + event = config.event_prefix ++ [:query] - For example: + attach_init_handler(config) - @impl Application - def start(_type, _args) do - OpentelemetryEcto.setup([:blog, :repo]) + attach_query_handler(event, config) + end - children = [...] - Supervisor.start_link(children, strategy: :one_for_one) - end + defp attach_init_handler(config) do + :telemetry.attach( + {__MODULE__, :init, config.event_prefix}, + [:ecto, :repo, :init], + &__MODULE__.handle_init/4, + config + ) + end - ## Options - - You may also supply the following options in the second argument: - - * `:time_unit` - a time unit used to convert the values of query phase - timings, defaults to `:microsecond`. See `System.convert_time_unit/3`. - * `:span_prefix` - the first part of the span name. - Defaults to the concatenation of the event name with periods, such as - `"blog.repo.query"`. This will always be followed with a colon and the - source (the table name for SQL adapters). For example: `"blog.repo.query:users"`. - * `:additional_attributes` - additional attributes to include in the span. If there - are conflicts with default provided attributes, the ones provided with - this config will have precedence. - * `:db_statement` - `:disabled` (default), `:enabled`, or a function. - Whether or not to include DB statements in the **span attributes** (as the - `db.statement` attribute). - Optionally provide a function that takes a query string and returns a - sanitized version of it. This is useful for removing sensitive information from the - query string. Unless this option is `:enabled` or a function, - query statements will not be recorded on spans. + defp attach_query_handler(event, config) do + result = :telemetry.attach({__MODULE__, event}, event, &__MODULE__.handle_event/4, config) - """ - @spec setup(:telemetry.event_name(), [setup_option()]) :: :ok | {:error, :already_exists} - def setup(event_prefix, options \\ []) when is_list(options) do - event = event_prefix ++ [:query] - :telemetry.attach({__MODULE__, event}, event, &__MODULE__.handle_event/4, options) + case result do + :ok -> + :ok + + {:error, :already_exists} -> + :error + end end - @doc false - def handle_event( - event, - measurements, - %{query: query, source: source, result: query_result, repo: repo, type: type}, - config - ) do - # Doing all this even if the span isn't sampled so the sampler - # could technically use the attributes to decide if it should sample or not + defp get_meta_table do + case :ets.whereis(:otel_ecto_repo_meta) do + :undefined -> + :ets.new(:otel_ecto_repo_meta, [:public, :named_table, :set, {:read_concurrency, true}]) + |> :ets.whereis() - total_time = measurements.total_time - end_time = :opentelemetry.timestamp() - start_time = end_time - total_time - database = repo.config()[:database] + tid -> + tid + end + end - url = - case repo.config()[:url] do - nil -> - # TODO: add port - URI.to_string(%URI{scheme: "ecto", host: repo.config()[:hostname]}) + defp get_repo_metadata(key, :persistent_term) do + config = :persistent_term.get(:otel_ecto, %{repo_meta: %{}}) + Map.get(config.repo_meta, key, %{}) + end - url -> - url - end + defp get_repo_metadata(key, :ets) do + case :ets.lookup(:otel_ecto_repo_meta, key) do + [] -> %{} + [meta] -> meta + end + end - span_prefix = - case Keyword.fetch(config, :span_prefix) do - {:ok, prefix} -> prefix - :error -> Enum.join(event, ".") - end + defp query_opts(%{options: options}) do + case Keyword.get(options, :otel) do + nil -> %{} + opts when is_map(opts) -> opts + _ -> %{} + end + end - span_suffix = if source != nil, do: ":#{source}", else: "" - span_name = span_prefix <> span_suffix + defp query_opts(_), do: %{} - time_unit = Keyword.get(config, :time_unit, :microsecond) - additional_attributes = Keyword.get(config, :additional_attributes, %{}) + @doc false + def handle_init([:ecto, :repo, :init], %{}, meta, config) do + key = {meta.repo, meta.opts[:telemetry_prefix] || config.event_prefix} - db_type = - case type do - :ecto_sql_query -> :sql - _ -> type - end + if config.repo_metadata_storage == :persistent_term do + term = :persistent_term.get(:otel_ecto, %{repo_meta: %{}}) + updated_term = %{repo_meta: Map.put(term.repo_meta, key, Map.new(meta.opts))} + :persistent_term.put(:otel_ecto, updated_term) + else + tid = get_meta_table() + + :ets.insert(tid, {key, Map.new(meta.opts)}) + end + end - # TODO: need connection information to complete the required attributes - # net.peer.name or net.peer.ip and net.peer.port - base_attributes = %{ - "db.type": db_type, - source: source, - "db.instance": database, - "db.name": database, - "db.url": url, - "total_time_#{time_unit}s": System.convert_time_unit(total_time, :native, time_unit) - } + @doc false + def handle_event(_event, measurements, meta, config) do + %{query: query, source: source, result: query_result, repo: repo, type: type} = + config.telemetry_metadata_preprocessor.(meta) - db_statement_config = Keyword.get(config, :db_statement, :disabled) + per_query_opts = query_opts(meta) + + # Doing all this even if the span isn't sampled so the sampler + # could technically use the attributes to decide if it should sample or not + + total_time = measurements.total_time + end_time = :opentelemetry.timestamp() + start_time = end_time - total_time + + repo_config = + get_repo_metadata({repo, config.event_prefix}, config.repo_metadata_storage) attributes = - base_attributes - |> add_measurements(measurements, time_unit) - |> maybe_add_db_statement(db_statement_config, query) - |> maybe_add_db_system(repo.__adapter__()) - |> add_additional_attributes(additional_attributes) + %{ + unquote(DBAttributes.db_system()) => db_system(repo.__adapter__()), + unquote(DBAttributes.db_namespace()) => repo_config.database, + unquote(DBAttributes.db_query_text()) => query + } + |> set_db_query_text(query, type) + |> set_db_collection_name(source) + |> set_server_address(repo, repo_config) + |> maybe_add_server_port(repo.__adapter__(), repo_config) + |> set_db_operation_name(query, type) + |> maybe_add_error_type(repo.__adapter__(), query_result) + |> add_measurements(measurements) + |> set_additional_attributes(config, per_query_opts) + + span_name = span_name(attributes, per_query_opts) parent_context = case OpentelemetryProcessPropagator.fetch_ctx(self()) do @@ -179,65 +339,188 @@ defmodule OpentelemetryEcto do end end + @doc false + def default_metadata_preprocessor(meta), do: meta + defp format_error(%{__exception__: true} = exception) do Exception.message(exception) end defp format_error(_), do: "" - defp add_measurements(attributes, measurements, time_unit) do + defp set_server_address(attrs, repo, repo_config) do + case repo.__adapter__() do + Ecto.Adapters.SQLite3 -> + Map.put(attrs, ServerAttributes.server_address(), repo_config.database) + + _ -> + Map.put(attrs, ServerAttributes.server_address(), repo_config.hostname) + end + end + + db_systems = [ + {Ecto.Adapters.Postgres, DBAttributes.db_system_values().postgresql}, + {Ecto.Adapters.MyXQL, DBAttributes.db_system_values().mysql}, + {Ecto.Adapters.SQLite3, DBAttributes.db_system_values().sqlite}, + {Ecto.Adapters.Tds, DBAttributes.db_system_values().mssql} + ] + + for {adapter, system} <- db_systems do + defp db_system(unquote(adapter)), do: unquote(system) + end + + # NOTE: This is the catch-all clause where we use other_sql as the db.system value, but it may not be a SQL based database. + defp db_system(_), do: DBAttributes.db_system_values().other_sql + + defp set_db_collection_name(attributes, source) do + Map.put(attributes, DBAttributes.db_collection_name(), source) + end + + # only set port for non-standard ports + defp maybe_add_server_port(attributes, adapter, repo_config) do + case {adapter, Map.get(repo_config, :port)} do + {_, nil} -> attributes + {Ecto.Adapters.Postgres, 5432} -> attributes + {Ecto.Adapters.MyXQL, 3306} -> attributes + {Ecto.Adapters.Tds, 1433} -> attributes + {Ecto.Adapters.SQLite3, _} -> attributes + {_, port} -> Map.put(attributes, ServerAttributes.server_port(), port) + end + end + + defp set_db_operation_name(attributes, query, :ecto_sql_query) do + Map.put(attributes, unquote(DBAttributes.db_operation_name()), extract_sql_command(query)) + end + + defp set_db_operation_name(attributes, _, _), do: attributes + + defp extract_sql_command("SELECT " <> _rest), do: :SELECT + defp extract_sql_command("INSERT " <> _rest), do: :INSERT + defp extract_sql_command("UPDATE " <> _rest), do: :UPDATE + defp extract_sql_command("DELETE " <> _rest), do: :DELETE + defp extract_sql_command("WITH " <> _rest), do: :WITH + defp extract_sql_command("BEGIN " <> _rest), do: :BEGIN + defp extract_sql_command("COMMIT " <> _rest), do: :COMMIT + defp extract_sql_command("ROLLBACK " <> _rest), do: :ROLLBACK + defp extract_sql_command("CREATE " <> _rest), do: :CREATE + defp extract_sql_command("ALTER " <> _rest), do: :ALTER + defp extract_sql_command("DROP " <> _rest), do: :DROP + defp extract_sql_command("TRUNCATE " <> _rest), do: :TRUNCATE + defp extract_sql_command("USE " <> _rest), do: :USE + defp extract_sql_command("SHOW " <> _rest), do: :SHOW + defp extract_sql_command("DESCRIBE " <> _rest), do: :DESCRIBE + defp extract_sql_command("EXPLAIN " <> _rest), do: :EXPLAIN + defp extract_sql_command("SET " <> _rest), do: :SET + defp extract_sql_command("GRANT " <> _rest), do: :GRANT + defp extract_sql_command("REVOKE " <> _rest), do: :REVOKE + defp extract_sql_command("SAVEPOINT " <> _rest), do: :SAVEPOINT + defp extract_sql_command("RELEASE " <> _rest), do: :RELEASE + defp extract_sql_command("PREPARE " <> _rest), do: :PREPARE + defp extract_sql_command("EXECUTE " <> _rest), do: :EXECUTE + defp extract_sql_command("DEALLOCATE " <> _rest), do: :DEALLOCATE + defp extract_sql_command("CALL " <> _rest), do: :CALL + defp extract_sql_command("FETCH " <> _rest), do: :FETCH + defp extract_sql_command("DECLARE " <> _rest), do: :DECLARE + defp extract_sql_command("CLOSE " <> _rest), do: :CLOSE + defp extract_sql_command("DISCARD " <> _rest), do: :DISCARD + defp extract_sql_command("LISTEN " <> _rest), do: :LISTEN + defp extract_sql_command("NOTIFY " <> _rest), do: :NOTIFY + defp extract_sql_command("REINDEX " <> _rest), do: :REINDEX + defp extract_sql_command("VACUUM " <> _rest), do: :VACUUM + defp extract_sql_command("CLUSTER " <> _rest), do: :CLUSTER + defp extract_sql_command("COPY " <> _rest), do: :COPY + defp extract_sql_command("ANALYZE " <> _rest), do: :ANALYZE + + # Fallback clause for unrecognized commands + defp extract_sql_command(_query), do: :UNKNOWN + + defp maybe_add_error_type(attributes, _adapter, {:ok, _}), do: attributes + + defp maybe_add_error_type(attributes, adapter, {:error, error}) do + error_type = get_error_type(adapter, error) + Map.put(attributes, unquote(ErrorAttributes.error_type()), error_type) + end + + defp get_error_type(Ecto.Adapters.Postgres, %{postgres: %{code: code}}), do: code + defp get_error_type(Ecto.Adapters.MyXQL, %{mysql: %{code: code}}), do: code + defp get_error_type(Ecto.Adapters.Tds, %{mssql: %{number: number}}), do: number + defp get_error_type(Ecto.Adapters.SQLite3, _), do: :_OTHER + defp get_error_type(_adapter, _), do: :_OTHER + + defp add_measurements(attributes, measurements) do measurements |> Enum.reduce(attributes, fn - {k, v}, acc - when not is_nil(v) and k in [:decode_time, :query_time, :queue_time, :idle_time] -> - Map.put( - acc, - String.to_atom("#{k}_#{time_unit}s"), - System.convert_time_unit(v, :native, time_unit) + {k, v}, attrs when not is_nil(v) -> + set_measurement( + attrs, + k, + System.convert_time_unit(v, :native, :nanosecond) / 1_000_000_000 ) - _, acc -> - acc + _, attrs -> + attrs end) end - defp maybe_add_db_statement(attributes, :enabled, query) do - Map.put(attributes, :"db.statement", query) + defp set_measurement(attrs, :total_time, time) do + Map.put(attrs, unquote(EctoAttributes.ecto_total_time_duration()), time) end - defp maybe_add_db_statement(attributes, :disabled, _query) do - attributes + defp set_measurement(attrs, :decode_time, time) do + Map.put(attrs, unquote(EctoAttributes.ecto_decode_time_duration()), time) end - defp maybe_add_db_statement(attributes, sanitizer, query) when is_function(sanitizer, 1) do - Map.put(attributes, :"db.statement", sanitizer.(query)) + defp set_measurement(attrs, :query_time, time) do + Map.put(attrs, unquote(EctoAttributes.ecto_query_time_duration()), time) end - defp maybe_add_db_statement(attributes, _, _query) do - attributes + defp set_measurement(attrs, :queue_time, time) do + Map.put(attrs, unquote(EctoAttributes.ecto_queue_time_duration()), time) end - defp maybe_add_db_system(attributes, Ecto.Adapters.Postgres) do - Map.put(attributes, :"db.system", :postgresql) + defp set_measurement(attrs, :idle_time, time) do + Map.put(attrs, unquote(EctoAttributes.ecto_idle_time_duration()), time) end - defp maybe_add_db_system(attributes, Ecto.Adapters.MyXQL) do - Map.put(attributes, :"db.system", :mysql) - end + defp set_measurement(attrs, _measurement, _time), do: attrs - defp maybe_add_db_system(attributes, Ecto.Adapters.SQLite3) do - Map.put(attributes, :"db.system", :sqlite) + defp set_db_query_text(attributes, query, :ecto_sql_query) do + Map.put(attributes, unquote(DBAttributes.db_query_text()), query) end - defp maybe_add_db_system(attributes, Ecto.Adapters.Tds) do - Map.put(attributes, :"db.system", :mssql) - end + defp set_db_query_text(attributes, _, _), do: attributes - defp maybe_add_db_system(attributes, _) do - attributes + defp set_additional_attributes(attrs, %{additional_span_attributes: extra}, %{ + attributes: per_query_attrs + }) + when is_map(per_query_attrs) do + extra + |> Map.merge(per_query_attrs) + |> Map.merge(attrs) end - defp add_additional_attributes(attributes, additional_attributes) do - Map.merge(attributes, additional_attributes) + defp set_additional_attributes(attrs, %{additional_span_attributes: extra}, _) do + Map.merge(extra, attrs) end + + defp set_additional_attributes(attrs, _, _), do: attrs + + defp span_name(_, %{span_name: name}) when is_atom(name) or is_binary(name), do: name + + # SHOULD be `{db.operation.name} {target}` if there is a (low-cardinality) {db.operation.name} available. + defp span_name( + %{unquote(DBAttributes.db_operation_name()) => db_operation_name} = attributes, + _ + ), + do: "#{db_operation_name} #{target(attributes)}" + + # If there is no (low-cardinality) `db.operation.name` available, database span names SHOULD be `{target}`. + defp span_name(attributes, _), do: target(attributes) + + # `db.collection.name` SHOULD be used for data manipulation operations or operations on database collections. + defp target(%{unquote(DBAttributes.db_collection_name()) => db_collection_name}), + do: db_collection_name + + # `db.namespace` SHOULD be used for operations on a specific database namespace. + defp target(%{unquote(DBAttributes.db_namespace()) => db_namespace}), do: db_namespace end diff --git a/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto_attributes.ex b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto_attributes.ex new file mode 100644 index 00000000..0eaac1ae --- /dev/null +++ b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto_attributes.ex @@ -0,0 +1,65 @@ +defmodule OpentelemetryEcto.EctoAttributes do + @moduledoc """ + OpenTelemetry Semantic Conventions for Ecto attributes. + """ + + @doc """ + The time spent decoding the data received from the database in seconds. + + ### Value type + + Value must be of type `float()`. + """ + @spec ecto_decode_time_duration :: :"ecto.decode_time.duration" + def ecto_decode_time_duration do + :"ecto.decode_time.duration" + end + + @doc """ + The time the connection spent waiting before being checked out for the query in seconds. + + ### Value type + + Value must be of type `float()`. + """ + @spec ecto_idle_time_duration :: :"ecto.idle_time.duration" + def ecto_idle_time_duration do + :"ecto.idle_time.duration" + end + + @doc """ + The time spent waiting to check out a database connection in seconds. + + ### Value type + + Value must be of type `float()`. + """ + @spec ecto_queue_time_duration :: :"ecto.queue_time.duration" + def ecto_queue_time_duration do + :"ecto.queue_time.duration" + end + + @doc """ + The time spent executing the query in seconds. + + ### Value type + + Value must be of type `float()`. + """ + @spec ecto_query_time_duration :: :"ecto.query_time.duration" + def ecto_query_time_duration do + :"ecto.query_time.duration" + end + + @doc """ + The sum of (queue_time, query_time, and decode_time)️ in seconds. + + ### Value type + + Value must be of type `float()`. + """ + @spec ecto_total_time_duration :: :"ecto.total_time.duration" + def ecto_total_time_duration do + :"ecto.total_time.duration" + end +end diff --git a/instrumentation/opentelemetry_ecto/mix.exs b/instrumentation/opentelemetry_ecto/mix.exs index d7d85745..b61dcc31 100644 --- a/instrumentation/opentelemetry_ecto/mix.exs +++ b/instrumentation/opentelemetry_ecto/mix.exs @@ -1,15 +1,20 @@ defmodule OpentelemetryEcto.MixProject do use Mix.Project - @version "1.2.0" + @version "2.0.0-beta.1" def project do [ app: :opentelemetry_ecto, description: description(), version: @version, - elixir: "~> 1.11", + elixir: "~> 1.14", start_permanent: Mix.env() == :prod, + dialyzer: [ + plt_add_apps: [:ex_unit, :mix], + plt_core_path: "plts", + plt_local_path: "plts" + ], deps: deps(), aliases: aliases(), elixirc_paths: elixirc_paths(Mix.env()), @@ -51,20 +56,31 @@ defmodule OpentelemetryEcto.MixProject do defp elixirc_paths(_), do: ["lib"] defp aliases() do - [test: ["ecto.drop -q", "ecto.create -q", "ecto.migrate --quiet", "test"]] + [ + test: [ + "ecto.drop -q", + "ecto.create -q", + "test" + ] + ] end defp deps do [ - {:telemetry, "~> 0.4 or ~> 1.0"}, - {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:ex_doc, "~> 0.34", only: [:dev], runtime: false}, - {:ecto_sql, ">= 3.0.0", only: [:dev, :test]}, - {:postgrex, ">= 0.15.0", only: [:dev, :test]}, - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:opentelemetry_process_propagator, "~> 0.3"} + {:nimble_options, "~> 1.0"}, + {:telemetry, "~> 1.0"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_process_propagator, "~> 0.3"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:opentelemetry, "~> 1.5", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, + {:ecto_sqlite3, "~> 0.19", only: [:dev, :test]}, + {:ecto_sql, "~> 3.12", only: [:dev, :test]}, + {:postgrex, "~> 0.20", only: [:dev, :test]}, + {:myxql, "~> 0.7", only: [:dev, :test]}, + {:tds, "~> 2.3", only: [:dev, :test]}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] end end diff --git a/instrumentation/opentelemetry_ecto/mix.lock b/instrumentation/opentelemetry_ecto/mix.lock index 1200583a..1862f6d7 100644 --- a/instrumentation/opentelemetry_ecto/mix.lock +++ b/instrumentation/opentelemetry_ecto/mix.lock @@ -1,29 +1,37 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, - "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.19.0", "00030bbaba150369ff3754bbc0d2c28858e8f528ae406bf6997d1772d3a03203", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "297b16750fe229f3056fe32afd3247de308094e8b0298aef0d73a8493ce97c81"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "exqlite": {:hex, :exqlite, "0.29.0", "e6f1de4bfe3ce6e4c4260b15fef830705fa36632218dc7eafa0a5aba3a5d6e04", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a75f8a069fcdad3e5f95dfaddccd13c2112ea3b742fdcc234b96410e9c1bde00"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "myxql": {:hex, :myxql, "0.7.1", "7c7b75aa82227cd2bc9b7fbd4de774fb19a1cdb309c219f411f82ca8860f8e01", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a491cdff53353a09b5850ac2d472816ebe19f76c30b0d36a43317a67c9004936"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "tds": {:hex, :tds, "2.3.5", "fedfb96d53206f01eac62ead859e47e1541a62e1553e9eb7a8801c7dca59eae8", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "52e350f5dd5584bbcff9859e331be144d290b41bd4c749b936014a17660662f2"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/instrumentation/opentelemetry_ecto/priv/test_repo/migrations/1_setup_tables.exs b/instrumentation/opentelemetry_ecto/priv/test_repo/migrations/1_setup_tables.exs index 02a8f877..1a8d72b7 100644 --- a/instrumentation/opentelemetry_ecto/priv/test_repo/migrations/1_setup_tables.exs +++ b/instrumentation/opentelemetry_ecto/priv/test_repo/migrations/1_setup_tables.exs @@ -3,17 +3,17 @@ defmodule OpentelemetryEcto.TestRepo.Migrations.SetupTables do def change do create table(:users) do - add :email, :string + add(:email, :string) end create table(:posts) do - add :body, :text - add :user_id, references(:users) + add(:body, :text) + add(:user_id, references(:users)) end create table(:comments) do - add :body, :text - add :user_id, references(:users) + add(:body, :text) + add(:user_id, references(:users)) end end end diff --git a/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs b/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs index 14555909..e626767f 100644 --- a/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs +++ b/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs @@ -3,151 +3,395 @@ defmodule OpentelemetryEctoTest do import Ecto.Query require OpenTelemetry.Tracer + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.Incubating.DBAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpentelemetryEcto.EctoAttributes + alias OpentelemetryEcto.TestRepo, as: Repo + alias OpentelemetryEcto.MyXQLTestRepo, as: MyXQLRepo + alias OpentelemetryEcto.Sqlite3TestRepo, as: Sqlite3Repo + alias OpentelemetryEcto.TdsTestRepo, as: TdsRepo alias OpentelemetryEcto.TestModels.{Comment, User, Post} require Ecto.Query, as: Query require OpenTelemetry.Tracer, as: Tracer - @event_name [:opentelemetry_ecto, :test_repo] - require Record for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do Record.defrecord(name, spec) end - setup do - :application.stop(:opentelemetry) - :application.set_env(:opentelemetry, :tracer, :otel_tracer_default) + setup_all do + # this is done to capture init events when the repos get started + setup_instrumentation() + + OpentelemetryEcto.TestRepo.start_link() + OpentelemetryEcto.TestRepo.Replica1.start_link() + OpentelemetryEcto.MyXQLTestRepo.start_link() + OpentelemetryEcto.TdsTestRepo.start_link() + OpentelemetryEcto.Sqlite3TestRepo.start_link() + + Ecto.Migrator.with_repo(OpentelemetryEcto.TestRepo, &Ecto.Migrator.run(&1, :up, all: true)) + + Ecto.Migrator.with_repo( + OpentelemetryEcto.TestRepo.Replica1, + &Ecto.Migrator.run(&1, :up, all: true) + ) + + Ecto.Migrator.with_repo( + OpentelemetryEcto.MyXQLTestRepo, + &Ecto.Migrator.run(&1, :up, all: true) + ) + + Ecto.Migrator.with_repo(OpentelemetryEcto.TdsTestRepo, &Ecto.Migrator.run(&1, :up, all: true)) + + Ecto.Migrator.with_repo( + OpentelemetryEcto.Sqlite3TestRepo, + &Ecto.Migrator.run(&1, :up, all: true) + ) + + ExUnit.start(capture_log: true) + + Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.TestRepo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.TestRepo.Replica1, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.MyXQLTestRepo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.TdsTestRepo, {:shared, self()}) + Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.Sqlite3TestRepo, {:shared, self()}) + + clear_handlers() + end + + setup do :application.set_env(:opentelemetry, :processors, [ {:otel_batch_processor, %{scheduled_delay_ms: 1}} ]) - :application.start(:opentelemetry) - :otel_batch_processor.set_exporter(:otel_exporter_pid, self()) - OpenTelemetry.Tracer.start_span("test") - on_exit(fn -> - OpenTelemetry.Tracer.end_span() + clear_handlers() + + Repo.delete_all(Comment) + Repo.delete_all(Post) + Repo.delete_all(User) end) end - test "captures basic query events" do - attach_handler() + defp setup_instrumentation(opts \\ []) do + [ + event_prefix: [:opentelemetry_ecto, :test_repo] + ] + |> Keyword.merge(opts) + |> OpentelemetryEcto.setup() + end - Repo.all(User) + defp clear_handlers do + :telemetry.list_handlers([]) + |> Enum.reject(&match?(%{id: {OpentelemetryEcto, :init}}, &1)) + |> Enum.each(fn h -> :telemetry.detach(h.id) end) + end + + test "dynamic repo" do + setup_instrumentation() + setup_instrumentation(event_prefix: [:tenant, :test]) + + {:ok, dyn_pid} = + start_supervised({ + OpentelemetryEcto.TestRepo, + name: :tenant_test, hostname: "0.0.0.0", port: 5433, telemetry_prefix: [:tenant, :test] + }) + + start_supervised({ + OpentelemetryEcto.TestRepo, + name: nil, hostname: "0.0.0.0", port: 5433 + }) + + start_supervised({ + OpentelemetryEcto.TestRepo, + name: nil, hostname: "127.0.0.1", port: 5433 + }) + + Ecto.Adapter.lookup_meta(Repo.get_dynamic_repo()) + + Repo.all(User, telemetry_options: [foo: :bar]) assert_receive {:span, span( - name: "opentelemetry_ecto.test_repo.query:users", + name: "SELECT users", attributes: attributes, kind: :client )} - assert %{ - "db.system": :postgresql, - "db.instance": "opentelemetry_ecto_test", - "db.type": :sql, - "db.url": "ecto://localhost", - decode_time_microseconds: _, - query_time_microseconds: _, - queue_time_microseconds: _, - source: "users", - total_time_microseconds: _ - } = :otel_attributes.map(attributes) - end + attrs = :otel_attributes.map(attributes) + + refute Map.has_key?(attrs, ServerAttributes.server_port()), + "port is not set when default for db" + + Repo.put_dynamic_repo(:tenant_test) + + Repo.__adapter__().storage_up(Repo.config()) + + Ecto.Migrator.with_repo(Repo, &Ecto.Migrator.run(&1, :up, all: true, dynamic_repo: dyn_pid)) + setup_instrumentation(event_prefix: [:tenant, :test]) + Ecto.Adapter.lookup_meta(Repo.get_dynamic_repo()) + + Ecto.Adapter.lookup_meta(Repo.Replica1.get_dynamic_repo()) - test "exclude unsantized query" do - attach_handler() Repo.all(User) - assert_receive {:span, span(attributes: attributes)} - assert !Map.has_key?(:otel_attributes.map(attributes), :"db.statement") + assert_receive {:span, + span( + name: "SELECT users", + attributes: attributes, + kind: :client + )} + + attrs = :otel_attributes.map(attributes) + + assert Map.get(attrs, ServerAttributes.server_port()) == 5433, + "port is set when not default for db" end - test "include unsanitized query when enabled" do - attach_handler(db_statement: :enabled) + test "captures basic query events - postgres" do + setup_instrumentation() + Repo.all(User) - assert_receive {:span, span(attributes: attributes)} + assert_receive {:span, + span( + name: "SELECT users", + attributes: attributes, + kind: :client + )} - assert %{"db.statement": "SELECT u0.\"id\", u0.\"email\" FROM \"users\" AS u0"} = - :otel_attributes.map(attributes) + attrs = :otel_attributes.map(attributes) + + expected_attrs = + [ + EctoAttributes.ecto_decode_time_duration(), + EctoAttributes.ecto_query_time_duration(), + EctoAttributes.ecto_queue_time_duration(), + EctoAttributes.ecto_total_time_duration() + ] + + for attr <- expected_attrs do + actual = Map.get(attrs, attr) + assert is_float(actual), "#{attr} expected a float got #{inspect(actual)}" + end + + expected_attrs = [ + {DBAttributes.db_system(), DBAttributes.db_system_values().postgresql}, + {DBAttributes.db_collection_name(), "users"}, + {DBAttributes.db_namespace(), "opentelemetry_ecto_test"}, + {ServerAttributes.server_address(), "localhost"}, + {DBAttributes.db_query_text(), ~s(SELECT u0."id", u0."email" FROM "users" AS u0)}, + {DBAttributes.db_operation_name(), :SELECT} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end end - test "include sanitized query with sanitizer function" do - attach_handler(db_statement: fn str -> String.replace(str, "SELECT", "") end) - Repo.all(User) + test "captures basic query events - myxql" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :my_xql_test_repo]) - assert_receive {:span, span(attributes: attributes)} + MyXQLRepo.all(User) - assert %{"db.statement": " u0.\"id\", u0.\"email\" FROM \"users\" AS u0"} = - :otel_attributes.map(attributes) + assert_receive {:span, + span( + name: "SELECT users", + attributes: attributes, + kind: :client + )} + + attrs = :otel_attributes.map(attributes) + + expected_attrs = + [ + EctoAttributes.ecto_decode_time_duration(), + EctoAttributes.ecto_query_time_duration(), + EctoAttributes.ecto_queue_time_duration(), + EctoAttributes.ecto_total_time_duration() + ] + + for attr <- expected_attrs do + actual = Map.get(attrs, attr) + assert is_float(actual), "#{attr} expected a float got #{inspect(actual)}" + end + + expected_attrs = [ + {DBAttributes.db_system(), DBAttributes.db_system_values().mysql}, + {DBAttributes.db_collection_name(), "users"}, + {DBAttributes.db_namespace(), "opentelemetry_ecto_test"}, + {ServerAttributes.server_address(), "localhost"}, + {DBAttributes.db_query_text(), "SELECT u0.`id`, u0.`email` FROM `users` AS u0"}, + {DBAttributes.db_operation_name(), :SELECT} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end end - test "include additional_attributes" do - attach_handler(additional_attributes: %{"config.attribute": "special value", "db.instance": "my_instance"}) + test "captures basic query events - tds" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :tds_test_repo]) - Repo.all(User) + TdsRepo.all(User) - assert_receive {:span, span(attributes: attributes)} + assert_receive {:span, + span( + name: "SELECT users", + attributes: attributes, + kind: :client + )} - assert %{"config.attribute": "special value", "db.instance": "my_instance"} = - :otel_attributes.map(attributes) + attrs = :otel_attributes.map(attributes) + + expected_attrs = + [ + EctoAttributes.ecto_decode_time_duration(), + EctoAttributes.ecto_query_time_duration(), + EctoAttributes.ecto_queue_time_duration(), + EctoAttributes.ecto_total_time_duration() + ] + + for attr <- expected_attrs do + actual = Map.get(attrs, attr) + assert is_float(actual), "#{attr} expected a float got #{inspect(actual)}" + end + + expected_attrs = [ + {DBAttributes.db_system(), DBAttributes.db_system_values().mssql}, + {DBAttributes.db_collection_name(), "users"}, + {DBAttributes.db_namespace(), "opentelemetry_ecto_test"}, + {ServerAttributes.server_address(), "localhost"}, + {DBAttributes.db_query_text(), "SELECT u0.[id], u0.[email] FROM [users] AS u0"}, + {DBAttributes.db_operation_name(), :SELECT} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end end - test "changes the time unit" do - attach_handler(time_unit: :millisecond) + test "captures basic query events - sqlite3" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :sqlite3_test_repo]) - Repo.all(Post) + Sqlite3Repo.all(User) assert_receive {:span, span( - name: "opentelemetry_ecto.test_repo.query:posts", - attributes: attributes + name: "SELECT users", + attributes: attributes, + kind: :client )} + attrs = :otel_attributes.map(attributes) + + expected_attrs = + [ + EctoAttributes.ecto_decode_time_duration(), + EctoAttributes.ecto_query_time_duration(), + EctoAttributes.ecto_queue_time_duration(), + EctoAttributes.ecto_total_time_duration() + ] + + for attr <- expected_attrs do + actual = Map.get(attrs, attr) + assert is_float(actual), "#{attr} expected a float got #{inspect(actual)}" + end + + expected_attrs = [ + {DBAttributes.db_system(), DBAttributes.db_system_values().sqlite}, + {DBAttributes.db_collection_name(), "users"}, + {DBAttributes.db_namespace(), "opentelemetry_ecto_test.db"}, + {ServerAttributes.server_address(), "opentelemetry_ecto_test.db"}, + {DBAttributes.db_query_text(), ~s(SELECT u0."id", u0."email" FROM "users" AS u0)}, + {DBAttributes.db_operation_name(), :SELECT} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end + end + + test "allow per-request options via telemetry_options" do + setup_instrumentation(additional_span_attributes: %{"config.attribute": "special value", "db.system": "my_system"}) + + Repo.all(User, + telemetry_options: [ + otel: %{ + span_name: "custom span name", + attributes: %{ + "config.attribute": "special value overwritten", + "db.system": "my_system", + extra: "should add" + } + } + ] + ) + + assert_receive {:span, span(name: "custom span name", attributes: attributes)} + + # don't merge instrumentation-set attrs but can overwrite config set assert %{ + "config.attribute": "special value overwritten", "db.system": :postgresql, - "db.instance": "opentelemetry_ecto_test", - "db.type": :sql, - "db.url": "ecto://localhost", - decode_time_milliseconds: _, - query_time_milliseconds: _, - queue_time_milliseconds: _, - source: "posts", - total_time_milliseconds: _ - } = :otel_attributes.map(attributes) + extra: "should add" + } = + :otel_attributes.map(attributes) + end + + def custom_metadata_processor(meta) do + %{meta | query: "custom sanitized"} end - test "changes the span name prefix" do - attach_handler(span_prefix: "Ecto") + test "allows modification of metadata" do + setup_instrumentation(telemetry_metadata_preprocessor: &__MODULE__.custom_metadata_processor/1) Repo.all(User) - assert_receive {:span, span(name: "Ecto:users")} + assert_receive {:span, span(attributes: attributes)} + + assert "custom sanitized" == :otel_attributes.map(attributes)[DBAttributes.db_query_text()] + end + + test "include additional_attributes" do + setup_instrumentation(additional_span_attributes: %{"config.attribute": "special value", "db.system": "my_system"}) + + Repo.all(User) + + assert_receive {:span, span(attributes: attributes)} + + # don't merge instrumentation-set attrs + assert %{"config.attribute": "special value", "db.system": :postgresql} = + :otel_attributes.map(attributes) end test "collects multiple spans" do user = Repo.insert!(%User{email: "opentelemetry@erlang.org"}) Repo.insert!(%Post{body: "We got traced!", user: user}) - attach_handler() + setup_instrumentation() User |> Repo.all() |> Repo.preload([:posts]) - assert_receive {:span, span(name: "opentelemetry_ecto.test_repo.query:users")} - assert_receive {:span, span(name: "opentelemetry_ecto.test_repo.query:posts")} + assert_receive {:span, span(name: "SELECT users")} + assert_receive {:span, span(name: "SELECT posts")} end - test "sets error message on error" do - attach_handler() + test "sets error message on error - postgres" do + setup_instrumentation() try do Repo.all(from(u in "users", select: u.non_existent_field)) @@ -157,11 +401,81 @@ defmodule OpentelemetryEctoTest do assert_receive {:span, span( - name: "opentelemetry_ecto.test_repo.query:users", - status: {:status, :error, message} + name: "SELECT users", + status: {:status, :error, message}, + attributes: attributes )} assert message =~ "non_existent_field does not exist" + + assert %{unquote(ErrorAttributes.error_type()) => :undefined_column} = + :otel_attributes.map(attributes) + end + + test "sets error message on error - myxql" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :my_xql_test_repo]) + + try do + MyXQLRepo.all(from(u in "users", select: u.non_existent_field)) + rescue + _ -> :ok + end + + assert_receive {:span, + span( + name: "SELECT users", + status: {:status, :error, message}, + attributes: attributes + )} + + assert message =~ "Unknown column 'u0.non_existent_field'" + + assert %{unquote(ErrorAttributes.error_type()) => 1054} = + :otel_attributes.map(attributes) + end + + test "sets error message on error - tds" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :tds_test_repo]) + + try do + TdsRepo.all(from(u in "users", select: u.non_existent_field)) + rescue + _ -> :ok + end + + assert_receive {:span, + span( + name: "SELECT users", + status: {:status, :error, message}, + attributes: attributes + )} + + assert message =~ "Invalid column name 'non_existent_field'" + + assert %{unquote(ErrorAttributes.error_type()) => 207} = + :otel_attributes.map(attributes) + end + + test "sets error message on error - sqlite3" do + setup_instrumentation(event_prefix: [:opentelemetry_ecto, :sqlite3_test_repo]) + + try do + Sqlite3Repo.all(from(u in "users", select: u.non_existent_field)) + rescue + _ -> :ok + end + + assert_receive {:span, + span( + name: "SELECT users", + status: {:status, :error, message}, + attributes: attributes + )} + + assert message =~ "no such column: u0.non_existent_field" + + assert %{unquote(ErrorAttributes.error_type()) => :_OTHER} = + :otel_attributes.map(attributes) end test "preloads in sequence are tied to the parent span" do @@ -169,7 +483,7 @@ defmodule OpentelemetryEctoTest do Repo.insert!(%Post{body: "We got traced!", user: user}) Repo.insert!(%Comment{body: "We got traced!", user: user}) - attach_handler() + setup_instrumentation() Tracer.with_span "parent span" do Repo.all(Query.from(User, preload: [:posts, :comments]), in_parallel: false) @@ -180,19 +494,19 @@ defmodule OpentelemetryEctoTest do assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:users" + name: "SELECT users" )} assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:posts" + name: "SELECT posts" )} assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:comments" + name: "SELECT comments" )} end @@ -201,7 +515,7 @@ defmodule OpentelemetryEctoTest do Repo.insert!(%Post{body: "We got traced!", user: user}) Repo.insert!(%Comment{body: "We got traced!", user: user}) - attach_handler() + setup_instrumentation() Tracer.with_span "parent span" do Repo.all(Query.from(User, preload: [:posts, :comments])) @@ -212,19 +526,19 @@ defmodule OpentelemetryEctoTest do assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:users" + name: "SELECT users" )} assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:posts" + name: "SELECT posts" )} assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:comments" + name: "SELECT comments" )} end @@ -233,7 +547,7 @@ defmodule OpentelemetryEctoTest do Repo.insert!(%Post{body: "We got traced!", user: user}) Repo.insert!(%Comment{body: "We got traced!", user: user}) - attach_handler() + setup_instrumentation() Tracer.with_span "parent span" do users_query = from(u in User, preload: [:posts, :comments]) @@ -246,34 +560,34 @@ defmodule OpentelemetryEctoTest do assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:users" + name: "SELECT users" )} # comments preload assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:comments" + name: "SELECT comments" )} # users preload assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:users" + name: "SELECT users" )} # preloads of user assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:posts" + name: "SELECT posts" )} assert_receive {:span, span( parent_span_id: ^root_span_id, - name: "opentelemetry_ecto.test_repo.query:comments" + name: "SELECT comments" )} end @@ -282,7 +596,7 @@ defmodule OpentelemetryEctoTest do Repo.insert!(%Post{body: "We got traced!", user: user}) Repo.insert!(%Comment{body: "We got traced!", user: user}) - attach_handler() + setup_instrumentation() Tracer.with_span "root span" do task = @@ -301,18 +615,7 @@ defmodule OpentelemetryEctoTest do assert_receive {:span, span( parent_span_id: ^parent_span_id, - name: "opentelemetry_ecto.test_repo.query:users" + name: "SELECT users" )} end - - def attach_handler(config \\ []) do - # For now setup the handler manually in each test - handler = {__MODULE__, self()} - - :telemetry.attach(handler, @event_name ++ [:query], &OpentelemetryEcto.handle_event/4, config) - - on_exit(fn -> - :telemetry.detach(handler) - end) - end end diff --git a/instrumentation/opentelemetry_ecto/test/support/myxql_test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/myxql_test_repo.ex new file mode 100644 index 00000000..8157a721 --- /dev/null +++ b/instrumentation/opentelemetry_ecto/test/support/myxql_test_repo.ex @@ -0,0 +1,5 @@ +defmodule OpentelemetryEcto.MyXQLTestRepo do + use Ecto.Repo, + otp_app: :opentelemetry_ecto, + adapter: Ecto.Adapters.MyXQL +end diff --git a/instrumentation/opentelemetry_ecto/test/support/sqlite3_test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/sqlite3_test_repo.ex new file mode 100644 index 00000000..b24a083d --- /dev/null +++ b/instrumentation/opentelemetry_ecto/test/support/sqlite3_test_repo.ex @@ -0,0 +1,5 @@ +defmodule OpentelemetryEcto.Sqlite3TestRepo do + use Ecto.Repo, + otp_app: :opentelemetry_ecto, + adapter: Ecto.Adapters.SQLite3 +end diff --git a/instrumentation/opentelemetry_ecto/test/support/tds_test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/tds_test_repo.ex new file mode 100644 index 00000000..d5210ad4 --- /dev/null +++ b/instrumentation/opentelemetry_ecto/test/support/tds_test_repo.ex @@ -0,0 +1,5 @@ +defmodule OpentelemetryEcto.TdsTestRepo do + use Ecto.Repo, + otp_app: :opentelemetry_ecto, + adapter: Ecto.Adapters.Tds +end diff --git a/instrumentation/opentelemetry_ecto/test/support/test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/test_repo.ex index 74e818be..8a37c11f 100644 --- a/instrumentation/opentelemetry_ecto/test/support/test_repo.ex +++ b/instrumentation/opentelemetry_ecto/test/support/test_repo.ex @@ -1,6 +1,22 @@ defmodule OpentelemetryEcto.TestRepo do use Ecto.Repo, otp_app: :opentelemetry_ecto, - adapter: Ecto.Adapters.Postgres, - telemetry_prefix: [:opentelemetry_ecto, :test_repo] + adapter: Ecto.Adapters.Postgres + + @replicas [ + OpentelemetryEcto.TestRepo.Replica1 + ] + + def replica do + Enum.random(@replicas) + end + + for repo <- @replicas do + defmodule repo do + use Ecto.Repo, + otp_app: :opentelemetry_ecto, + adapter: Ecto.Adapters.Postgres, + read_only: true + end + end end diff --git a/instrumentation/opentelemetry_ecto/test/test_helper.exs b/instrumentation/opentelemetry_ecto/test/test_helper.exs index 7e95f222..6a0af57d 100644 --- a/instrumentation/opentelemetry_ecto/test/test_helper.exs +++ b/instrumentation/opentelemetry_ecto/test/test_helper.exs @@ -1,5 +1 @@ -OpentelemetryEcto.TestRepo.start_link() - ExUnit.start(capture_log: true) - -Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.TestRepo, {:shared, self()}) diff --git a/instrumentation/opentelemetry_finch/CHANGELOG.md b/instrumentation/opentelemetry_finch/CHANGELOG.md index 50ebd7cb..f7ec4478 100644 --- a/instrumentation/opentelemetry_finch/CHANGELOG.md +++ b/instrumentation/opentelemetry_finch/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.0 + +### Changed + +* Update OpenTelemetry API and Semantic Conventions + ## 0.2.0 * Span attributes update diff --git a/instrumentation/opentelemetry_finch/README.md b/instrumentation/opentelemetry_finch/README.md index 0974bbaa..fa64527c 100644 --- a/instrumentation/opentelemetry_finch/README.md +++ b/instrumentation/opentelemetry_finch/README.md @@ -11,7 +11,7 @@ dependencies in `mix.exs`: ```elixir def deps do [ - {:opentelemetry_finch, "~> 0.1"} + {:opentelemetry_finch, "~> 0.3"} ] end ``` @@ -61,4 +61,3 @@ In your application start: 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_finch](https://hexdocs.pm/opentelemetry_finch). - diff --git a/instrumentation/opentelemetry_finch/mix.exs b/instrumentation/opentelemetry_finch/mix.exs index fc54d625..c72f695c 100644 --- a/instrumentation/opentelemetry_finch/mix.exs +++ b/instrumentation/opentelemetry_finch/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryFinch.MixProject do use Mix.Project - @version "0.2.0" + @version "0.3.0" def project do [ @@ -54,14 +54,14 @@ defmodule OpentelemetryFinch.MixProject do defp deps do [ - {:telemetry, "~> 0.4 or ~> 1.0"}, - {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry_semantic_conventions, "~> 0.2"}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:ex_doc, "~> 0.34.0", only: [:dev], runtime: false}, - {:finch, "~> 0.18", only: [:dev, :test]}, - {:dialyxir, "~> 1.2", only: [:dev, :test], runtime: false}, + {:telemetry, "~> 1.0"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:opentelemetry, "~> 1.5", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, + {:finch, "~> 0.19", only: [:dev, :test]}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, {:bypass, "~> 2.0", only: :test} ] end diff --git a/instrumentation/opentelemetry_finch/mix.lock b/instrumentation/opentelemetry_finch/mix.lock index 91876f03..1b26dad7 100644 --- a/instrumentation/opentelemetry_finch/mix.lock +++ b/instrumentation/opentelemetry_finch/mix.lock @@ -1,38 +1,37 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, - "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, - "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [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 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [: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", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/instrumentation/opentelemetry_nebulex/CHANGELOG.md b/instrumentation/opentelemetry_nebulex/CHANGELOG.md index e1f3f910..c5a9b65a 100644 --- a/instrumentation/opentelemetry_nebulex/CHANGELOG.md +++ b/instrumentation/opentelemetry_nebulex/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.0 + +### Changed + + * Update OpenTelemetry API and Semantic Conventions + ## 0.1.0 -* Initial release + * Initial release diff --git a/instrumentation/opentelemetry_nebulex/mix.exs b/instrumentation/opentelemetry_nebulex/mix.exs index e79cde7b..7a64167b 100644 --- a/instrumentation/opentelemetry_nebulex/mix.exs +++ b/instrumentation/opentelemetry_nebulex/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryNebulex.MixProject do use Mix.Project - @version "0.1.0" + @version "0.2.0" def project do [ @@ -54,14 +54,14 @@ defmodule OpentelemetryNebulex.MixProject do defp deps do [ - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.34.0", only: [:dev], runtime: false}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, {:nebulex, "~> 2.1", only: [:dev, :test]}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_telemetry, "~> 1.0"}, - {:telemetry, "~> 0.4 or ~> 1.0"} + {:opentelemetry, "~> 1.5", only: [:dev, :test]}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]}, + {:opentelemetry_telemetry, "~> 1.1"}, + {:telemetry, "~> 1.0"} ] end end diff --git a/instrumentation/opentelemetry_nebulex/mix.lock b/instrumentation/opentelemetry_nebulex/mix.lock index 75686ff4..0ca1c891 100644 --- a/instrumentation/opentelemetry_nebulex/mix.lock +++ b/instrumentation/opentelemetry_nebulex/mix.lock @@ -2,25 +2,23 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nebulex": {:hex, :nebulex, "2.6.0", "6e581c0b53aab80a1431488d367a41c6a8ee53763f86e7a7a6754ee571ecfdab", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "cf4a0040bd6d58b8d0204f668641973520fdbd78bd8618e1cdb7a11e7bc560cf"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nebulex": {:hex, :nebulex, "2.6.5", "b1caa82ef46e9cf8c28f170b432c6938747741ab5d84b2c944277180b8ad8695", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "4eb4092058ba53289cb4d5a1b109de6fd094883dfc84a1c2f2ccc57e61a24935"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/instrumentation/opentelemetry_oban/docker-compose.yml b/instrumentation/opentelemetry_oban/docker-compose.yml index 52cc9417..6893303c 100644 --- a/instrumentation/opentelemetry_oban/docker-compose.yml +++ b/instrumentation/opentelemetry_oban/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.7" services: postgres: - image: postgres:16.3 + image: postgres:17.5 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres diff --git a/instrumentation/opentelemetry_oban/lib/opentelemetry_oban/plugin_handler.ex b/instrumentation/opentelemetry_oban/lib/opentelemetry_oban/plugin_handler.ex index c86e09b9..e77b8cb3 100644 --- a/instrumentation/opentelemetry_oban/lib/opentelemetry_oban/plugin_handler.ex +++ b/instrumentation/opentelemetry_oban/lib/opentelemetry_oban/plugin_handler.ex @@ -54,14 +54,14 @@ defmodule OpentelemetryOban.PluginHandler do def handle_plugin_exception( _event, _measurements, - %{stacktrace: stacktrace, error: error} = metadata, + %{kind: :error, reason: exception, stacktrace: stacktrace} = metadata, _config ) do ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) # Record exception and mark the span as errored - Span.record_exception(ctx, error, stacktrace) - Span.set_status(ctx, OpenTelemetry.status(:error, "")) + Span.record_exception(ctx, exception, stacktrace) + Span.set_status(ctx, OpenTelemetry.status(:error, Exception.message(exception))) OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) end diff --git a/instrumentation/opentelemetry_oban/mix.exs b/instrumentation/opentelemetry_oban/mix.exs index bd0c779f..5ba1ea18 100644 --- a/instrumentation/opentelemetry_oban/mix.exs +++ b/instrumentation/opentelemetry_oban/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryOban.MixProject do use Mix.Project - @version "1.1.0" + @version "1.1.1" def project do [ @@ -51,7 +51,7 @@ defmodule OpentelemetryOban.MixProject do {:opentelemetry, "~> 1.0", only: [:test]}, {:opentelemetry_exporter, "~> 1.0", only: [:test]}, {:telemetry, "~> 0.4 or ~> 1.0"}, - {:ex_doc, "~> 0.34", only: [:dev], runtime: false}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, {:postgrex, ">= 0.0.0", only: [:dev, :test]} ] end diff --git a/instrumentation/opentelemetry_oban/mix.lock b/instrumentation/opentelemetry_oban/mix.lock index de97e075..2abcfc31 100644 --- a/instrumentation/opentelemetry_oban/mix.lock +++ b/instrumentation/opentelemetry_oban/mix.lock @@ -2,28 +2,28 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, - "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "oban": {:hex, :oban, "2.17.4", "3ebe79dc0cad16f23e5feea418f9bc5b07d453b8fb7caf376d812be96157a5c5", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "71a804abea3bb7e104782a5b5337cbab76c1a56b9689a6d5159a3873c93898b6"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"}, - "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/instrumentation/opentelemetry_oban/test/opentelemetry_oban/plugin_handler_test.exs b/instrumentation/opentelemetry_oban/test/opentelemetry_oban/plugin_handler_test.exs index 3b1a4f6d..87e93aac 100644 --- a/instrumentation/opentelemetry_oban/test/opentelemetry_oban/plugin_handler_test.exs +++ b/instrumentation/opentelemetry_oban/test/opentelemetry_oban/plugin_handler_test.exs @@ -71,6 +71,14 @@ defmodule OpentelemetryOban.PluginHandlerTest do %{plugin: Elixir.Oban.Plugins.Stager} ) + exception = %UndefinedFunctionError{ + arity: 0, + function: :error, + message: nil, + module: Some, + reason: nil + } + :telemetry.execute( [:oban, :plugin, :exception], %{duration: 444}, @@ -80,17 +88,11 @@ defmodule OpentelemetryOban.PluginHandlerTest do stacktrace: [ {Some, :error, [], []} ], - error: %UndefinedFunctionError{ - arity: 0, - function: :error, - message: nil, - module: Some, - reason: nil - } + reason: exception } ) - expected_status = OpenTelemetry.status(:error, "") + expected_status = OpenTelemetry.status(:error, Exception.message(exception)) assert_receive {:span, span( diff --git a/instrumentation/opentelemetry_oban/test/test_helper.exs b/instrumentation/opentelemetry_oban/test/test_helper.exs index 5a3ab6a3..3270d9cd 100644 --- a/instrumentation/opentelemetry_oban/test/test_helper.exs +++ b/instrumentation/opentelemetry_oban/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.start() +ExUnit.start(capture_log: true) TestRepo.start_link( database: "opentelemetry_oban_test", diff --git a/instrumentation/opentelemetry_phoenix/CHANGELOG.md b/instrumentation/opentelemetry_phoenix/CHANGELOG.md index 9d141596..3c5d4af6 100644 --- a/instrumentation/opentelemetry_phoenix/CHANGELOG.md +++ b/instrumentation/opentelemetry_phoenix/CHANGELOG.md @@ -1,72 +1,107 @@ # Changelog +## 2.0.0 + +### Features + +- Added Bandit instrumentation support +- Semantic Conventions v1.27 support + +### Breaking Changes + +- Specifying an adapter is now required. Simply add the instrumentation + library as a dep and follow its setup instructions, then specify your + your adapter. +- Various HTTP Semantic Convention changes are included in the cowboy + and bandit libraries. One major change regards span naming. This may + affect your observability tools when keying on span names. The key + change there is the HTTP method is now a prefix, e.g. "GET /users/:user_id" +- OpenTelemetry API v1.4 required + +## 2.0.0-rc.1 + +### Features + +- Added Bandit instrumentation support +- Semantic Conventions v1.27 support + +### Breaking Changes + +- Specifying an adapter is now required. Simply add the instrumentation + library as a dep and follow its setup instructions, then specify your + your adapter. +- Various HTTP Semantic Convention changes are included in the cowboy + and bandit libraries. One major change regards span naming. This may + affect your observability tools when keying on span names. The key + change there is the HTTP method is now a prefix, e.g. "GET /users/:user_id" +- OpenTelemetry API v1.4 required + ## 1.2.0 ### Features -* Add support for LiveView courtesy of @derekkraan +- Add support for LiveView courtesy of @derekkraan ### Fixes -* Do not set a span as errored for exceptions, only based on 5xx HTTP status +- Do not set a span as errored for exceptions, only based on 5xx HTTP status ### Changed -* Minimum supported Elixir version changed to 1.11. +- Minimum supported Elixir version changed to 1.11. ## 1.1.1 ### Fixes -* [Relax nimble_options +- [Relax nimble_options requirement](https://github.com/open-telemetry/opentelemetry-erlang-contrib/pull/161) ## 1.1.0 ### Features -* Add support for opentelemetry_cowboy to capture the full request lifecycle +- Add support for opentelemetry_cowboy to capture the full request lifecycle when using the Plug.Cowboy adapter ## 1.0.0 ### Fixes -* Prevent attempting to record an exception when no active span present -* Only mark 5xx level status codes as errored +- Prevent attempting to record an exception when no active span present +- Only mark 5xx level status codes as errored ## 1.0.0-rc.7 ### Changed -* Opentelemetry 1.0 support +- Opentelemetry 1.0 support ## 1.0.0-rc.6 ### Changed -* Opentelemetry 1.0.0-rc.4 support +- Opentelemetry 1.0.0-rc.4 support ### Fixes -* pass attributes on span start for better sampling options -* fix http status attribute to match spec +- pass attributes on span start for better sampling options +- fix http status attribute to match spec ## 1.0.0-rc.4 ### Changed -* Opentelemetry dependency is locked to rc2 or lower in prep for breaking changes in rc3 +- Opentelemetry dependency is locked to rc2 or lower in prep for breaking changes in rc3 ## 1.0.0-rc.3 ### Changed -* Update dependencies to allow telemetry 1.0.0 +- Update dependencies to allow telemetry 1.0.0 ## 0.2.0 ### Changed -* Upgraded to Opentelemetry v0.5.0 - +- Upgraded to Opentelemetry v0.5.0 diff --git a/instrumentation/opentelemetry_phoenix/README.md b/instrumentation/opentelemetry_phoenix/README.md index d7e37642..f9e45179 100644 --- a/instrumentation/opentelemetry_phoenix/README.md +++ b/instrumentation/opentelemetry_phoenix/README.md @@ -10,46 +10,33 @@ After installing, setup the handler in your application behaviour before your top-level supervisor starts. ```elixir -OpentelemetryPhoenix.setup() +OpentelemetryPhoenix.setup(adapter: :bandit) ``` See the documentation for `OpentelemetryPhoenix.setup/1` for additional options that may be supplied. - ## Installation ```elixir def deps do [ - {:opentelemetry_phoenix, "~> 1.2"} + {:opentelemetry_phoenix, "~> 2.0.0-rc.2"} ] end ``` -It is high recommended to also install [OpentelemetryCowboy](https://hex.pm/packages/opentelemetry_cowboy) to capture the full +[OpentelemetryBandit](https://hex.pm/packages/opentelemetry_bandit) or [OpentelemetryCowboy](https://hex.pm/packages/opentelemetry_cowboy) must be installed to capture the full request lifecycle. Phoenix only handles part of the request lifecycle which can lead to incomplete request durations and lost traces for requests terminated at the socket level or before reaching Phoenix. -## Compatibility Matrix - -| OpentelemetryPhoenix Version | Otel Version | Notes | -| :--------------------------- | :----------- | :---- | -| | | | -| v0.1.0 | <= v.0.5.0 | | -| v1.0.0-rc.3 | v1.0.0-rc.1 | | -| | v1.0.0-rc.2 | | -| v1.0.0-rc.4 | v1.0.0-rc.2 | Otel rc.3 will be a breaking change | -| v1.0.0-rc.5 | v1.0.0-rc.3 | | -| v1.0.0-rc.6 | v1.0.0-rc.4 | | -| v1.0 | v1.0 | | - -## Note on phoenix integration +## Note on Phoenix integration `OpentelemetryPhoenix` requires phoenix to use `Plug.Telemetry` in order to correctly trace endpoint calls. The `endpoint.ex` file should look like: + ```Elixir defmodule MyApp.Endpoint do use Phoenix.Endpoint, otp_app: :my_app @@ -58,5 +45,5 @@ defmodule MyApp.Endpoint do ... end ``` -The [Phoenix endpoint.ex template](https://github.com/phoenixframework/phoenix/blob/v1.6.0/installer/templates/phx_web/endpoint.ex#L39) can be used as a reference +The [Phoenix endpoint.ex template](https://github.com/phoenixframework/phoenix/blob/v1.6.0/installer/templates/phx_web/endpoint.ex#L39) can be used as a reference diff --git a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex index 559529cb..40574ee6 100644 --- a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex +++ b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix.ex @@ -6,8 +6,9 @@ defmodule OpentelemetryPhoenix do doc: "The endpoint prefix in your endpoint." ], adapter: [ - type: {:in, [:cowboy2, :bandit, nil]}, - default: nil, + type: {:in, [:cowboy2, :bandit]}, + default: :cowboy2, + required: true, doc: "The phoenix server adapter being used.", type_doc: ":atom" ], @@ -27,10 +28,12 @@ defmodule OpentelemetryPhoenix do ### Supported options #{NimbleOptions.docs(@options_schema)} - If you are using PlugCowboy as your adapter you can add `:opentelemetry_cowboy` to your project - and pass the `:adapter` option when calling setup. Setting this option will prevent a new - span from being started and the existing cowboy span to be continued. This is the recommended - setup for measuring accurate latencies. + #### Adapters + + * `cowboy2` - when using PlugCowboy as your adapter you must add `:opentelemetry_cowboy` to your project + and pass `adapter: :cowboy2` option when calling setup. + * `bandit` - when using `Bandit.PhoenixAdapter` as your adapter you must add `:opentelemetry_bandit` to your project + and pass `adapter: :bandit` option when calling setup ## Usage @@ -50,24 +53,25 @@ defmodule OpentelemetryPhoenix do end """ - require OpenTelemetry.Tracer - alias OpenTelemetry.SemanticConventions + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + alias OpenTelemetry.Tracer - alias OpentelemetryPhoenix.Reason - require SemanticConventions.Trace require OpenTelemetry.Tracer @tracer_id __MODULE__ @typedoc "Setup options" - @type opts :: [endpoint_prefix() | adapter()] + @type opts :: [endpoint_prefix() | adapter() | liveview()] @typedoc "The endpoint prefix in your endpoint. Defaults to `[:phoenix, :endpoint]`" @type endpoint_prefix :: {:endpoint_prefix, [atom()]} - @typedoc "The phoenix server adapter being used. Optional" - @type adapter :: {:adapter, :cowboy2 | :bandit | term()} + @typedoc "The phoenix server adapter being used. Required" + @type adapter :: {:adapter, :cowboy2 | :bandit} + + @typedoc "Attach LiveView handlers. Optional" + @type liveview :: {:liveview, boolean()} @doc """ Initializes and configures the telemetry handlers. @@ -77,9 +81,7 @@ defmodule OpentelemetryPhoenix do opts = NimbleOptions.validate!(opts, @options_schema) attach_endpoint_start_handler(opts) - attach_endpoint_stop_handler(opts) - attach_router_start_handler() - attach_router_dispatch_exception_handler() + attach_router_start_handler(opts) if opts[:liveview] do attach_liveview_handlers() @@ -99,17 +101,7 @@ defmodule OpentelemetryPhoenix do end @doc false - def attach_endpoint_stop_handler(opts) do - :telemetry.attach( - {__MODULE__, :endpoint_stop}, - opts[:endpoint_prefix] ++ [:stop], - &__MODULE__.handle_endpoint_stop/4, - %{adapter: opts[:adapter]} - ) - end - - @doc false - def attach_router_start_handler do + def attach_router_start_handler(_opts) do :telemetry.attach( {__MODULE__, :router_dispatch_start}, [:phoenix, :router_dispatch, :start], @@ -118,16 +110,6 @@ defmodule OpentelemetryPhoenix do ) end - @doc false - def attach_router_dispatch_exception_handler do - :telemetry.attach( - {__MODULE__, :router_dispatch_exception}, - [:phoenix, :router_dispatch, :exception], - &__MODULE__.handle_router_dispatch_exception/4, - %{} - ) - end - def attach_liveview_handlers do :telemetry.attach_many( {__MODULE__, :live_view}, @@ -152,20 +134,13 @@ defmodule OpentelemetryPhoenix do :ok end - @doc false - def handle_endpoint_start(_event, _measurements, meta, config) do - Process.put({:otel_phoenix, :adapter}, config.adapter) - - case adapter() do - :cowboy2 -> - cowboy2_start() + # TODO: do we still need exception handling? Only when cowboy? - :bandit -> - bandit_start() + @doc false + def handle_endpoint_start(_event, _measurements, _meta, %{adapter: :bandit}), do: :ok - _ -> - default_start(meta) - end + def handle_endpoint_start(_event, _measurements, _meta, %{adapter: :cowboy2}) do + cowboy2_start() end defp cowboy2_start do @@ -173,165 +148,58 @@ defmodule OpentelemetryPhoenix do |> OpenTelemetry.Ctx.attach() end - defp bandit_start() do - OpentelemetryProcessPropagator.fetch_parent_ctx() - |> OpenTelemetry.Ctx.attach() - end - - defp default_start(meta) do - %{conn: conn} = meta - :otel_propagator_text_map.extract(conn.req_headers) - - peer_data = Plug.Conn.get_peer_data(conn) - - user_agent = header_value(conn, "user-agent") - peer_ip = Map.get(peer_data, :address) - - attributes = %{ - SemanticConventions.Trace.http_client_ip() => client_ip(conn), - SemanticConventions.Trace.http_flavor() => http_flavor(conn.adapter), - SemanticConventions.Trace.http_method() => conn.method, - SemanticConventions.Trace.http_scheme() => "#{conn.scheme}", - SemanticConventions.Trace.http_target() => conn.request_path, - SemanticConventions.Trace.http_user_agent() => user_agent, - SemanticConventions.Trace.net_host_name() => conn.host, - SemanticConventions.Trace.net_sock_host_addr() => to_string(:inet_parse.ntoa(conn.remote_ip)), - SemanticConventions.Trace.net_host_port() => conn.port, - SemanticConventions.Trace.net_sock_peer_addr() => to_string(:inet_parse.ntoa(peer_ip)), - SemanticConventions.Trace.net_peer_port() => peer_data.port, - SemanticConventions.Trace.net_transport() => :"IP.TCP" - } - - # start the span with a default name. Route name isn't known until router dispatch - OpentelemetryTelemetry.start_telemetry_span(@tracer_id, "HTTP #{conn.method}", meta, %{ - kind: :server, - attributes: attributes - }) - end - - @doc false - def handle_endpoint_stop(_event, _measurements, meta, _config) do - case adapter() do - :cowboy2 -> - :ok - - :bandit -> - :ok - - _ -> - default_stop(meta) - end - end - - defp default_stop(meta) do - %{conn: conn} = meta - - # ensure the correct span is current and update the status - OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, meta) - - Tracer.set_attribute(SemanticConventions.Trace.http_status_code(), conn.status) - - if conn.status >= 500 do - Tracer.set_status(OpenTelemetry.status(:error, "")) - end - - # end the Phoenix span - OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta) - end - @doc false def handle_router_dispatch_start(_event, _measurements, meta, _config) do attributes = %{ :"phoenix.plug" => meta.plug, :"phoenix.action" => meta.plug_opts, - SemanticConventions.Trace.http_route() => meta.route + HTTPAttributes.http_route() => meta.route } - Tracer.update_name("#{meta.route}") + Tracer.update_name("#{meta.conn.method} #{meta.route}") Tracer.set_attributes(attributes) end - @doc false - def handle_router_dispatch_exception( - _event, - _measurements, - %{kind: kind, reason: reason, stacktrace: stacktrace}, - _config - ) do - if OpenTelemetry.Span.is_recording(OpenTelemetry.Tracer.current_span_ctx()) do - {[reason: reason], attrs} = - Reason.normalize(reason) - |> Keyword.split([:reason]) - - # try to normalize all errors to Elixir exceptions - exception = Exception.normalize(kind, reason, stacktrace) - - # record exception and mark the span as errored - Tracer.record_exception(exception, stacktrace, attrs) - - # do not close the span as endpoint stop will still be called with - # more info, including the status code, which is nil at this stage - end - end - def handle_liveview_event( [:phoenix, _live, :mount, :start], _measurements, - meta, + %{socket: %{view: live_view}} = meta, _handler_configuration ) do - %{socket: socket} = meta - %{view: live_view} = socket - - attributes = %{} - OpentelemetryTelemetry.start_telemetry_span( @tracer_id, "#{inspect(live_view)}.mount", meta, %{kind: :server} ) - |> OpenTelemetry.Span.set_attributes(attributes) end def handle_liveview_event( [:phoenix, _live, :handle_params, :start], _measurements, - meta, + %{socket: %{view: live_view}} = meta, _handler_configuration ) do - %{socket: socket} = meta - %{view: live_view} = socket - - attributes = %{} - OpentelemetryTelemetry.start_telemetry_span( @tracer_id, "#{inspect(live_view)}.handle_params", meta, %{kind: :server} ) - |> OpenTelemetry.Span.set_attributes(attributes) end def handle_liveview_event( [:phoenix, _live, :handle_event, :start], _measurements, - meta, + %{socket: %{view: live_view}, event: event} = meta, _handler_configuration ) do - %{socket: socket, event: event, params: _params} = meta - %{view: live_view} = socket - - attributes = %{} - OpentelemetryTelemetry.start_telemetry_span( @tracer_id, "#{inspect(live_view)}.handle_event##{event}", meta, %{kind: :server} ) - |> OpenTelemetry.Span.set_attributes(attributes) end def handle_liveview_event( @@ -357,44 +225,4 @@ defmodule OpentelemetryPhoenix do OpenTelemetry.Span.set_status(ctx, OpenTelemetry.status(:error, "")) OpentelemetryTelemetry.end_telemetry_span(@tracer_id, meta) end - - defp http_flavor({_adapter_name, meta}) do - case Map.get(meta, :version) do - :"HTTP/1.0" -> :"1.0" - :"HTTP/1.1" -> :"1.1" - :"HTTP/2.0" -> :"2.0" - :"HTTP/2" -> :"2.0" - :SPDY -> :SPDY - :QUIC -> :QUIC - nil -> "" - end - end - - defp client_ip(%{remote_ip: remote_ip} = conn) do - case header_value(conn, "x-forwarded-for") do - "" -> - remote_ip - |> :inet_parse.ntoa() - |> to_string() - - ip_address -> - ip_address - |> String.split(",", parts: 2) - |> List.first() - end - end - - defp header_value(conn, header) do - case Plug.Conn.get_req_header(conn, header) do - [] -> - "" - - [value | _] -> - value - end - end - - defp adapter do - Process.get({:otel_phoenix, :adapter}) - end end diff --git a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex b/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex deleted file mode 100644 index 40d49a42..00000000 --- a/instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/reason.ex +++ /dev/null @@ -1,91 +0,0 @@ -defmodule OpentelemetryPhoenix.Reason do - def normalize(%{reason: reason}) do - # %Plug.Conn.WrapperError{} - normalize(reason) - end - - def normalize(:badarg) do - [reason: :badarg] - end - - def normalize(:badarith) do - [reason: :badarith] - end - - def normalize(:system_limit) do - [reason: :system_limit] - end - - def normalize(:cond_clause) do - [reason: :cond_clause] - end - - def normalize(:undef) do - [reason: :undef] - end - - def normalize({:badarity, {fun, args}}) do - {:arity, arity} = Function.info(fun, :arity) - [reason: :badarity, function: _inspect(fun), arity: arity, args: _inspect(args)] - end - - def normalize({:badfun, term}) do - [reason: :badfun, term: _inspect(term)] - end - - def normalize({:badstruct, struct, term}) do - [reason: :badstruct, struct: struct, term: _inspect(term)] - end - - def normalize({:badmatch, term}) do - [reason: :badmatch, term: _inspect(term)] - end - - def normalize({:badmap, term}) do - [reason: :badmap, term: _inspect(term)] - end - - def normalize({:badbool, op, term}) do - [reason: :badbool, operator: op, term: _inspect(term)] - end - - def normalize({:badkey, key}) do - [reason: :badkey, key: key] - end - - def normalize({:badkey, key, map}) do - [reason: :badkey, key: key, map: _inspect(map)] - end - - def normalize({:case_clause, term}) do - [reason: :case_clause, term: _inspect(term)] - end - - def normalize({:with_clause, term}) do - [reason: :with_clause, term: _inspect(term)] - end - - def normalize({:try_clause, term}) do - [reason: :try_clause, term: _inspect(term)] - end - - def normalize({:badarg, payload}) do - [reason: :badarg, payload: _inspect(payload)] - end - - def normalize(other) do - [reason: other] - end - - def normalize(other, _stacktrace) do - [reason: other] - end - - defp _inspect(term) do - if String.Chars.impl_for(term) do - term - else - inspect(term) - end - end -end diff --git a/instrumentation/opentelemetry_phoenix/mix.exs b/instrumentation/opentelemetry_phoenix/mix.exs index 7a55ce05..605671d9 100644 --- a/instrumentation/opentelemetry_phoenix/mix.exs +++ b/instrumentation/opentelemetry_phoenix/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryPhoenix.MixProject do use Mix.Project - @version "1.2.0" + @version "2.0.1" def project do [ @@ -62,18 +62,25 @@ defmodule OpentelemetryPhoenix.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:nimble_options, "~> 0.5 or ~> 1.0"}, - {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry_telemetry, "~> 1.0"}, + {:nimble_options, "~> 1.0"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_telemetry, "~> 1.1"}, {:opentelemetry_process_propagator, "~> 0.3"}, - {:opentelemetry_semantic_conventions, "~> 0.2"}, - {:telemetry, "~> 0.4 or ~> 1.0"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:otel_http, "~> 0.2"}, + {:telemetry, "~> 1.0"}, {:plug, ">= 1.11.0"}, {:cowboy_telemetry, "~> 0.4", only: [:dev, :test]}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:ex_doc, "~> 0.34", only: [:dev], runtime: false}, - {:plug_cowboy, "~> 2.4", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]}, + {:opentelemetry, "~> 1.5", only: [:dev, :test]}, + {:opentelemetry_bandit, "~> 0.2.0", only: [:dev, :test]}, + {:opentelemetry_cowboy, "~> 1.0.0", only: [:dev, :test]}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, + {:phoenix, "~> 1.7", only: [:dev, :test]}, + {:phoenix_html, "~> 4.1", only: [:dev, :test]}, + {:plug_cowboy, "~> 2.5", only: [:dev, :test]}, + {:bandit, "~> 1.5", only: [:dev, :test]}, + {:req, "~> 0.5", only: [:dev, :test]}, {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false} ] end diff --git a/instrumentation/opentelemetry_phoenix/mix.lock b/instrumentation/opentelemetry_phoenix/mix.lock index dfbff016..01a9d564 100644 --- a/instrumentation/opentelemetry_phoenix/mix.lock +++ b/instrumentation/opentelemetry_phoenix/mix.lock @@ -1,34 +1,52 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "bandit": {:hex, :bandit, "1.6.11", "2fbadd60c95310eefb4ba7f1e58810aa8956e18c664a3b2029d57edb7d28d410", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "543f3f06b4721619a1220bed743aa77bf7ecc9c093ba9fab9229ff6b99eacc65"}, + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, - "cowboy": {:hex, :cowboy, "2.11.0", "356bf784599cf6f2cdc6ad12fdcfb8413c2d35dab58404cf000e1feaed3f5645", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403"}, + "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, + "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_bandit": {:hex, :opentelemetry_bandit, "0.2.0", "60ee4789994d4532ec1b4c05cb8fad333c60ba2c248eb908918369fde045bbda", [:mix], [{:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:otel_http, "~> 0.2", [hex: :otel_http, repo: "hexpm", optional: false]}, {:plug, ">= 1.15.0", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57e31355a860250c9203ae34f0bf0290a14b72ab02b154535e1b2512a0767bca"}, + "opentelemetry_cowboy": {:hex, :opentelemetry_cowboy, "1.0.0", "786c7cde66a2493323c79d2c94e679ff501d459a9b403d8b60b9bef116333117", [:rebar3], [{:cowboy_telemetry, "~> 0.4", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.1", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:otel_http, "~> 0.2", [hex: :otel_http, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7575716eaccacd0eddc3e7e61403aecb5d0a6397183987d6049094aeb0b87a7c"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"}, - "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"}, - "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "otel_http": {:hex, :otel_http, "0.2.0", "b17385986c7f1b862f5d577f72614ecaa29de40392b7618869999326b9a61d8a", [:rebar3], [], "hexpm", "f2beadf922c8cfeb0965488dd736c95cc6ea8b9efce89466b3904d317d7cc717"}, + "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, + "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [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", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "thousand_island": {:hex, :thousand_island, "1.3.13", "d598c609172275f7b1648c9f6eddf900e42312b09bfc2f2020358f926ee00d39", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5a34bdf24ae2f965ddf7ba1a416f3111cfe7df50de8d66f6310e01fc2e80b02a"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.24.0", "d00e2887551ff8cdae4d0340d90d9fcbc4943c7b5f49d32ed4bc23aff4db9a44", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "90b25a58ee433d91c17f036d4d354bf8859a089bfda60e68a86f8eecae45ef1b"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, } diff --git a/instrumentation/opentelemetry_phoenix/test/integration_test.exs b/instrumentation/opentelemetry_phoenix/test/integration_test.exs new file mode 100644 index 00000000..bd91822f --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/test/integration_test.exs @@ -0,0 +1,654 @@ +Code.require_file("support/endpoint_helper.exs", __DIR__) + +otp_vsn = + :erlang.system_info(:otp_release) + |> to_string() + |> String.to_integer() + +if otp_vsn >= 27 do + defmodule OpentelemetryPhoenix.Integration.TracingTest do + use ExUnit.Case, async: false + import ExUnit.CaptureLog + import Phoenix.Integration.EndpointHelper + + @moduletag :integration + + @adapters [:cowboy, :bandit] + + defmodule TestController do + use Phoenix.Controller, + formats: [:html, :json] + + import Plug.Conn + + def root(conn, _params) do + Plug.Conn.send_resp(conn, 200, "ok") + end + + def hello(conn, _params) do + Plug.Conn.send_resp(conn, 200, "ok") + # %{"page" => "hello"} == params + end + + def user(conn, _params) do + Plug.Conn.send_resp(conn, 200, "ok") + end + + def with_body(conn, _params) do + conn + |> put_resp_content_type("application/json") + |> send_resp(200, Jason.encode!(%{"a" => "b"})) + end + + def oops(_, _) do + raise "oops" + end + + def halted(conn, _params) do + conn |> send_resp(500, "Internal Server Error") |> halt() + end + end + + defmodule Router do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + + get("/", TestController, :root) + + get("/hello", TestController, :hello) + + get("/users/:user_id", TestController, :user) + + get("/with_body", TestController, :with_body) + + get("/router/oops", TestController, :oops) + + get("/halted", TestController, :halted) + end + + for adapter <- @adapters do + defmodule Module.concat(["#{String.capitalize(to_string(adapter))}Endpoint"]) do + defmodule ErrorView do + def render("404.json", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do + %{error: "Got 404 from #{kind} with #{conn.method}"} + end + + def render(template, %{conn: conn}) do + unless conn.private.phoenix_endpoint do + raise "no endpoint in error view" + end + + "#{template} from Phoenix.ErrorView" + end + end + + use Phoenix.Endpoint, otp_app: :endpoint_int + + plug(Plug.Telemetry, event_prefix: [:phoenix, adapter, :endpoint]) + + plug(Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + ) + + plug(Plug.MethodOverride) + plug(Plug.Head) + + plug(:oops) + plug(Router) + + @doc """ + Verify errors from the plug stack too (before the router). + """ + def oops(conn, _opts) do + if conn.path_info == ~w(oops) do + raise "oops" + else + conn + end + end + end + end + + require OpenTelemetry.Span + require Record + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do + Record.defrecord(name, spec) + end + + for {name, spec} <- + Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do + Record.defrecord(name, spec) + end + + alias OpenTelemetry.SemConv.ClientAttributes + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.ExceptionAttributes + alias OpenTelemetry.SemConv.NetworkAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpenTelemetry.SemConv.UserAgentAttributes + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + alias OpenTelemetry.SemConv.Incubating.URLAttributes + + setup do + :otel_simple_processor.set_exporter(:otel_exporter_pid, self()) + + # Find available ports to use for this test + [bandit, cowboy] = get_unused_port_numbers(2) + + adapters = %{ + bandit: %{ + port: bandit, + spec: + {BanditEndpoint, + [ + http: [port: bandit], + url: [host: "bandit-example.com"], + adapter: Bandit.PhoenixAdapter, + server: true, + drainer: false, + render_errors: [accepts: ~w(html json)] + ]} + }, + cowboy: %{ + port: cowboy, + spec: + {CowboyEndpoint, + [ + http: [port: cowboy], + url: [host: "cowboy-example.com"], + adapter: Phoenix.Endpoint.Cowboy2Adapter, + server: true, + drainer: false, + render_errors: [accepts: ~w(html json)] + ]} + } + } + + on_exit(fn -> + :telemetry.list_handlers([]) + |> Enum.each(fn h -> :telemetry.detach(h.id) end) + end) + + adapters + end + + defp setup_adapter(adapter, opts \\ []) + + defp setup_adapter(:bandit, opts) do + OpentelemetryBandit.setup(opts) + + OpentelemetryPhoenix.setup( + adapter: :bandit, + endpoint_prefix: [:phoenix, :bandit, :endpoint] + ) + end + + defp setup_adapter(:cowboy, opts) do + :opentelemetry_cowboy.setup(opts) + + OpentelemetryPhoenix.setup( + adapter: :cowboy2, + endpoint_prefix: [:phoenix, :cowboy, :endpoint] + ) + end + + adapter_suites = + for adapter <- [:bandit, :cowboy], protocol <- [:http1, :http2], do: {adapter, protocol} + + for {adapter, protocol} <- adapter_suites do + describe "#{adapter} - #{protocol}" do + test "basic request with default options", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + setup_adapter(unquote(adapter)) + + Req.get("http://localhost:#{adapter_info.port}/users/1234", + params: [a: 1, b: "abc"], + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + }, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + assert_receive {:span, + span( + name: "GET /users/:user_id", + kind: :server, + attributes: span_attrs, + parent_span_id: 13_235_353_014_750_950_193 + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_proto = if unquote(protocol) == :http1, do: :"1.1", else: :"2" + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {NetworkAttributes.network_protocol_version(), expected_proto}, + {URLAttributes.url_path(), "/users/1234"}, + {URLAttributes.url_query(), "a=1&b=abc"}, + {URLAttributes.url_scheme(), :http}, + {HTTPAttributes.http_route(), "/users/:user_id"}, + {:"phoenix.action", :user}, + {:"phoenix.plug", OpentelemetryPhoenix.Integration.TracingTest.TestController} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") + assert OpenTelemetry.Tracer.current_span_ctx() == :undefined + end) + end + + test "public endpoint true", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + setup_adapter(unquote(adapter), public_endpoint: true) + + Req.get("http://localhost:#{adapter_info.port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + }, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + refute_receive {:span, + span( + name: "GET /hello", + kind: :server, + parent_span_id: 13_235_353_014_750_950_193 + )} + + assert_receive {:span, + span( + name: "GET /hello", + kind: :server, + links: links, + parent_span_id: :undefined + )} + + assert length(:otel_links.list(links)) == 1 + end) + end + + test "public endpoint fn", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter), + public_endpoint_fn: {__MODULE__, :public_endpoint_fn, []} + ) + + System.put_env("TENANT", "customer") + + Req.get("http://localhost:#{adapter_info.port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + }, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + refute_receive {:span, + span( + name: "GET /hello", + kind: :server, + parent_span_id: 13_235_353_014_750_950_193 + )} + + assert_receive {:span, + span( + name: "GET /hello", + kind: :server, + links: links, + parent_span_id: :undefined + )} + + assert length(:otel_links.list(links)) == 1 + + System.put_env("TENANT", "internal") + + Req.get("http://localhost:#{adapter_info.port}/hello", + headers: %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + }, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + assert_receive {:span, + span( + name: "GET /hello", + kind: :server, + parent_span_id: 13_235_353_014_750_950_193 + )} + + refute_receive {:span, + span( + name: "GET /hello", + kind: :server, + parent_span_id: :undefined + )} + + System.delete_env("PUBLIC") + end) + end + + test "with all opt-ins", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter), + opt_in_attrs: [ + ClientAttributes.client_port(), + HTTPAttributes.http_request_body_size(), + HTTPAttributes.http_response_body_size(), + NetworkAttributes.network_local_address(), + NetworkAttributes.network_local_port(), + NetworkAttributes.network_transport() + ], + request_headers: ["test-header"], + response_headers: ["content-type"] + ) + + Req.get!("http://localhost:#{adapter_info.port}/with_body", + headers: %{"test-header" => "request header"}, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + assert_receive {:span, + span( + name: "GET /with_body", + attributes: span_attrs + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {HTTPAttributes.http_request_body_size(), 0}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {String.to_atom("#{HTTPAttributes.http_request_header()}.test-header"), ["request header"]}, + {String.to_atom("#{HTTPAttributes.http_response_header()}.content-type"), + ["application/json; charset=utf-8"]}, + {NetworkAttributes.network_local_address(), "localhost"}, + {NetworkAttributes.network_local_port(), adapter_info.port}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/with_body"}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") + + client_port = Map.get(attrs, ClientAttributes.client_port()) + assert is_integer(client_port) + + body_size = Map.get(attrs, HTTPAttributes.http_response_body_size()) + # for some reason bandit and cowboy measure this differently with + # bandit being much larger despite the bodies being the same and compression + # not being enabled + assert is_integer(body_size) && body_size > 0 + end) + end + + test "with custom header settings", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter), + client_address_headers: ["x-forwarded-for", "custom-client"], + client_headers_sort_fn: &__MODULE__.custom_client_header_sort/2, + scheme_headers: ["custom-scheme", "x-forwarded-proto"], + scheme_headers_sort_fn: &__MODULE__.custom_scheme_header_sort/2, + server_address_headers: ["custom-host", "forwarded", "host"], + server_headers_sort_fn: &__MODULE__.custom_server_header_sort/2 + ) + + Req.get("http://localhost:#{adapter_info.port}/hello", + headers: %{ + "forwarded" => + ~S(host=developer.mozilla.org:4321; for=192.0.2.60, for="[2001:db8:cafe::17]";proto=http;by=203.0.113.43), + "x-forwarded-proto" => "http", + "custom-scheme" => "https", + "custom-client" => "23.23.23.23", + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "congo=t61rcWkgMzE" + }, + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + assert_receive {:span, + span( + name: "GET /hello", + kind: :server, + attributes: span_attrs + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "23.23.23.23"}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {ServerAttributes.server_address(), "developer.mozilla.org"}, + {ServerAttributes.server_port(), 4321}, + {URLAttributes.url_scheme(), :https} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") + end) + end + + test "with missing user-agent", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter)) + + {:ok, {{_, 200, _}, _, _}} = + :httpc.request( + :get, + {~c"http://localhost:#{adapter_info.port}/hello", []}, + [], + [] + ) + + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + + assert Map.get(attrs, UserAgentAttributes.user_agent_original()) == "" + end) + end + + test "with exception", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter)) + + Req.get("http://localhost:#{adapter_info.port}/router/oops", + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, + span( + name: "GET /router/oops", + attributes: span_attrs, + events: events, + status: ^expected_status + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ClientAttributes.client_address(), "127.0.0.1"}, + {ErrorAttributes.error_type(), RuntimeError}, + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 500}, + {NetworkAttributes.network_peer_address(), "127.0.0.1"}, + {URLAttributes.url_path(), "/router/oops"}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert actual == expected, " expected #{attr} to equal #{expected}, got #{actual}" + end + + [ + event( + name: :exception, + attributes: event_attributes + ) + ] = :otel_events.list(events) + + assert [ + ExceptionAttributes.exception_message(), + ExceptionAttributes.exception_stacktrace(), + ExceptionAttributes.exception_type() + ] == + Enum.sort(Map.keys(:otel_attributes.map(event_attributes))), + "exception attributes" + end) + end + + test "with halted request", %{unquote(adapter) => adapter_info} do + capture_log(fn -> + {:ok, _} = start_supervised(adapter_info.spec) + + setup_adapter(unquote(adapter)) + + Req.get("http://localhost:#{adapter_info.port}/halted", + retry: false, + connect_options: [protocols: [unquote(protocol)]] + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, + span( + name: "GET /halted", + kind: :server, + attributes: span_attrs, + status: ^expected_status + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ErrorAttributes.error_type(), "500"}, + {HTTPAttributes.http_response_status_code(), 500}, + {URLAttributes.url_scheme(), :http} + ] + + for {attr, val} <- expected_attrs do + assert Map.get(attrs, attr) == val, " expected #{attr} to equal #{val}" + end + end) + end + end + end + + def custom_client_header_sort(h1, h2) do + h1_priority = custom_client_header_priority(h1) + h2_priority = custom_client_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_client_header_priority({header_name, _value}) do + case header_name do + "custom-client" -> 1 + "x-forwarded-for" -> 2 + end + end + + def custom_server_header_sort(h1, h2) do + h1_priority = custom_server_header_priority(h1) + h2_priority = custom_server_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_server_header_priority({header_name, _value}) do + case header_name do + "custom-host" -> 1 + "x-forwarded-host" -> 2 + "forwarded" -> 3 + _ -> 4 + end + end + + def custom_scheme_header_sort(h1, h2) do + h1_priority = custom_scheme_header_priority(h1) + h2_priority = custom_scheme_header_priority(h2) + + case {h1_priority, h2_priority} do + {h1, h2} when h1 <= h2 -> + true + + {h1, h2} when h1 > h2 -> + false + end + end + + defp custom_scheme_header_priority({header_name, _value}) do + case header_name do + "custom-scheme" -> 1 + "x-forwarded-proto" -> 2 + end + end + + def public_endpoint_fn(_conn, _opts) do + System.get_env("TENANT") != "internal" + end + end +end diff --git a/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs b/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs index ebd415cc..af6edfed 100644 --- a/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs +++ b/instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix_test.exs @@ -6,7 +6,7 @@ defmodule OpentelemetryPhoenixTest do require OpenTelemetry.Span require Record - alias PhoenixMeta, as: Meta + alias OpenTelemetry.SemConv.ExceptionAttributes alias PhoenixLiveViewMeta, as: LiveViewMeta for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do @@ -18,257 +18,19 @@ defmodule OpentelemetryPhoenixTest do end setup do + Application.ensure_all_started([:telemetry]) :otel_simple_processor.set_exporter(:otel_exporter_pid, self()) - :ok - end - - test "records spans for Phoenix web requests" do - OpentelemetryPhoenix.setup() - - :telemetry.execute( - [:phoenix, :endpoint, :start], - %{system_time: System.system_time()}, - Meta.endpoint_start() - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :start], - %{system_time: System.system_time()}, - Meta.router_dispatch_start() - ) - - :telemetry.execute( - [:phoenix, :endpoint, :stop], - %{duration: 444}, - Meta.endpoint_stop() - ) - - assert_receive {:span, - span( - name: "/users/:user_id", - attributes: attributes, - parent_span_id: 13_235_353_014_750_950_193 - )} - - assert %{ - "http.client_ip": "10.211.55.2", - "http.flavor": :"1.1", - "net.host.name": "localhost", - "http.method": "GET", - "http.route": "/users/:user_id", - "http.scheme": "http", - "http.status_code": 200, - "http.target": "/users/123", - "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", - "net.sock.host.addr": "10.211.55.2", - "net.host.port": 4000, - "net.sock.peer.addr": "10.211.55.2", - "net.peer.port": 64291, - "net.transport": :"IP.TCP", - "phoenix.action": :user, - "phoenix.plug": Elixir.MyStoreWeb.PageController - } == :otel_attributes.map(attributes) - end - - test "parses x-forwarded-for with single value" do - OpentelemetryPhoenix.setup() - - x_forwarded_for_request("203.0.113.195") - - assert_receive {:span, span(attributes: attributes)} - - assert Map.fetch!(:otel_attributes.map(attributes), :"http.client_ip") == "203.0.113.195" - end - - test "parses x-forwarded-for with multiple values" do - OpentelemetryPhoenix.setup() - - x_forwarded_for_request("203.0.113.195, 70.41.3.18, 150.172.238.178") - - assert_receive {:span, span(attributes: attributes)} - - assert Map.fetch!(:otel_attributes.map(attributes), :"http.client_ip") == "203.0.113.195" - end - - test "records exceptions for Phoenix web requests" do - OpentelemetryPhoenix.setup() - - :telemetry.execute( - [:phoenix, :endpoint, :start], - %{system_time: System.system_time()}, - Meta.endpoint_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :start], - %{system_time: System.system_time()}, - Meta.router_dispatch_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :exception], - %{duration: 222}, - Meta.router_dispatch_exception(:normal) - ) - - :telemetry.execute( - [:phoenix, :endpoint, :stop], - %{duration: 444}, - Meta.endpoint_stop(:exception) - ) - - expected_status = OpenTelemetry.status(:error, "") - - assert_receive {:span, - span( - name: "/users/:user_id/exception", - attributes: attributes, - kind: :server, - events: events, - parent_span_id: 13_235_353_014_750_950_193, - status: ^expected_status - )} - - assert %{ - "http.client_ip": "10.211.55.2", - "http.flavor": :"1.1", - "net.host.name": "localhost", - "http.method": "GET", - "http.route": "/users/:user_id/exception", - "http.scheme": "http", - "http.status_code": 500, - "http.target": "/users/123/exception", - "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", - "net.sock.host.addr": "10.211.55.2", - "net.host.port": 4000, - "net.sock.peer.addr": "10.211.55.2", - "net.peer.port": 64291, - "net.transport": :"IP.TCP", - "phoenix.action": :code_exception, - "phoenix.plug": MyStoreWeb.PageController - } == :otel_attributes.map(attributes) - - [ - event( - name: "exception", - attributes: event_attributes - ) - ] = :otel_events.list(events) - - assert [:"exception.message", :"exception.stacktrace", :"exception.type", :key, :map] == - Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) - end + on_exit(fn -> + :telemetry.list_handlers([]) + |> Enum.each(fn h -> :telemetry.detach(h.id) end) + end) - test "records exceptions for nested Phoenix routers" do - OpentelemetryPhoenix.setup() - - :telemetry.execute( - [:phoenix, :endpoint, :start], - %{system_time: System.system_time()}, - Meta.endpoint_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :start], - %{system_time: System.system_time()}, - Meta.router_dispatch_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :exception], - %{duration: 222}, - Meta.router_dispatch_exception(:normal) - ) - - :telemetry.execute( - [:phoenix, :endpoint, :stop], - %{duration: 444}, - Meta.endpoint_stop(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :exception], - %{duration: 222}, - Meta.router_dispatch_exception(:normal) - ) - - assert_receive {:span, _} - - assert [_ | _] = :telemetry.list_handlers([:phoenix, :router_dispatch, :exception]) - end - - test "records exceptions for Phoenix web requests with plug wrappers" do - OpentelemetryPhoenix.setup() - - :telemetry.execute( - [:phoenix, :endpoint, :start], - %{system_time: System.system_time()}, - Meta.endpoint_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :start], - %{system_time: System.system_time()}, - Meta.router_dispatch_start(:exception) - ) - - :telemetry.execute( - [:phoenix, :router_dispatch, :exception], - %{duration: 222}, - Meta.router_dispatch_exception(:plug_wrapper) - ) - - :telemetry.execute( - [:phoenix, :endpoint, :stop], - %{duration: 444}, - Meta.endpoint_stop(:exception) - ) - - expected_status = OpenTelemetry.status(:error, "") - - assert_receive {:span, - span( - name: "/users/:user_id/exception", - attributes: attributes, - kind: :server, - events: events, - parent_span_id: 13_235_353_014_750_950_193, - status: ^expected_status - )} - - assert %{ - "http.client_ip": "10.211.55.2", - "http.flavor": :"1.1", - "net.host.name": "localhost", - "http.method": "GET", - "http.route": "/users/:user_id/exception", - "http.scheme": "http", - "http.status_code": 500, - "http.target": "/users/123/exception", - "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", - "net.sock.host.addr": "10.211.55.2", - "net.host.port": 4000, - "net.sock.peer.addr": "10.211.55.2", - "net.peer.port": 64291, - "net.transport": :"IP.TCP", - "phoenix.action": :code_exception, - "phoenix.plug": MyStoreWeb.PageController - } == :otel_attributes.map(attributes) - - [ - event( - name: "exception", - attributes: event_attributes - ) - ] = :otel_events.list(events) - - assert [:"exception.message", :"exception.stacktrace", :"exception.type"] == - Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) + :ok end test "records spans for Phoenix LiveView mount" do - OpentelemetryPhoenix.setup() + OpentelemetryPhoenix.setup(adapter: :cowboy2) :telemetry.execute( [:phoenix, :live_view, :mount, :start], @@ -292,7 +54,7 @@ defmodule OpentelemetryPhoenixTest do end test "records spans for Phoenix LiveView handle_params" do - OpentelemetryPhoenix.setup() + OpentelemetryPhoenix.setup(adapter: :cowboy2) :telemetry.execute( [:phoenix, :live_view, :handle_params, :start], @@ -316,7 +78,7 @@ defmodule OpentelemetryPhoenixTest do end test "records spans for Phoenix LiveView handle_event" do - OpentelemetryPhoenix.setup() + OpentelemetryPhoenix.setup(adapter: :cowboy2) :telemetry.execute( [:phoenix, :live_view, :handle_event, :start], @@ -340,7 +102,7 @@ defmodule OpentelemetryPhoenixTest do end test "handles exception during Phoenix LiveView handle_params" do - OpentelemetryPhoenix.setup() + OpentelemetryPhoenix.setup(adapter: :cowboy2) :telemetry.execute( [:phoenix, :live_view, :mount, :start], @@ -385,17 +147,21 @@ defmodule OpentelemetryPhoenixTest do [ event( - name: "exception", + name: :exception, attributes: event_attributes ) ] = :otel_events.list(events) - assert [:"exception.message", :"exception.stacktrace", :"exception.type"] == + assert [ + ExceptionAttributes.exception_message(), + ExceptionAttributes.exception_stacktrace(), + ExceptionAttributes.exception_type() + ] == Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) end test "handles exceptions during Phoenix LiveView handle_event" do - OpentelemetryPhoenix.setup() + OpentelemetryPhoenix.setup(adapter: :cowboy2) :telemetry.execute( [:phoenix, :live_view, :handle_event, :start], @@ -420,36 +186,15 @@ defmodule OpentelemetryPhoenixTest do [ event( - name: "exception", + name: :exception, attributes: event_attributes ) ] = :otel_events.list(events) - assert [:"exception.message", :"exception.stacktrace", :"exception.type"] == - Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) - end - - defp x_forwarded_for_request(x_forwarded_for) do - meta = Meta.endpoint_start() - - meta = %{ - meta - | conn: %{ - meta.conn - | req_headers: [{"x-forwarded-for", x_forwarded_for} | meta.conn.req_headers] - } - } - - :telemetry.execute( - [:phoenix, :endpoint, :start], - %{system_time: System.system_time()}, - meta - ) - - :telemetry.execute( - [:phoenix, :endpoint, :stop], - %{duration: 444}, - Meta.endpoint_stop() - ) + assert [ + ExceptionAttributes.exception_message(), + ExceptionAttributes.exception_stacktrace(), + ExceptionAttributes.exception_type() + ] == Enum.sort(Map.keys(:otel_attributes.map(event_attributes))) end end diff --git a/instrumentation/opentelemetry_phoenix/test/support/endpoint_helper.exs b/instrumentation/opentelemetry_phoenix/test/support/endpoint_helper.exs new file mode 100644 index 00000000..bd977fea --- /dev/null +++ b/instrumentation/opentelemetry_phoenix/test/support/endpoint_helper.exs @@ -0,0 +1,30 @@ +defmodule Phoenix.Integration.EndpointHelper do + # From Phoenix repo + # https://github.com/phoenixframework/phoenix/blob/main/test/support/endpoint_helper.exs + + @moduledoc """ + Utility functions for integration testing endpoints. + """ + + @doc """ + Finds `n` unused network port numbers. + """ + def get_unused_port_numbers(n) when is_integer(n) and n > 1 do + 1..n + # Open up `n` sockets at the same time, so we don't get + # duplicate port numbers + |> Enum.map(&listen_on_os_assigned_port/1) + |> Enum.map(&get_port_number_and_close/1) + end + + defp listen_on_os_assigned_port(_) do + {:ok, socket} = :gen_tcp.listen(0, []) + socket + end + + defp get_port_number_and_close(socket) do + {:ok, port_number} = :inet.port(socket) + :gen_tcp.close(socket) + port_number + end +end diff --git a/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex b/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex deleted file mode 100644 index 3d1316d5..00000000 --- a/instrumentation/opentelemetry_phoenix/test/support/phoenix_meta.ex +++ /dev/null @@ -1,1020 +0,0 @@ -defmodule PhoenixMeta do - def router_dispatch_exception(:plug_wrapper) do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64921}, - pid: "", - port: 4000, - qs: "", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"user_id" => "123"}, - path_info: ["exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :plug_session_fetch => fn -> :ok end - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - error: %Plug.Conn.WrapperError{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64921}, - pid: "", - port: 4000, - qs: "", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123", "exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_action => :code_exception, - :phoenix_controller => MyStoreWeb.PageController, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_flash => %{}, - :phoenix_format => "html", - :phoenix_layout => {MyStoreWeb.LayoutView, :app}, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :phoenix_view => MyStoreWeb.PageView, - :plug_session => %{}, - :plug_session_fetch => :done - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"}, - {"x-frame-options", "SAMEORIGIN"}, - {"x-xss-protection", "1; mode=block"}, - {"x-content-type-options", "nosniff"}, - {"x-download-options", "noopen"}, - {"x-permitted-cross-domain-policies", "none"}, - {"cross-origin-window-policy", "deny"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - kind: :error, - reason: :badarith, - stack: [ - {MyStoreWeb.PageController, :code_exception, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 9]}, - {MyStoreWeb.PageController, :action, 2, [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {Phoenix.Router, :__call__, 2, [file: ~c"lib/phoenix/router.ex", line: 352]}, - {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: ~c"lib/plug/debugger.ex", line: 132]}, - {MyStoreWeb.Endpoint, :call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: ~c"lib/phoenix/endpoint/cowboy2_handler.ex", line: 65]}, - {:cowboy_handler, :execute, 2, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl", - line: 37 - ]}, - {:cowboy_stream_h, :execute, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 300 - ]}, - {:cowboy_stream_h, :request_process, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 291 - ]}, - {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 226]} - ] - }, - kind: :error, - reason: %Plug.Conn.WrapperError{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123", "exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_action => :code_exception, - :phoenix_controller => MyStoreWeb.PageController, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_flash => %{}, - :phoenix_format => "html", - :phoenix_layout => {MyStoreWeb.LayoutView, :app}, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :phoenix_view => MyStoreWeb.PageView, - :plug_session => %{}, - :plug_session_fetch => :done - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"}, - {"x-frame-options", "SAMEORIGIN"}, - {"x-xss-protection", "1; mode=block"}, - {"x-content-type-options", "nosniff"}, - {"x-download-options", "noopen"}, - {"x-permitted-cross-domain-policies", "none"}, - {"cross-origin-window-policy", "deny"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - kind: :error, - reason: :badarith, - stack: [ - {MyStoreWeb.PageController, :code_exception, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 9]}, - {MyStoreWeb.PageController, :action, 2, [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {Phoenix.Router, :__call__, 2, [file: ~c"lib/phoenix/router.ex", line: 352]}, - {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: ~c"lib/plug/debugger.ex", line: 132]}, - {MyStoreWeb.Endpoint, :call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: ~c"lib/phoenix/endpoint/cowboy2_handler.ex", line: 65]}, - {:cowboy_handler, :execute, 2, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl", - line: 37 - ]}, - {:cowboy_stream_h, :execute, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 300 - ]}, - {:cowboy_stream_h, :request_process, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 291 - ]}, - {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 226]} - ] - }, - stacktrace: [ - {MyStoreWeb.PageController, :code_exception, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 9]}, - {MyStoreWeb.PageController, :action, 2, [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {MyStoreWeb.PageController, :phoenix_controller_pipeline, 2, - [file: ~c"lib/my_store_web/controllers/page_controller.ex", line: 1]}, - {Phoenix.Router, :__call__, 2, [file: ~c"lib/phoenix/router.ex", line: 352]}, - {MyStoreWeb.Endpoint, :plug_builder_call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {MyStoreWeb.Endpoint, :"call (overridable 3)", 2, [file: ~c"lib/plug/debugger.ex", line: 132]}, - {MyStoreWeb.Endpoint, :call, 2, [file: ~c"lib/my_store_web/endpoint.ex", line: 1]}, - {Phoenix.Endpoint.Cowboy2Handler, :init, 4, [file: ~c"lib/phoenix/endpoint/cowboy2_handler.ex", line: 65]}, - {:cowboy_handler, :execute, 2, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_handler.erl", - line: 37 - ]}, - {:cowboy_stream_h, :execute, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 300 - ]}, - {:cowboy_stream_h, :request_process, 3, - [ - file: ~c"/Users/bryan/dev/opentelemetry_phoenix/test/support/my_store/deps/cowboy/src/cowboy_stream_h.erl", - line: 291 - ]}, - {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 226]} - ] - } - end - - def router_dispatch_exception(:normal) do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64921}, - pid: "", - port: 4000, - qs: "", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"user_id" => "123"}, - path_info: ["exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :plug_session_fetch => fn -> :ok end - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdJBuZy-nj1FHgAAAaB"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - kind: :error, - reason: { - :badkey, - :name, - %{ - username: "rick" - } - }, - stacktrace: [ - {MyStore.Users, :sort_by_name, 2, [file: ~c"lib/my_store/users.ex", line: 159]}, - {Enum, :"-to_sort_fun/1-fun-0-", 3, [file: ~c"lib/enum.ex", line: 2542]}, - {:lists, :sort, 2, [file: ~c"lists.erl", line: 969]} - ] - } - end - - def endpoint_stop do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 2, - version: :"HTTP/1.1" - }}, - assigns: %{layout: {MyStoreWeb.LayoutView, "app.html"}}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_action => :user, - :phoenix_controller => MyStoreWeb.PageController, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_flash => %{}, - :phoenix_format => "html", - :phoenix_layout => {MyStoreWeb.LayoutView, :app}, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :phoenix_template => "index.html", - :phoenix_view => MyStoreWeb.PageView, - :plug_session => %{}, - :plug_session_fetch => :done - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123", - resp_body: [ - "\n\n \n \n \n \n MyStore · Phoenix Framework\n \n \n \n \n
", - " \n \n\n" - ], - resp_cookies: %{}, - resp_headers: [ - {"content-type", "text/html; charset=utf-8"}, - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"}, - {"x-frame-options", "SAMEORIGIN"}, - {"x-xss-protection", "1; mode=block"}, - {"x-content-type-options", "nosniff"}, - {"x-download-options", "noopen"}, - {"x-permitted-cross-domain-policies", "none"}, - {"cross-origin-window-policy", "deny"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :set, - status: 200 - } - } - end - - def endpoint_stop(:exception) do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123", "exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_action => :code_exception, - :phoenix_controller => MyStoreWeb.PageController, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_flash => %{}, - :phoenix_format => "html", - :phoenix_layout => {MyStoreWeb.LayoutView, :app}, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :phoenix_view => MyStoreWeb.PageView, - :plug_session => %{}, - :plug_session_fetch => :done - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: - "\n\n\n \n ArithmeticError at GET /users/123/exception\n \n...", - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"}, - {"x-frame-options", "SAMEORIGIN"}, - {"x-xss-protection", "1; mode=block"}, - {"x-content-type-options", "nosniff"}, - {"x-download-options", "noopen"}, - {"x-permitted-cross-domain-policies", "none"}, - {"cross-origin-window-policy", "deny"}, - {"content-type", "text/html; charset=utf-8"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :set, - status: 500 - } - } - end - - def endpoint_start do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 2, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %Plug.Conn.Unfetched{aspect: :body_params}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1"}, - path_info: ["users", "123"], - path_params: %{}, - port: 4000, - private: %{ - phoenix_endpoint: MyStoreWeb.Endpoint, - phoenix_request_logger: {"request_logger", "request_logger"} - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - } - } - end - - def endpoint_start(:exception) do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %Plug.Conn.Unfetched{aspect: :body_params}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1"}, - path_info: ["users", "123", "exception"], - path_params: %{}, - port: 4000, - private: %{ - phoenix_endpoint: MyStoreWeb.Endpoint, - phoenix_request_logger: {"request_logger", "request_logger"} - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - } - } - end - - def router_dispatch_start do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 2, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :plug_session_fetch => fn -> :ok end - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdyKN4aQSWR3BMAAAAI"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - log: :debug, - path_params: %{"user_id" => "123"}, - pipe_through: [:browser], - plug: MyStoreWeb.PageController, - plug_opts: :user, - route: "/users/:user_id" - } - end - - def router_dispatch_start(:exception) do - %{ - conn: %Plug.Conn{ - adapter: - {Plug.Cowboy.Conn, - %{ - bindings: %{}, - body_length: 0, - cert: :undefined, - has_body: false, - headers: %{ - "accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "accept-encoding" => "gzip, deflate", - "accept-language" => "en-US,en;q=0.5", - "cache-control" => "max-age=0", - "connection" => "keep-alive", - "host" => "localhost:4000", - "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", - "tracestate" => "congo=t61rcWkgMzE", - "upgrade-insecure-requests" => "1", - "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0" - }, - host: "localhost", - host_info: :undefined, - method: "GET", - path: "/users/123/exception", - path_info: :undefined, - peer: {{10, 211, 55, 2}, 64291}, - pid: "", - port: 4000, - qs: "page=1", - ref: MyStoreWeb.Endpoint.HTTP, - scheme: "http", - sock: {{10, 211, 55, 2}, 4000}, - streamid: 1, - version: :"HTTP/1.1" - }}, - assigns: %{}, - body_params: %{}, - cookies: %{}, - halted: false, - host: "localhost", - method: "GET", - owner: "", - params: %{"page" => "1", "user_id" => "123"}, - path_info: ["users", "123", "exception"], - path_params: %{"user_id" => "123"}, - port: 4000, - private: %{ - MyStoreWeb.Router => {[], %{}}, - :phoenix_endpoint => MyStoreWeb.Endpoint, - :phoenix_request_logger => {"request_logger", "request_logger"}, - :phoenix_router => MyStoreWeb.Router, - :plug_session_fetch => fn -> :ok end - }, - query_params: %{"page" => "1"}, - query_string: "page=1", - remote_ip: {10, 211, 55, 2}, - req_cookies: %{}, - req_headers: [ - {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, - {"accept-encoding", "gzip, deflate"}, - {"accept-language", "en-US,en;q=0.5"}, - {"cache-control", "max-age=0"}, - {"connection", "keep-alive"}, - {"host", "localhost:4000"}, - {"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}, - {"tracestate", "congo=t61rcWkgMzE"}, - {"upgrade-insecure-requests", "1"}, - {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0"} - ], - request_path: "/users/123/exception", - resp_body: nil, - resp_cookies: %{}, - resp_headers: [ - {"cache-control", "max-age=0, private, must-revalidate"}, - {"x-request-id", "FjdxbmwZYwjZpIQAAAAJ"} - ], - scheme: :http, - script_name: [], - secret_key_base: "", - state: :unset, - status: nil - }, - log: :debug, - path_params: %{"user_id" => "123"}, - pipe_through: [:browser], - plug: MyStoreWeb.PageController, - plug_opts: :code_exception, - route: "/users/:user_id/exception" - } - end -end diff --git a/instrumentation/opentelemetry_phoenix/test/test_helper.exs b/instrumentation/opentelemetry_phoenix/test/test_helper.exs index 2ec33292..869559e7 100644 --- a/instrumentation/opentelemetry_phoenix/test/test_helper.exs +++ b/instrumentation/opentelemetry_phoenix/test/test_helper.exs @@ -1,2 +1 @@ ExUnit.start() -Code.put_compiler_option(:warnings_as_errors, true) diff --git a/instrumentation/opentelemetry_redix/CHANGELOG.md b/instrumentation/opentelemetry_redix/CHANGELOG.md index c1e928c5..868134b1 100644 --- a/instrumentation/opentelemetry_redix/CHANGELOG.md +++ b/instrumentation/opentelemetry_redix/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.0 + +### Changed + +* Update OpenTelemetry API and Semantic Conventions + ## 0.1.1 ### Bug fixes diff --git a/instrumentation/opentelemetry_redix/README.md b/instrumentation/opentelemetry_redix/README.md index b4f2f847..755b13a9 100644 --- a/instrumentation/opentelemetry_redix/README.md +++ b/instrumentation/opentelemetry_redix/README.md @@ -36,7 +36,7 @@ dependencies in `mix.exs`: ```elixir def deps do [ - {:opentelemetry_redix, "~> 0.1"} + {:opentelemetry_redix, "~> 0.2"} ] end ``` @@ -51,4 +51,3 @@ dependencies in `mix.exs`: 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_redix](https://hexdocs.pm/opentelemetry_redix). - diff --git a/instrumentation/opentelemetry_redix/mix.exs b/instrumentation/opentelemetry_redix/mix.exs index c02988cc..1798d66a 100644 --- a/instrumentation/opentelemetry_redix/mix.exs +++ b/instrumentation/opentelemetry_redix/mix.exs @@ -1,7 +1,7 @@ defmodule OpentelemetryRedix.MixProject do use Mix.Project - @version "0.1.1" + @version "0.2.0" def project do [ @@ -55,15 +55,15 @@ defmodule OpentelemetryRedix.MixProject do defp deps do [ - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.34.0", only: [:dev], runtime: false}, - {:opentelemetry, "~> 1.0", only: [:dev, :test]}, - {:opentelemetry_api, "~> 1.0"}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.38", only: [:dev], runtime: false}, + {:opentelemetry, "~> 1.5", only: [:dev, :test]}, + {:opentelemetry_api, "~> 1.4"}, {:opentelemetry_process_propagator, "~> 0.3"}, - {:opentelemetry_semantic_conventions, "~> 0.2"}, - {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, - {:redix, "~> 1.0", only: [:dev, :test]}, - {:telemetry, "~> 0.4 or ~> 1.0"} + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:opentelemetry_exporter, "~> 1.8", only: [:dev, :test]}, + {:redix, "~> 1.5", only: [:dev, :test]}, + {:telemetry, "~> 1.0"} ] end end diff --git a/instrumentation/opentelemetry_redix/mix.lock b/instrumentation/opentelemetry_redix/mix.lock index 9e54892f..3569d130 100644 --- a/instrumentation/opentelemetry_redix/mix.lock +++ b/instrumentation/opentelemetry_redix/mix.lock @@ -2,25 +2,25 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "redix": {:hex, :redix, "1.3.0", "f4121163ff9d73bf72157539ff23b13e38422284520bb58c05e014b19d6f0577", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "60d483d320c77329c8cbd3df73007e51b23f3fae75b7693bc31120d83ab26131"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "redix": {:hex, :redix, "1.5.2", "ab854435a663f01ce7b7847f42f5da067eea7a3a10c0a9d560fa52038fd7ab48", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78538d184231a5d6912f20567d76a49d1be7d3fca0e1aaaa20f4df8e1142dcb8"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/instrumentation/opentelemetry_req/CHANGELOG.md b/instrumentation/opentelemetry_req/CHANGELOG.md index 4c91e678..adced421 100644 --- a/instrumentation/opentelemetry_req/CHANGELOG.md +++ b/instrumentation/opentelemetry_req/CHANGELOG.md @@ -1,32 +1,43 @@ # Changelog +### 1.0.0-rc.1 + +### Features + +- OpenTelemetry v1.27 support + +### Breaking Changes + +- Various HTTP Semantic Convention changes are included. One major change + regards span naming. This may affect your observability tools when keying + on span names. The key change there is the HTTP method is now a prefix, e.g. "GET /users/:user_id" + ## 0.2.0 ### Fixes -* Add support for Req v0.4 +- Add support for Req v0.4 -* Change http.url to follow [OpenTelemetry http spec](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#http-client). +- Change http.url to follow [OpenTelemetry http spec](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#http-client). -* Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` +- Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` -* Strip user credentials passed via URL +- Strip user credentials passed via URL ## 0.1.2 ### Fixes -* Fix ctx not being set back to parent upon completion +- Fix ctx not being set back to parent upon completion ## 0.1.1 ### Fixes -* Fix client span to be the ctx injected to headers +- Fix client span to be the ctx injected to headers ## 0.1.0 ### Features -* Initial release - +- Initial release diff --git a/instrumentation/opentelemetry_req/README.md b/instrumentation/opentelemetry_req/README.md index e9912bec..ad839846 100644 --- a/instrumentation/opentelemetry_req/README.md +++ b/instrumentation/opentelemetry_req/README.md @@ -5,15 +5,14 @@ [Req](https://hex.pm/packages/req) plugin for OpenTelemetry instrumentation and propagation. -See [Docs](https://hex.pm/packages/opentelemetry_req) for usage instructions. +See [Docs](https://hexdocs.pm/opentelemetry_req) for usage instructions. ## Installation ```elixir def deps do [ - {:opentelemetry_req, "~> 0.2.0"} + {:opentelemetry_req, "~> 1.0.0-beta.1"} ] end ``` - diff --git a/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex b/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex index f7328520..bca4bd17 100644 --- a/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex +++ b/instrumentation/opentelemetry_req/lib/opentelemetry_req.ex @@ -1,26 +1,109 @@ defmodule OpentelemetryReq do + alias OpenTelemetry.Ctx + + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.NetworkAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpenTelemetry.SemConv.UserAgentAttributes + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + alias OpenTelemetry.SemConv.Incubating.URLAttributes + + alias OpenTelemetry.Tracer + alias OpenTelemetry.SemanticConventions.Trace + require Trace + require Tracer + require Logger + + opt_ins = [ + HTTPAttributes.http_request_body_size(), + HTTPAttributes.http_response_body_size(), + NetworkAttributes.network_transport(), + URLAttributes.url_scheme(), + URLAttributes.url_template(), + UserAgentAttributes.user_agent_original() + ] + + @options_schema NimbleOptions.new!( + opt_in_attrs: [ + type: {:list, {:in, opt_ins}}, + default: [], + type_spec: quote(do: opt_in_attrs()), + doc: """ + Opt-in and experimental attributes. Use semantic conventions library to ensure compatibility, e.g. `[{HTTPAttributes.http_request_body_size(), true}]` + + #{Enum.map_join(opt_ins, "\n\n", &" * `#{inspect(&1)}`")} + """ + ], + propagate_trace_headers: [ + type: :boolean, + default: false, + doc: "Trace headers will be propagated" + ], + request_header_attrs: [ + type: {:list, :string}, + default: [], + doc: "List of request headers to add as attributes. (lowercase)" + ], + response_header_attrs: [ + type: {:list, :string}, + default: [], + doc: "List of response headers to add as attributes. (lowercase)" + ], + span_name: [ + type: {:or, [:atom, nil, :string]}, + default: nil, + doc: "User defined span name override" + ] + ) + + @typedoc "Use semantic conventions library to ensure compatibility, e.g. `HTTPAttributes.http_request_body_size()`" + @type opt_in_attr() :: + unquote(HTTPAttributes.http_request_body_size()) + | unquote(HTTPAttributes.http_response_body_size()) + | unquote(NetworkAttributes.network_transport()) + | unquote(URLAttributes.url_scheme()) + | unquote(URLAttributes.url_template()) + | unquote(UserAgentAttributes.user_agent_original()) + + @type opt_in_attrs() :: [opt_in_attr()] + + @type options() :: [unquote(NimbleOptions.option_typespec(@options_schema))] + @moduledoc """ - Wraps the request in an opentelemetry span. Span names must be parameterized, 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. + Wraps a Req request in an opentelemetry span. Spans are not created until the request is completed or errored. - ## Request Options + ## Req Path Params + + It is strongly encouraged to use the [`put_path_params` step](https://hexdocs.pm/req/Req.Steps.html#put_path_params/1) option. + This allows the span name to include the `{target}` portion of the span name described in the + [HTTP Span Name guidelines](https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name). - * `: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` + > #### Requirements {: .info} + > + > * `path_params` option should be set along with a templated path. Only `:colon` style is supported + > * `URLAttributes.url_template()` opt-in attribute must be set to `true` - ### Example with path_params + ## Semantic Conventions + All available required and recommended [Client HTTP Span](https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client) semantic conventions are implemented. + Supported opt-in and experimental attributes can be configured using the `opt_in_attrs` option. + + ## Options + + ### Opt-in Semantic Convention Attributes + + Otel SemConv requires users to explicitly opt in for any attribute with a + requirement level of `opt-in` or `experimental`. To ensure compatibility, always use the + SemConv attribute. + + Example: ``` client = Req.new() |> OpentelemetryReq.attach( - base_url: "http://localhost:4000", - propagate_trace_ctx: true + opt_in_attrs: [SemConv.URLAttributes.url_template()] ) client @@ -30,39 +113,76 @@ defmodule OpentelemetryReq do ) ``` - ### Example without path_params + Request and response header attributes are opt-in and can be set with the + `request_header_attrs` and `response_header_attrs` options. Values should be lower-case. + + ### Trace Header Propagation + By default, trace propagation headers are not injected to requests. There are + two options available to propagate trace headers: + + * set `propagate_trace_headers` option to `true` when attaching or in the call + * manually use `:otel_propagator_text_map.inject/1` + + Example: ``` client = Req.new() - |> OpentelemetryReq.attach( - base_url: "http://localhost:4000", - propagate_trace_ctx: true, - no_path_params: true - ) + |> OpentelemetryReq.attach(propagate_trace_headers: true) - client - |> Req.get( - url: "/api/users" - ) + # or + + client = + Req.new() + |> OpentelemetryReq.attach() + + Req.get(client, "/", propagate_trace_headers: true) ``` - If you don't set `path_params` the request will raise. - """ - alias OpenTelemetry.Tracer - alias OpenTelemetry.SemanticConventions.Trace - require Trace - require Tracer - require Logger + ### Span Name Override + + The span name can be overridden by setting the `span_name` option in the call. + + Example: + ``` + client = + Req.new() + |> OpentelemetryReq.attach() + + Req.get(client, "/", span_name: "custom") + ``` + > #### Option Precedence {: .info} + > + > Options passed in a request take precedence over those passed in `attach/1`. + > + """ + + @spec attach(Req.Request.t(), options()) :: Req.Request.t() def attach(%Req.Request{} = request, options \\ []) do + config = + options + |> NimbleOptions.validate!(@options_schema) + |> Enum.into(%{}) + |> then(fn config -> + if Enum.member?(config.opt_in_attrs, URLAttributes.url_template()) do + Map.put(config, :url_template_enabled, true) + else + Map.put(config, :url_template_enabled, false) + end + end) + request - |> Req.Request.register_options([:span_name, :no_path_params, :propagate_trace_ctx]) - |> Req.Request.merge_options(options) + |> Req.Request.put_private(:otel, config) + |> Req.Request.register_options([ + :propagate_trace_headers, + :request_header_attrs, + :response_header_attrs, + :span_name + ]) |> 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 + put_trace_headers: &propagate_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) @@ -86,14 +206,31 @@ defmodule OpentelemetryReq do end defp end_span({request, %Req.Response{} = response}) do - attrs = - Map.put(%{}, Trace.http_status_code(), response.status) - |> maybe_append_resp_content_length(response) + config = Req.Request.get_private(request, :otel) - Tracer.set_attributes(attrs) + opt_in = + %{ + HTTPAttributes.http_response_body_size() => extract_response_body_size(response) + } + |> Map.take(config.opt_in_attrs) if response.status >= 400 do - OpenTelemetry.Tracer.set_status(OpenTelemetry.status(:error, "")) + Tracer.set_status(OpenTelemetry.status(:error, "")) + + %{ + HTTPAttributes.http_response_status_code() => response.status, + ErrorAttributes.error_type() => to_string(response.status) + } + |> set_resp_header_attrs(response, request.options) + |> Map.merge(opt_in) + |> Tracer.set_attributes() + else + %{ + HTTPAttributes.http_response_status_code() => response.status + } + |> set_resp_header_attrs(response, request.options) + |> Map.merge(opt_in) + |> Tracer.set_attributes() end OpenTelemetry.Tracer.end_span() @@ -105,12 +242,16 @@ defmodule OpentelemetryReq do end defp end_errored_span({request, exception}) do - OpenTelemetry.Tracer.set_status(OpenTelemetry.status(:error, format_exception(exception))) + Tracer.set_status(OpenTelemetry.status(:error, format_exception(exception))) - OpenTelemetry.Tracer.end_span() + Tracer.set_attributes(%{ + ErrorAttributes.error_type() => exception.__struct__ + }) + + Tracer.end_span() Process.delete(:otel_parent_ctx) - |> OpenTelemetry.Ctx.attach() + |> Ctx.attach() {request, exception} end @@ -124,11 +265,21 @@ defmodule OpentelemetryReq do defp span_name(request) do case request.options[:span_name] do nil -> - method = http_method(request.method) - - case Req.Request.get_private(request, :path_params_template) do - nil -> "HTTP #{method}" - params_template -> "#{params_template}" + config = Req.Request.get_private(request, :otel) + + if config.span_name do + config.span_name + else + method = parse_method(request.method) + + if config.url_template_enabled do + case Req.Request.get_private(request, :path_params_template) do + nil -> method + params_template -> "#{method} #{params_template}" + end + else + method + end end span_name -> @@ -139,16 +290,29 @@ defmodule OpentelemetryReq do defp build_req_attrs(request) do uri = request.url url = sanitize_url(uri) + config = Req.Request.get_private(request, :otel) + + opt_in = %{ + HTTPAttributes.http_request_body_size() => extract_request_body_size(request), + NetworkAttributes.network_transport() => :tcp, + URLAttributes.url_scheme() => extract_scheme(uri), + URLAttributes.url_template() => extract_url_template(request), + UserAgentAttributes.user_agent_original() => extract_user_agent(request) + } %{ - 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 + HTTPAttributes.http_request_method() => parse_method(request.method), + ServerAttributes.server_address() => uri.host, + ServerAttributes.server_port() => extract_port(uri), + URLAttributes.url_full() => url } - |> maybe_append_req_content_length(request) - |> maybe_append_retry_count(request) + |> set_retry_count(request) + |> set_req_header_attrs(request) + |> then(fn attrs -> + opt_in + |> Map.take(config.opt_in_attrs) + |> Map.merge(attrs) + end) end defp sanitize_url(uri) do @@ -156,81 +320,124 @@ defmodule OpentelemetryReq do |> URI.to_string() end - defp maybe_append_req_content_length(attrs, req) do - case Req.Request.get_header(req, "content-length") do + defp extract_port(%{port: port}) when is_integer(port), do: port + + defp extract_port(%{scheme: scheme}) do + case scheme do + nil -> 80 + "http" -> 80 + "https" -> 443 + _ -> 80 + end + end + + defp extract_scheme(%{scheme: scheme}) do + case scheme do + nil -> :http + "http" -> :http + "https" -> :https + _ -> :http + end + end + + defp extract_url_template(request) do + Req.Request.get_private(request, :path_params_template, "") + end + + defp extract_user_agent(request) do + case Req.Request.get_header(request, "user-agent") do [] -> - attrs + "" - [length] -> - Map.put(attrs, Trace.http_request_content_length(), length) + [user_agent | _] -> + user_agent end end - defp maybe_append_resp_content_length(attrs, req) do + defp extract_response_body_size(req) do case Req.Response.get_header(req, "content-length") do [] -> - attrs + 0 + + [length_str | _] when is_binary(length_str) -> + # Req sets this as a string but should be an integer + # https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length + # https://opentelemetry.io/docs/specs/semconv/attributes-registry/http + String.to_integer(length_str) + end + end + + defp extract_request_body_size(req) do + case Req.Request.get_header(req, "content-length") do + [] -> + 0 - [length] -> - Map.put(attrs, Trace.http_response_content_length(), length) + [length_str | _] when is_binary(length_str) -> + # Req sets this as a string but should be an integer + # https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length + # https://opentelemetry.io/docs/specs/semconv/attributes-registry/http + String.to_integer(length_str) end end - defp maybe_append_retry_count(attrs, req) do + defp set_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) + Map.put(attrs, HTTPAttributes.http_request_resend_count(), retry_count) else attrs end end - defp http_method(method) do + defp parse_method(method) do case method do - :get -> :GET - :head -> :HEAD - :post -> :POST - :patch -> :PATCH - :put -> :PUT - :delete -> :DELETE - :connect -> :CONNECT - :options -> :OPTIONS - :trace -> :TRACE + :connect -> HTTPAttributes.http_request_method_values().connect + :delete -> HTTPAttributes.http_request_method_values().delete + :get -> HTTPAttributes.http_request_method_values().get + :head -> HTTPAttributes.http_request_method_values().head + :options -> HTTPAttributes.http_request_method_values().options + :patch -> HTTPAttributes.http_request_method_values().patch + :post -> HTTPAttributes.http_request_method_values().post + :put -> HTTPAttributes.http_request_method_values().put + :trace -> HTTPAttributes.http_request_method_values().trace end end - defp maybe_put_trace_headers(request) do - if request.options[:propagate_trace_ctx] do - propagator = :opentelemetry.get_text_map_injector() - headers_to_inject = :otel_propagator_text_map.inject(propagator, [], &[{&1, &2} | &3]) + defp propagate_trace_headers(request) do + should_inject = + Req.Request.get_option( + request, + :propagate_trace_headers, + Req.Request.get_private(request, :otel)[:propagate_trace_headers] + ) - Enum.reduce(headers_to_inject, request, fn {name, value}, acc -> - Req.Request.put_header(acc, name, value) - end) + if should_inject do + Req.Request.put_headers(request, :otel_propagator_text_map.inject([])) else request end end - defp require_path_params_option(request) do - if !request.options[:no_path_params] and !request.options[:path_params] do - {Req.Request.halt(request), __MODULE__.PathParamsOptionError.new()} - else - request - end + defp set_req_header_attrs(attrs, req) do + Map.merge( + attrs, + :otel_http.extract_headers_attributes( + :request, + req.headers, + Map.get(req.options, :request_header_attrs, []) + ) + ) end - defmodule PathParamsOptionError do - defexception [:message] - - def new do - %__MODULE__{} - end - - @impl true - def message(_) do - ":path_params option must be set" - end + defp set_resp_header_attrs(attrs, resp, options) do + Map.merge( + attrs, + :otel_http.extract_headers_attributes( + :response, + resp.headers, + Map.get(options, :response_header_attrs, []) + ) + ) end end diff --git a/instrumentation/opentelemetry_req/mix.exs b/instrumentation/opentelemetry_req/mix.exs index 779d70d9..a72b45d9 100644 --- a/instrumentation/opentelemetry_req/mix.exs +++ b/instrumentation/opentelemetry_req/mix.exs @@ -1,14 +1,14 @@ defmodule OpentelemetryReq.MixProject do use Mix.Project - @version "0.2.0" + @version "1.0.0" def project do [ app: :opentelemetry_req, description: description(), version: @version, - elixir: "~> 1.11", + elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), name: "Opentelemetry Req", @@ -59,11 +59,17 @@ defmodule OpentelemetryReq.MixProject do defp deps do [ {:jason, "~> 1.3"}, - {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry_semantic_conventions, "~> 0.2"}, + {:nimble_options, "~> 1.1"}, + {:opentelemetry_api, "~> 1.4"}, + {:opentelemetry_semantic_conventions, "~> 1.27"}, + {:otel_http, "~> 0.2"}, {:req, ">= 0.3.5"}, - {:ex_doc, "~> 0.34", only: [:dev, :test]}, - {:opentelemetry, "~> 1.0", only: :test} + {:ex_doc, "~> 0.38", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.8", only: [:test]}, + {:opentelemetry, "~> 1.5", only: :test}, + {:bypass, "~> 2.1", only: :test}, + {:plug, ">= 1.15.0", only: [:test]}, + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false} ] end end diff --git a/instrumentation/opentelemetry_req/mix.lock b/instrumentation/opentelemetry_req/mix.lock index d1c220c2..73ec3ce7 100644 --- a/instrumentation/opentelemetry_req/mix.lock +++ b/instrumentation/opentelemetry_req/mix.lock @@ -1,21 +1,40 @@ %{ - "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, - "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [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 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [: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", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, - "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "req": {:hex, :req, "0.4.8", "2b754a3925ddbf4ad78c56f30208ced6aefe111a7ea07fb56c23dccc13eb87ae", [: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", "7146e51d52593bb7f20d00b5308a5d7d17d663d6e85cd071452b613a8277100c"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"}, + "otel_http": {:hex, :otel_http, "0.2.0", "b17385986c7f1b862f5d577f72614ecaa29de40392b7618869999326b9a61d8a", [:rebar3], [], "hexpm", "f2beadf922c8cfeb0965488dd736c95cc6ea8b9efce89466b3904d317d7cc717"}, + "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [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", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.24.0", "d00e2887551ff8cdae4d0340d90d9fcbc4943c7b5f49d32ed4bc23aff4db9a44", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "90b25a58ee433d91c17f036d4d354bf8859a089bfda60e68a86f8eecae45ef1b"}, } diff --git a/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs b/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs index 0ba8b0e6..6ce7c8f3 100644 --- a/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs +++ b/instrumentation/opentelemetry_req/test/opentelemetry_req_test.exs @@ -1,6 +1,14 @@ defmodule OpentelemetryReqTest do use ExUnit.Case, async: true doctest OpentelemetryReq + + alias OpenTelemetry.SemConv.ErrorAttributes + alias OpenTelemetry.SemConv.NetworkAttributes + alias OpenTelemetry.SemConv.ServerAttributes + alias OpenTelemetry.SemConv.UserAgentAttributes + alias OpenTelemetry.SemConv.Incubating.HTTPAttributes + alias OpenTelemetry.SemConv.Incubating.URLAttributes + require Record for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do @@ -17,48 +25,365 @@ defmodule OpentelemetryReqTest do :application.start(:opentelemetry) - req = - Req.new() - |> OpentelemetryReq.attach() + bypass = Bypass.open() + {:ok, bypass: bypass} + end + + defp client(opts \\ []) do + Req.new() + |> OpentelemetryReq.attach(opts) + end + + test "basic request" do + plug = fn conn -> + conn + |> Plug.Conn.put_status(200) + |> Req.Test.json(%{id: 3}) + end + + Req.get!(client(), + plug: plug, + url: "http://localtest:8080/users/:id", + path_params: [id: 3], + params: [a: "b"] + ) + + assert_receive {:span, + span( + name: :GET, + kind: :client, + attributes: span_attrs + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {HTTPAttributes.http_request_method(), :GET}, + {HTTPAttributes.http_response_status_code(), 200}, + {ServerAttributes.server_address(), "localtest"}, + {ServerAttributes.server_port(), 8080}, + {URLAttributes.url_full(), "http://localtest:8080/users/3?a=b"} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{actual}" + end + end + + # with all opt-ins + test "with all other opt-ins and header options", %{bypass: bypass} do + Bypass.expect_once(bypass, "POST", "/users/3", fn conn -> + conn + |> Plug.Conn.put_status(200) + |> Req.Test.json(%{user_id: 3}) + end) + + fields = [a: 1, b: {"2", filename: "b.txt"}] + + client = + client(%{ + opt_in_attrs: [ + HTTPAttributes.http_request_body_size(), + HTTPAttributes.http_response_body_size(), + NetworkAttributes.network_transport(), + URLAttributes.url_scheme(), + URLAttributes.url_template(), + UserAgentAttributes.user_agent_original() + ] + }) + + Req.post!(client, + url: "http://localhost:#{bypass.port}/users/:user_id", + path_params: [user_id: 3], + params: [a: "b"], + headers: %{test_header: "request header"}, + form_multipart: fields, + request_header_attrs: ["test-header", "user-agent"], + response_header_attrs: ["content-type"] + ) + + assert_receive {:span, + span( + name: "POST /users/:user_id", + kind: :client, + attributes: span_attrs + )} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {HTTPAttributes.http_request_method(), :POST}, + {HTTPAttributes.http_response_status_code(), 200}, + {HTTPAttributes.http_request_body_size(), 224}, + {HTTPAttributes.http_response_body_size(), 13}, + {NetworkAttributes.network_transport(), :tcp}, + {String.to_atom("#{HTTPAttributes.http_request_header()}.test-header"), ["request header"]}, + {String.to_atom("#{HTTPAttributes.http_response_header()}.content-type"), + ["application/json; charset=utf-8"]}, + {ServerAttributes.server_address(), "localhost"}, + {ServerAttributes.server_port(), bypass.port}, + {URLAttributes.url_full(), "http://localhost:#{bypass.port}/users/3?a=b"}, + {URLAttributes.url_scheme(), :http}, + {URLAttributes.url_template(), "/users/:user_id"} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{inspect(actual)}" + end + + user_agent = Map.get(attrs, UserAgentAttributes.user_agent_original()) + assert String.starts_with?(user_agent, "req/") + end + + describe "errors" do + test "timeout exception error" do + Req.get(client(), + plug: fn conn -> + Req.Test.transport_error(conn, :timeout) + end, + retry: false, + url: "/" + ) + + expected_status = OpenTelemetry.status(:error, "timeout") + + assert_receive {:span, span(attributes: span_attrs, status: ^expected_status)} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ErrorAttributes.error_type(), Req.TransportError} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{inspect(actual)}" + end + end + + test "4xx level error", %{bypass: bypass} do + Bypass.expect_once(bypass, "GET", "/", fn conn -> + conn + |> Plug.Conn.put_status(404) + |> Req.Test.text("not found") + end) + + Req.get(client(), + retry: false, + url: "http://localhost:#{bypass.port}" + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, span(attributes: span_attrs, status: ^expected_status)} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ErrorAttributes.error_type(), "404"} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{inspect(actual)}" + end + end + + test "5xx level error", %{bypass: bypass} do + Bypass.expect_once(bypass, "GET", "/", fn conn -> + conn + |> Plug.Conn.put_status(500) + |> Req.Test.text("internal server error") + end) + + Req.get(client(), + retry: false, + url: "http://localhost:#{bypass.port}" + ) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, span(attributes: span_attrs, status: ^expected_status)} + + attrs = :otel_attributes.map(span_attrs) + + expected_attrs = [ + {ErrorAttributes.error_type(), "500"} + ] + + for {attr, expected} <- expected_attrs do + actual = Map.get(attrs, attr) + assert expected == actual, "#{attr} expected #{expected} got #{inspect(actual)}" + end + end + end + + # exception test + def ok_resp(conn) do + conn |> Req.Test.text("ok") + end + + describe "span name" do + test "with no path params" do + Req.get!(client(), + plug: &ok_resp/1, + url: "/" + ) + + assert_receive {:span, span(name: :GET)} + end + + test "with path params but template attr not set" do + Req.get!(client(), + plug: &ok_resp/1, + url: "http://localtest:8080/users/:id", + path_params: [id: 3], + params: [a: "b"] + ) + + assert_receive {:span, span(name: :GET)} + end + + test "with path params" do + Req.get!(client(opt_in_attrs: [URLAttributes.url_template()]), + plug: &ok_resp/1, + url: "http://localtest:8080/users/:id", + path_params: [id: 3], + params: [a: "b"] + ) + + assert_receive {:span, span(name: "GET /users/:id")} + end + + test "with span name attach option" do + Req.get!(client(span_name: "test"), + plug: &ok_resp/1, + url: "http://localtest:8080/users/:id", + path_params: [id: 3], + params: [a: "b"] + ) + + assert_receive {:span, span(name: "test")} + end + + test "with span name request option" do + Req.get!(client(span_name: "test"), + plug: &ok_resp/1, + url: "http://localtest:8080/users/:id", + path_params: [id: 3], + params: [a: "b"], + span_name: "overridden" + ) - {:ok, req: req} + assert_receive {:span, span(name: "overridden")} + end end - test "span", %{req: req} do - adapter = fn request -> - assert URI.to_string(request.url) == "/users/3" - {request, Req.Response.new(status: 204)} + describe "ports" do + test "when port present" do + Req.get!(client(), + plug: &ok_resp/1, + url: "http://localtest:8080" + ) + + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + assert 8080 == Map.get(attrs, ServerAttributes.server_port()) + end + + test "when port not set and no scheme" do + Req.get!(client(), + plug: &ok_resp/1, + url: "/ok" + ) + + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + assert 80 == Map.get(attrs, ServerAttributes.server_port()) end - resp = - Req.get!(req, - adapter: adapter, - url: "/users/:id", - path_params: [id: 3] + test "when port not set and http scheme" do + Req.get!(client(), + plug: &ok_resp/1, + url: "http://localtest" ) - assert resp.status == 204 - assert_receive {:span, span(name: "/users/:id")} - refute_receive _ + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + assert 80 == Map.get(attrs, ServerAttributes.server_port()) + end + + test "when port not set and https scheme" do + Req.get!(client(), + plug: &ok_resp/1, + url: "https://localtest" + ) + + assert_receive {:span, span(attributes: span_attrs)} + + attrs = :otel_attributes.map(span_attrs) + assert 443 == Map.get(attrs, ServerAttributes.server_port()) + end end - test "propagate traces", %{req: req} do - adapter = fn request -> - assert [value] = Req.Request.get_header(request, "traceparent") - assert byte_size(value) > 10 - {request, Req.Response.new(status: 204)} + describe "propagation" do + test "off by default" do + plug = fn conn -> + assert [] == Plug.Conn.get_req_header(conn, "traceparent") + + ok_resp(conn) + end + + Req.get!(client(), + plug: plug, + url: "/" + ) + end + + test "enabled in attach" do + plug = fn conn -> + assert 1 == length(Plug.Conn.get_req_header(conn, "traceparent")) + + ok_resp(conn) + end + + Req.get!(client(propagate_trace_headers: true), + plug: plug, + url: "/" + ) end - resp = - Req.get!(req, - adapter: adapter, + test "enabled in request" do + plug = fn conn -> + assert 1 == length(Plug.Conn.get_req_header(conn, "traceparent")) + + ok_resp(conn) + end + + Req.get!(client(), + plug: plug, url: "/", - no_path_params: true, - propagate_trace_ctx: true + propagate_trace_headers: true ) + end + + test "disabled in request" do + plug = fn conn -> + assert 0 == length(Plug.Conn.get_req_header(conn, "traceparent")) + + ok_resp(conn) + end - assert resp.status == 204 - assert_receive {:span, span()} - refute_receive _ + Req.get!(client(propagate_trace_headers: true), + plug: plug, + url: "/", + propagate_trace_headers: false + ) + end end end diff --git a/instrumentation/opentelemetry_tesla/mix.exs b/instrumentation/opentelemetry_tesla/mix.exs index 82f6857d..880fe537 100644 --- a/instrumentation/opentelemetry_tesla/mix.exs +++ b/instrumentation/opentelemetry_tesla/mix.exs @@ -59,7 +59,7 @@ defmodule OpentelemetryTesla.MixProject do {:opentelemetry_telemetry, "~> 1.1"}, {:opentelemetry_semantic_conventions, "~> 0.2"}, {:tesla, "~> 1.4"}, - {:ex_doc, "~> 0.34", only: :dev, runtime: false}, + {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:bypass, "~> 2.1", only: :test}, {:jason, "~> 1.3", only: :test} ] diff --git a/instrumentation/opentelemetry_tesla/mix.lock b/instrumentation/opentelemetry_tesla/mix.lock index eeb5f962..d85b49d6 100644 --- a/instrumentation/opentelemetry_tesla/mix.lock +++ b/instrumentation/opentelemetry_tesla/mix.lock @@ -3,22 +3,22 @@ "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, - "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.1", "4a73bfa29d7780ffe33db345465919cef875034854649c37ac789eb8e8f38b21", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ee43b14e6866123a3ee1344e3c0d3d7591f4537542c2a925fcdbf46249c9b50b"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tesla": {:hex, :tesla, "1.14.3", "b27ba2814cc08b5c4eb5f0245120198542cd023f575c490fa14447ae6763ea8d", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "4f419aa2ab908cff43a117d3b5de99edad8fd54690211cbbdc7d2941c03a1458"}, } diff --git a/instrumentation/opentelemetry_tesla/test/middleware/opentelemetry_tesla_middleware_test.exs b/instrumentation/opentelemetry_tesla/test/middleware/opentelemetry_tesla_middleware_test.exs index 9fbee706..6745ed73 100644 --- a/instrumentation/opentelemetry_tesla/test/middleware/opentelemetry_tesla_middleware_test.exs +++ b/instrumentation/opentelemetry_tesla/test/middleware/opentelemetry_tesla_middleware_test.exs @@ -22,171 +22,109 @@ defmodule Tesla.Middleware.OpenTelemetryTest do :application.start(:opentelemetry) - {:ok, bypass: bypass} + {:ok, bypass: bypass, base_url: endpoint_url(bypass.port)} end describe "span name" do test "uses generic route name when opentelemetry middleware is configured before path params middleware", %{ - bypass: bypass + bypass: bypass, + base_url: base_url } do - defmodule TestClient do - def get(client) do - params = [id: ~c"3"] - - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - Tesla.Middleware.PathParams - ] - - Tesla.client(middleware) - end - end - Bypass.expect_once(bypass, "GET", "/users/3", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + Tesla.Middleware.PathParams + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "3"]]) assert_receive {:span, span(name: "/users/:id", attributes: _attributes)} end test "uses low-cardinality method name when path params middleware is not used", %{ - bypass: bypass + bypass: bypass, + base_url: base_url } do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry - ] - - Tesla.client(middleware) - end - end - Bypass.expect_once(bypass, "GET", "/users/", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(name: "HTTP GET", attributes: _attributes)} end test "uses custom span name when passed in middleware opts", %{ - bypass: bypass + bypass: bypass, + base_url: base_url } do - defmodule TestClient do - def get(client) do - params = [id: ~c"3"] - - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - {Tesla.Middleware.OpenTelemetry, span_name: "POST :my-high-cardinality-url"}, - Tesla.Middleware.PathParams - ] - - Tesla.client(middleware) - end - end - Bypass.expect_once(bypass, "GET", "/users/3", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + {Tesla.Middleware.OpenTelemetry, span_name: "POST :my-high-cardinality-url"}, + Tesla.Middleware.PathParams + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "3"]]) assert_receive {:span, span(name: "POST :my-high-cardinality-url", attributes: _attributes)} end test "uses custom span name function when passed in middleware opts", %{ - bypass: bypass + bypass: bypass, + base_url: base_url } do - defmodule TestClient do - def get(client) do - params = [id: ~c"3"] - - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - {Tesla.Middleware.OpenTelemetry, - span_name: fn env -> - "#{String.upcase(to_string(env.method))} potato" - end}, - Tesla.Middleware.PathParams - ] - - Tesla.client(middleware) - end - end - Bypass.expect_once(bypass, "GET", "/users/3", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + {Tesla.Middleware.OpenTelemetry, + span_name: fn env -> + "#{String.upcase(to_string(env.method))} potato" + end}, + Tesla.Middleware.PathParams + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "3"]]) assert_receive {:span, span(name: "GET potato", attributes: _attributes)} end end - test "Records spans for Tesla HTTP client", %{bypass: bypass} do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry - ] - - Tesla.client(middleware) - end - end - + test "Records spans for Tesla HTTP client", %{bypass: bypass, base_url: base_url} do Bypass.expect_once(bypass, "GET", "/users", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(name: "HTTP GET", attributes: _attributes)} end @@ -223,52 +161,30 @@ defmodule Tesla.Middleware.OpenTelemetryTest do ] for code <- @error_codes do - test "Marks Span status as :error when HTTP request fails with #{code}", %{bypass: bypass} do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry - ] - - Tesla.client(middleware) - end - end - + test "Marks Span status as :error when HTTP request fails with #{code}", %{ + bypass: bypass, + base_url: base_url + } do Bypass.expect_once(bypass, "GET", "/users", fn conn -> Plug.Conn.resp(conn, unquote(code), "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(status: {:status, :error, ""})} end end - test "Marks Span status as :errors when max redirects are exceeded", %{bypass: bypass} do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - {Tesla.Middleware.FollowRedirects, max_redirects: 1} - ] - - Tesla.client(middleware) - end - end - + test "Marks Span status as :errors when max redirects are exceeded", %{ + bypass: bypass, + base_url: base_url + } do Bypass.expect(bypass, "GET", "/users", fn conn -> conn |> Plug.Conn.put_resp_header("Location", "/users/1") @@ -281,99 +197,69 @@ defmodule Tesla.Middleware.OpenTelemetryTest do |> Plug.Conn.resp(301, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + {Tesla.Middleware.FollowRedirects, max_redirects: 1} + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(status: {:status, :error, ""})} end test "Marks Span status as :error if error status is within `mark_status_ok` opt list", - %{bypass: bypass} do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - {Tesla.Middleware.OpenTelemetry, mark_status_ok: [404]} - ] - - Tesla.client(middleware) - end - end - + %{bypass: bypass, base_url: base_url} do Bypass.expect_once(bypass, "GET", "/users", fn conn -> Plug.Conn.resp(conn, 404, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + {Tesla.Middleware.OpenTelemetry, mark_status_ok: [404]} + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(status: {:status, :ok, ""})} end test "Marks Span status as :ok unless error status is within `mark_status_ok` opt list", - %{bypass: bypass} do - defmodule TestClient do - def get(client) do - Tesla.get(client, "/users/") - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - {Tesla.Middleware.OpenTelemetry, mark_status_ok: []} - ] - - Tesla.client(middleware) - end - end - + %{bypass: bypass, base_url: base_url} do Bypass.expect_once(bypass, "GET", "/users", fn conn -> Plug.Conn.resp(conn, 404, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get() + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + {Tesla.Middleware.OpenTelemetry, mark_status_ok: []} + ]) + + Tesla.get(client, "/users/") assert_receive {:span, span(status: {:status, :error, ""})} end - test "Appends query string parameters to http.url attribute", %{bypass: bypass} do - defmodule TestClient do - def get(client, id) do - params = [id: id] - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - Tesla.Middleware.PathParams, - {Tesla.Middleware.Query, [token: "some-token", array: ["foo", "bar"]]} - ] - - Tesla.client(middleware) - end - end - + test "Appends query string parameters to http.url attribute", %{ + bypass: bypass, + base_url: base_url + } do Bypass.expect_once(bypass, "GET", "/users/2", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get("2") + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + Tesla.Middleware.PathParams, + {Tesla.Middleware.Query, [token: "some-token", array: ["foo", "bar"]]} + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "2"]]) assert_receive {:span, span(name: _name, attributes: attributes)} @@ -384,34 +270,22 @@ defmodule Tesla.Middleware.OpenTelemetryTest do end test "http.url attribute is correct when request doesn't contain query string parameters", %{ - bypass: bypass + bypass: bypass, + base_url: base_url } do - defmodule TestClient do - def get(client, id) do - params = [id: id] - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - Tesla.Middleware.PathParams, - {Tesla.Middleware.Query, []} - ] - - Tesla.client(middleware) - end - end - Bypass.expect_once(bypass, "GET", "/users/2", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get("2") + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + Tesla.Middleware.PathParams, + {Tesla.Middleware.Query, []} + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "2"]]) assert_receive {:span, span(name: _name, attributes: attributes)} @@ -421,74 +295,49 @@ defmodule Tesla.Middleware.OpenTelemetryTest do "http://localhost:#{bypass.port}/users/2" end - test "Handles url path arguments correctly", %{bypass: bypass} do - defmodule TestClient do - def get(client, id) do - params = [id: id] - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - Tesla.Middleware.PathParams, - {Tesla.Middleware.Query, [token: "some-token"]} - ] - - Tesla.client(middleware) - end - end - + test "Handles url path arguments correctly", %{bypass: bypass, base_url: base_url} do Bypass.expect_once(bypass, "GET", "/users/2", fn conn -> Plug.Conn.resp(conn, 204, "") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get("2") + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + Tesla.Middleware.PathParams, + {Tesla.Middleware.Query, [token: "some-token"]} + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "2"]]) assert_receive {:span, span(name: _name, attributes: attributes)} assert %{"http.target": "/users/2"} = :otel_attributes.map(attributes) end - test "Records http.response_content_length param into the span", %{bypass: bypass} do - defmodule TestClient do - def get(client, id) do - params = [id: id] - Tesla.get(client, "/users/:id", opts: [path_params: params]) - end - - def client(url) do - middleware = [ - {Tesla.Middleware.BaseUrl, url}, - Tesla.Middleware.OpenTelemetry, - Tesla.Middleware.PathParams, - {Tesla.Middleware.Query, [token: "some-token"]} - ] - - Tesla.client(middleware) - end - end - - response = "HELLO 👋" - + test "Records http.response_content_length param into the span", %{ + bypass: bypass, + base_url: base_url + } do Bypass.expect_once(bypass, "GET", "/users/2", fn conn -> - Plug.Conn.resp(conn, 200, response) + Plug.Conn.resp(conn, 200, "HELLO 👋") end) - bypass.port - |> endpoint_url() - |> TestClient.client() - |> TestClient.get("2") + client = + Tesla.client([ + {Tesla.Middleware.BaseUrl, base_url}, + Tesla.Middleware.OpenTelemetry, + Tesla.Middleware.PathParams, + {Tesla.Middleware.Query, [token: "some-token"]} + ]) + + Tesla.get(client, "/users/:id", opts: [path_params: [id: "2"]]) assert_receive {:span, span(name: _name, attributes: attributes)} mapped_attributes = :otel_attributes.map(attributes) {response_size, _} = Integer.parse(mapped_attributes[:"http.response_content_length"]) - assert response_size == byte_size(response) + assert response_size == byte_size("HELLO 👋") end describe "trace propagation" do @@ -513,9 +362,7 @@ defmodule Tesla.Middleware.OpenTelemetryTest do end defp client(opts \\ []) do - [ - {Tesla.Middleware.OpenTelemetry, opts} - ] + [{Tesla.Middleware.OpenTelemetry, opts}] |> Tesla.client(fn env -> {:ok, env} end) end diff --git a/instrumentation/opentelemetry_xandra/.formatter.exs b/instrumentation/opentelemetry_xandra/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/instrumentation/opentelemetry_xandra/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/instrumentation/opentelemetry_xandra/.gitignore b/instrumentation/opentelemetry_xandra/.gitignore new file mode 100644 index 00000000..4d682422 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/.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_xandra-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/instrumentation/opentelemetry_xandra/CHANGELOG.md b/instrumentation/opentelemetry_xandra/CHANGELOG.md new file mode 100644 index 00000000..47830714 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +## v0.2.0 + + * Rename `OpenTelemetryXandra` to `OpentelemetryXandra` (notice the case change) to be in line with other libraries in this collection. + * Rename `OpenTelemetryXandra.setup/1` to `OpentelemetryXandra.attach/1`. + * Rename the `query_parser_fun` type to `operation_parser_fun`. + * Rename the `:query_parser` option to `:operation_parser`. + +## v0.1.0 + +First release. diff --git a/instrumentation/opentelemetry_xandra/README.md b/instrumentation/opentelemetry_xandra/README.md new file mode 100644 index 00000000..09545a27 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/README.md @@ -0,0 +1,23 @@ +# OpentelemetryXandra + +This library uses [Telemetry](https://github.com/beam-telemetry/telemetry/) events to create OpenTelemetry Spans for [Xandra](https://github.com/whatyouhide/xandra) queries. + +## Installation + +Add `opentelemetry_xandra` to your list of dependencies in `mix.exs`: + +```elixir +defp deps do + [ + # Other opentelemetry_* deps..., + {:opentelemetry_xandra, "~> 0.1"} + ] +end +``` + +## Compatibility Matrix + +| OpentelemetryXandra Version | OpenTelemetry Version | +| :-------------------------- | :-------------------- | +| v0.1.0 | v1.0.0 | +| v0.2.0 | v1.0.0 | diff --git a/instrumentation/opentelemetry_xandra/config/config.exs b/instrumentation/opentelemetry_xandra/config/config.exs new file mode 100644 index 00000000..bfc2bc18 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/config/config.exs @@ -0,0 +1,6 @@ +import Config + +if config_env() == :test do + config :opentelemetry, + processors: [{:otel_simple_processor, %{}}] +end diff --git a/instrumentation/opentelemetry_xandra/docker-compose.yml b/instrumentation/opentelemetry_xandra/docker-compose.yml new file mode 100644 index 00000000..5471b929 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3' + +services: + cassandra: + image: cassandra:${CASSANDRA_VERSION:-4.1} + ports: + - "9042:9042" # TCP connections + environment: + - HEAP_NEWSIZE=1M + - MAX_HEAP_SIZE=200M + healthcheck: + test: [ "CMD-SHELL", "nodetool -h ::FFFF:127.0.0.1 status" ] + interval: 20s + timeout: 10s + retries: 12 + logging: + driver: "json-file" + options: + max-size: 50m diff --git a/instrumentation/opentelemetry_xandra/lib/opentelemetry_xandra.ex b/instrumentation/opentelemetry_xandra/lib/opentelemetry_xandra.ex new file mode 100644 index 00000000..126a705e --- /dev/null +++ b/instrumentation/opentelemetry_xandra/lib/opentelemetry_xandra.ex @@ -0,0 +1,172 @@ +defmodule OpentelemetryXandra do + @moduledoc """ + A module to trace Xandra queries with OpenTelemetry. + + This library uses [Telemetry](https://github.com/beam-telemetry/telemetry) to + create OpenTelemetry Spans for Xandra queries. + + ## Usage + + See `attach/1`. + + ## Resources + + This library follows the OpenTelemetry Semantic Conventions for naming, according to: + + * [The Cassandra conventions](https://opentelemetry.io/docs/specs/semconv/database/cassandra/) + * [The DB conventions](https://opentelemetry.io/docs/specs/semconv/database/database-spans/) + + """ + + @tracer_id __MODULE__ + + @typedoc """ + Thet type for a function that returns the statement to be used in the span. + + See `attach/1` for more information. + """ + @type statement_fun :: (String.t() -> {:ok, String.t()} | :error) + + @typedoc """ + The type for a function that parses a query and returns the operation, database, and table. + + See `attach/1` for more information. + """ + @type operation_parser_fun :: + (String.t() -> {operation :: String.t(), database :: String.t(), table :: String.t()}) + + @doc """ + Attaches a Telemetry handler that records OTel spans for Xandra queries. + + ## Usage + + Call this function in your application's `c:Application.start/2` callback: + + def start(_type, _args) do + children = [ + # ... + ] + + OpentelemetryXandra.setup() + + Supervisor.start_link(children, strategy: :one_for_one) + end + + ## Options + + * `:operation_parser` - a function that takes a query (as a string) and should + return a DB operation string that will be used in the span name. For example, + for a query like `INSERT INTO users (id, name) VALUES (1, 'Alice')`, the + operation parser could return `INSERT`. The default operation parser + just takes the first word of the (whitespace-trimmed) query. + + * `:statement` - it can be a boolean, where `true` means that the `db.statement` + span attribute gets filled with the query statement. If `false`, the attribute + doesn't get set. It can also be a function of type `t:statement_fun/0`: if it + returns `{:ok, statement}` then `db.statement` gets set to `statement`, while + if it returns `:error` then `db.statement` doesn't get set. + + > #### Sensitive Information {: .error} + > + > Xandra does not sanitize the query that this library captures. Whatever string + > you pass to `Xandra.execute/4` and other functions gets used for the `:statement` + > option. + + """ + @spec setup(keyword()) :: :ok | {:error, :already_exists} + def setup(options \\ []) when is_list(options) do + config = %{ + operation_parser: Keyword.get(options, :operation_parser, &parse_operation/1) + } + + :telemetry.attach_many( + __MODULE__, + [ + [:xandra, :execute_query, :start], + [:xandra, :execute_query, :stop], + [:xandra, :execute_query, :exception] + ], + &__MODULE__.handle_event/4, + config + ) + end + + @doc false + def handle_event([:xandra, :execute_query, event], _measurements, metadata, config) do + _ = handle_event(event, metadata, config) + :ok + end + + defp handle_event(:start, metadata, config) do + attributes = attributes_from_query(metadata.query, config) + + OpentelemetryTelemetry.start_telemetry_span( + @tracer_id, + Map.fetch!(attributes, :"db.operation"), + metadata, + %{ + kind: :client, + # TODO: use semantic conventions once the library gets updated to the latest spec. + attributes: + Map.merge(attributes, %{ + "db.system": "cassandra", + "server.address": metadata.address, + "network.peer.address": metadata.address, + "network.peer.port": metadata.port + }) + } + ) + end + + defp handle_event(:stop, metadata, _config) do + span_ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + + status = + case Map.get(metadata, :reason) do + nil -> OpenTelemetry.status(:ok) + error when is_exception(error) -> OpenTelemetry.status(:error, Exception.message(error)) + other -> OpenTelemetry.status(:error, inspect(other)) + end + + OpenTelemetry.Span.set_status(span_ctx, status) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + defp handle_event(:exception, metadata, _config) do + span_ctx = OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata) + + status = OpenTelemetry.status(:error, inspect(metadata.reason)) + OpenTelemetry.Span.set_status(span_ctx, status) + + :otel_span.record_exception(span_ctx, metadata.kind, metadata.reason, metadata.stacktrace, []) + + OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata) + end + + defp attributes_from_query(query, config) + when is_struct(query, Xandra.Simple) or is_struct(query, Xandra.Prepared) do + case config.operation_parser.(query.statement) do + {operation, database, table} -> + ["db.operation": operation, "db.name": database, "db.sql.table": table] + |> Enum.reject(&match?({_, nil}, &1)) + |> Map.new() + + other -> + raise ArgumentError, + ":operation_parser must return a tuple with 3 elements, got: #{inspect(other)}" + end + end + + defp attributes_from_query(_meta, _config) do + %{"db.operation": "UNKNOWN"} + end + + defp parse_operation(statement) do + case String.trim(statement) do + "SELECT" <> _rest -> {"SELECT", nil, nil} + "UPDATE" <> _rest -> {"UPDATE", nil, nil} + _other -> {"UNKNOWN", nil, nil} + end + end +end diff --git a/instrumentation/opentelemetry_xandra/mix.exs b/instrumentation/opentelemetry_xandra/mix.exs new file mode 100644 index 00000000..4ed5d9b7 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/mix.exs @@ -0,0 +1,61 @@ +defmodule OpentelemetryXandra.MixProject do + use Mix.Project + + @version "0.2.0" + @description "Trace Xandra queries with OpenTelemetry." + @repo_url "https://github.com/open-telemetry/opentelemetry-erlang-contrib" + @folder_url "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/instrumentation/opentelemetry_xandra" + + def project do + [ + app: :opentelemetry_xandra, + description: @description, + version: @version, + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + deps: deps(), + + # Docs + source_url: @folder_url, + docs: [ + source_url_pattern: "#{@folder_url}/%{path}#L%{line}", + main: "OpentelemetryXandra", + extras: ["README.md"] + ], + + # Hex + package: [ + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => @folder_url, + "OpenTelemetry Erlang" => "https://github.com/open-telemetry/opentelemetry-erlang", + "OpenTelemetry Erlang Contrib" => @repo_url, + "OpenTelemetry.io" => "https://opentelemetry.io" + } + ] + ] + end + + def application do + [ + extra_applications: [] + ] + end + + defp deps do + [ + # Dev and test dependencies + {:decimal, "~> 2.0", only: [:dev, :test]}, + {:ex_doc, "~> 0.38", only: :dev}, + {:opentelemetry, "~> 1.0", only: [:dev, :test]}, + {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, + {:xandra, "~> 0.18", only: [:dev, :test]}, + + # Library dependencies + {:opentelemetry_api, "~> 1.0"}, + {:opentelemetry_process_propagator, "~> 0.3"}, + {:opentelemetry_telemetry, "~> 1.1"}, + {:telemetry, "~> 0.4 or ~> 1.0"} + ] + end +end diff --git a/instrumentation/opentelemetry_xandra/mix.lock b/instrumentation/opentelemetry_xandra/mix.lock new file mode 100644 index 00000000..4031e759 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/mix.lock @@ -0,0 +1,26 @@ +%{ + "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, + "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, + "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, + "opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, + "xandra": {:hex, :xandra, "0.18.1", "6ac8794161f69a5ada6e8c197e5e3472f44c94f7b3add208cd3abc8ee135f852", [:mix], [{:decimal, "~> 1.7 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25d74d8101ca303b7be102da14a37629ae94c1bd21827f9d199a27f5e89b785f"}, +} diff --git a/instrumentation/opentelemetry_xandra/test/opentelemetry_xandra_test.exs b/instrumentation/opentelemetry_xandra/test/opentelemetry_xandra_test.exs new file mode 100644 index 00000000..de1c1cfc --- /dev/null +++ b/instrumentation/opentelemetry_xandra/test/opentelemetry_xandra_test.exs @@ -0,0 +1,87 @@ +defmodule OpentelemetryXandraTest do + use ExUnit.Case, async: false + + require OpenTelemetry.Tracer + require Record + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do + Record.defrecordp(name, spec) + end + + for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do + Record.defrecordp(name, spec) + end + + setup do + :otel_simple_processor.set_exporter(:otel_exporter_pid, self()) + + OpenTelemetry.Tracer.start_span("test") + + on_exit(fn -> + OpenTelemetry.Tracer.end_span() + end) + end + + describe "span creation when executing queries" do + test "when the query is successful" do + OpentelemetryXandra.setup() + + conn = start_supervised!({Xandra, connect_timeout: 5_000}) + + Xandra.execute!(conn, "SELECT * FROM system.local", []) + + assert_receive {:span, span(name: "SELECT") = span} + + assert span(span, :kind) == :client + assert span(span, :status) == OpenTelemetry.status(:ok) + + attributes = :otel_attributes.map(span(span, :attributes)) + assert attributes[:"db.system"] == "cassandra" + assert attributes[:"db.operation"] == "SELECT" + assert attributes[:"server.address"] == "127.0.0.1" + assert attributes[:"network.peer.address"] == "127.0.0.1" + assert attributes[:"network.peer.port"] == 9042 + end + + test "when the query is a prepared query" do + OpentelemetryXandra.setup() + + conn = start_supervised!({Xandra, connect_timeout: 5_000}) + + prepared = Xandra.prepare!(conn, "SELECT * FROM system.local") + Xandra.execute!(conn, prepared, []) + + assert_receive {:span, span(name: "SELECT") = span} + + assert span(span, :kind) == :client + assert span(span, :status) == OpenTelemetry.status(:ok) + + attributes = :otel_attributes.map(span(span, :attributes)) + assert attributes[:"db.system"] == "cassandra" + assert attributes[:"db.operation"] == "SELECT" + assert attributes[:"server.address"] == "127.0.0.1" + assert attributes[:"network.peer.address"] == "127.0.0.1" + assert attributes[:"network.peer.port"] == 9042 + end + + test "with the :operation_parser option" do + OpentelemetryXandra.setup(operation_parser: fn _query -> {"SELECT", nil, nil} end) + + conn = start_supervised!({Xandra, connect_timeout: 5000}) + + Xandra.execute!(conn, "SELECT * FROM system.local", []) + + assert_receive {:span, span(name: "SELECT") = span} + + assert span(span, :kind) == :client + assert span(span, :status) == OpenTelemetry.status(:ok) + + attributes = :otel_attributes.map(span(span, :attributes)) + assert attributes[:"db.system"] == "cassandra" + assert attributes[:"db.operation"] == "SELECT" + assert attributes[:"server.address"] == "127.0.0.1" + assert attributes[:"network.peer.address"] == "127.0.0.1" + assert attributes[:"network.peer.port"] == 9042 + end + end +end diff --git a/instrumentation/opentelemetry_xandra/test/test_helper.exs b/instrumentation/opentelemetry_xandra/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/instrumentation/opentelemetry_xandra/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/processors/opentelemetry_baggage_processor/.gitignore b/processors/opentelemetry_baggage_processor/.gitignore new file mode 100644 index 00000000..02f661b4 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/.gitignore @@ -0,0 +1,33 @@ +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/processors/opentelemetry_baggage_processor/LICENSE b/processors/opentelemetry_baggage_processor/LICENSE new file mode 100644 index 00000000..57bc88a1 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/LICENSE @@ -0,0 +1,202 @@ + 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/processors/opentelemetry_baggage_processor/README.md b/processors/opentelemetry_baggage_processor/README.md new file mode 100644 index 00000000..ec24519c --- /dev/null +++ b/processors/opentelemetry_baggage_processor/README.md @@ -0,0 +1,72 @@ +# opentelemetry_baggage_processor + +A Span Processor that takes attributes from the Baggage and insert into the Span. + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `opentelemetry_baggage_processor` to your list of dependencies: + +```erlang +{deps, [ + {opentelemetry_baggage_processor, "~> 0.1"} +]}. +``` + +```elixir +def deps do + [ + {:opentelemetry_baggage_processor, "~> 0.1"} + ] +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_baggage_processor](https://hexdocs.pm/opentelemetry_baggage_processor). + +## Usage + + + +`opentelemetry_baggage_processor` provides a [Span Processor](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-processor) +that takes attributes from the [Baggage](https://hexdocs.pm/opentelemetry_api/otel_baggage.html) +and insert into the Span, once it starts. + +A Span Processor is not an application, to use it you must update your +configuration: + +```elixir +# config/config.exs + +config :opentelemetry, processors, + otel_baggage_processor: %{}}, + otel_batch_processor: %{ + exporter: {:opentelemetry_exporter, %{}} + } +``` + +The processor configuration is order-dependent, so `otel_baggage_processor` +configuration must come before the processor used for exporting – in this case, +`otel_batch_processor`. + +Now every new span should have what's inside your baggage as attribute. + +### Options + +* `:prefix` - adds a prefix for all baggage attributes. +* `:filter` - only add attributes if the baggage metadata has the configured key. +The key must be a binary. + +### Limitations + +Baggage will follow the Context. So any limitation to Context Propagation applies +to Baggage Propagation, and thus to what attributes are going to be added to your +Span. + +We can only apply attributes on Span's start, since that's when we can modify +them. There's a [BeforeEnd callback proposal](https://github.com/open-telemetry/opentelemetry-specification/issues/1089) +which would allow us to add the Baggage's attribute on Span's end too, but that +remains as something to be revisited in the future. + + diff --git a/processors/opentelemetry_baggage_processor/docs.config b/processors/opentelemetry_baggage_processor/docs.config new file mode 100644 index 00000000..ae1f27c6 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/docs.config @@ -0,0 +1,4 @@ +{source_url, <<"https://github.com/open-telemetry/opentelemetry-erlang-contrib">>}. +{extras, [<<"README.md">>]}. +{main, <<"readme">>}. +{proglang, erlang}. diff --git a/processors/opentelemetry_baggage_processor/docs.sh b/processors/opentelemetry_baggage_processor/docs.sh new file mode 100755 index 00000000..a8e26016 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/docs.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +# Setup: +# +# # 1. install OTP 24+ +# # 2. install ExDoc: +# $ mix escript.install github elixir-lang/ex_doc + +rebar3 compile +rebar3 edoc + +ex_doc "opentelemetry_baggage_processor" 0.0.1 "_build/default/lib/opentelemetry_baggage_processor/ebin" \ + --source-ref v0.0.1 \ + --config docs.config $@ \ + --output "doc" diff --git a/processors/opentelemetry_baggage_processor/rebar.config b/processors/opentelemetry_baggage_processor/rebar.config new file mode 100644 index 00000000..f9446243 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/rebar.config @@ -0,0 +1,29 @@ +{erl_opts, [debug_info]}. +{deps, [ + {opentelemetry_api, "~> 1.2"} +]}. + +{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.3"} + ]}, + {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"]}]}. + diff --git a/processors/opentelemetry_baggage_processor/rebar.lock b/processors/opentelemetry_baggage_processor/rebar.lock new file mode 100644 index 00000000..2e66f028 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/rebar.lock @@ -0,0 +1,11 @@ +{"1.2.0", +[{<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.0.4">>},0}, + {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.0.3">>},0}]}. +[ +{pkg_hash,[ + {<<"opentelemetry">>, <<"A7DAF00A248715EE72C12BA1CBAD878837E8DCA6E9BEA12A2381A1F27DF9EBB2">>}, + {<<"opentelemetry_api">>, <<"77F9644C42340CD8B18C728CDE4822ED55AE136F0D07761B78E8C54DA46AF93A">>}]}, +{pkg_hash_ext,[ + {<<"opentelemetry">>, <<"D75C3931884817679CA63C21395715427457691F5C9BE06D75B71D74ADC9D0B4">>}, + {<<"opentelemetry_api">>, <<"4293E06BD369BC004E6FAD5EDBB56456D891F14BD3F9F1772B18F1923E0678EA">>}]} +]. diff --git a/processors/opentelemetry_baggage_processor/src/opentelemetry_baggage_processor.app.src b/processors/opentelemetry_baggage_processor/src/opentelemetry_baggage_processor.app.src new file mode 100644 index 00000000..289d3bd5 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/src/opentelemetry_baggage_processor.app.src @@ -0,0 +1,16 @@ +{application, opentelemetry_baggage_processor, + [{description, "Library that defines a Span Processor which takes attributes from the Baggage and insert into the Span as attributes"}, + {vsn, "0.0.1"}, + {registered, []}, + {applications, + [kernel, + stdlib, + opentelemetry + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache-2.0"]}, + {links, [{"GitHub", "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/processors/opentelemetry_baggage_processor"}]} + ]}. + diff --git a/processors/opentelemetry_baggage_processor/src/otel_baggage_processor.erl b/processors/opentelemetry_baggage_processor/src/otel_baggage_processor.erl new file mode 100644 index 00000000..2a59185c --- /dev/null +++ b/processors/opentelemetry_baggage_processor/src/otel_baggage_processor.erl @@ -0,0 +1,68 @@ +-module(otel_baggage_processor). + +-behaviour(otel_span_processor). + +-include_lib("opentelemetry/include/otel_span.hrl"). +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). + +-export([on_start/3, on_end/2, force_flush/1]). + +-type processor_config() :: term(). + +-spec on_start(otel_ctx:t(), opentelemetry:span(), processor_config()) -> + opentelemetry:span(). +on_start(Ctx, Span, Config) -> + Baggage = otel_baggage:get_all(Ctx), + Prefix = maps:get(prefix, Config, undefined), + FilterKey = maps:get(filter, Config, undefined), + Attributes = + maps:fold(fun(Key, {Value, Metadata}, Attributes) -> + NewKey = add_prefix(Key, Prefix), + case filter(Metadata, FilterKey) of + false -> Attributes; + true -> [{NewKey, Value}] ++ Attributes + end + end, + [], + Baggage), + add_attributes(Span, Attributes). + +-spec on_end(opentelemetry:span(), processor_config()) -> + true | dropped | {error, invalid_span} | {error, no_export_buffer}. +on_end(_Span, _Config) -> + true. + +-spec force_flush(processor_config()) -> ok | {error, term()}. +force_flush(_Config) -> + ok. + +-spec add_attributes(opentelemetry:span(), opentelemetry:attributes_map()) -> + opentelemetry:span(). +add_attributes(Span = #span{attributes = SpanAttributes}, AttributesMap) -> + Span#span{attributes = otel_attributes:set(AttributesMap, SpanAttributes)}. + +-spec filter(otel_baggage:metadata(), map()) -> boolean(). +filter(_Metadata, undefined) -> + true; +filter(Metadata, FilterKey) -> + case lists:search(fun (Key) when Key == FilterKey -> + true; + (_) -> + false + end, + Metadata) + of + false -> + false; + {value, _} -> + true + end. + +-spec add_prefix(opentelemetry:attribute_key(), map()) -> opentelemetry:attribute_key(). +add_prefix(Key, Prefix) when is_binary(Key), is_binary(Prefix) -> + <>; +add_prefix(Key, Prefix) when is_atom(Key), is_binary(Prefix) -> + Key2 = atom_to_binary(Key), + <>; +add_prefix(Key, _Prefix) -> + Key. diff --git a/processors/opentelemetry_baggage_processor/test/otel_baggage_processor_SUITE.erl b/processors/opentelemetry_baggage_processor/test/otel_baggage_processor_SUITE.erl new file mode 100644 index 00000000..ebc41723 --- /dev/null +++ b/processors/opentelemetry_baggage_processor/test/otel_baggage_processor_SUITE.erl @@ -0,0 +1,95 @@ +-module(otel_baggage_processor_SUITE). + +-compile(export_all). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("opentelemetry/include/otel_span.hrl"). +-include_lib("opentelemetry_api/include/otel_tracer.hrl"). + +all() -> + [baggage_handling, add_prefix_to_attributes, filter_baggage_attributes]. + +init_per_suite(Config) -> + ok = application:load(opentelemetry_baggage_processor), + ok = application:load(opentelemetry), + application:set_env(opentelemetry, + processors, + [{otel_baggage_processor, #{}}, + {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(opentelemetry_baggage_processor), + Config. + +end_per_testcase(_, Config) -> + application:stop(opentelemetry), + Config. + +baggage_handling(_Config) -> + {ok, _} = application:ensure_all_started(opentelemetry), + otel_batch_processor:set_exporter(otel_exporter_pid, self()), + SpanCtx1 = ?start_span(<<"span-1">>), + ?set_current_span(SpanCtx1), + Ctx = otel_ctx:get_current(), + Ctx2 = otel_baggage:set(Ctx, <<"key">>, <<"value">>), + _Token = otel_ctx:attach(Ctx2), + SpanCtx2 = + ?start_span(<<"span-2">>, #{attributes => #{<<"existing-attribute">> => true}}), + ?end_span(), + ?set_current_span(SpanCtx2), + ?end_span(), + Attributes = get_span_attributes(<<"span-1">>), + ?assertEqual(Attributes, #{}), + Attributes2 = get_span_attributes(<<"span-2">>), + ?assertEqual(Attributes2, #{<<"key">> => <<"value">>, <<"existing-attribute">> => true}), + ok. + +add_prefix_to_attributes(_Config) -> + application:set_env(opentelemetry, + processors, + [{otel_baggage_processor, #{prefix => <<"app.">>}}, + {otel_batch_processor, #{scheduled_delay_ms => 1}}]), + {ok, _} = application:ensure_all_started(opentelemetry), + otel_batch_processor:set_exporter(otel_exporter_pid, self()), + Ctx = otel_ctx:get_current(), + Ctx2 = otel_baggage:set(Ctx, <<"key">>, <<"value">>), + Ctx3 = otel_baggage:set(Ctx2, atom_key, <<"value">>), + _Token = otel_ctx:attach(Ctx3), + SpanCtx1 = ?start_span(<<"span-1">>), + ?set_current_span(SpanCtx1), + ?end_span(), + Attributes = get_span_attributes(<<"span-1">>), + ?assertEqual(#{<<"app.key">> => <<"value">>, <<"app.atom_key">> => <<"value">>}, + Attributes), + ok. + +filter_baggage_attributes(_Config) -> + application:set_env(opentelemetry, + processors, + [{otel_baggage_processor, #{filter => <<"trace_field">>}}, + {otel_batch_processor, #{scheduled_delay_ms => 1}}]), + {ok, _} = application:ensure_all_started(opentelemetry), + otel_batch_processor:set_exporter(otel_exporter_pid, self()), + Ctx = otel_ctx:get_current(), + Ctx2 = otel_baggage:set(Ctx, <<"key">>, <<"value">>), + Ctx3 = otel_baggage:set(Ctx2, atom_key, <<"value">>, [<<"trace_field">>]), + _Token = otel_ctx:attach(Ctx3), + SpanCtx1 = ?start_span(<<"span-1">>), + ?set_current_span(SpanCtx1), + ?end_span(), + Attributes = get_span_attributes(<<"span-1">>), + ?assertEqual(#{<<"atom_key">> => <<"value">>}, Attributes), + ok. + +get_span_attributes(Name) -> + receive + {span, #span{name = Name, attributes = Attributes}} -> + otel_attributes:map(Attributes) + after 100 -> + error(timeout) + end. diff --git a/propagators/opentelemetry_process_propagator/lib/opentelemetry_process_propagator.ex b/propagators/opentelemetry_process_propagator/lib/opentelemetry_process_propagator.ex index f831c121..931a96b6 100644 --- a/propagators/opentelemetry_process_propagator/lib/opentelemetry_process_propagator.ex +++ b/propagators/opentelemetry_process_propagator/lib/opentelemetry_process_propagator.ex @@ -45,7 +45,7 @@ defmodule OpentelemetryProcessPropagator do Example of using `fetch_parent_ctx/1` to find a parent context. ```elixir - OpenTelemetry.with_span :span_started_in_your_app do + OpenTelemetry.Tracer.with_span :span_started_in_your_app do # some span being created in a process spawned by a library # you don't control, e.g. Ecto preloads diff --git a/propagators/opentelemetry_process_propagator/lib/task.ex b/propagators/opentelemetry_process_propagator/lib/task.ex index 15258b43..0cdef154 100644 --- a/propagators/opentelemetry_process_propagator/lib/task.ex +++ b/propagators/opentelemetry_process_propagator/lib/task.ex @@ -55,6 +55,8 @@ defmodule OpentelemetryProcessPropagator.Task do alias OpentelemetryProcessPropagator.Task.Wrapper require OpenTelemetry.Tracer + @type t :: Task.t() + @doc """ Returns a stream that runs the given function `fun` concurrently on each element in `enumerable` with the current `t:OpenTelemetry.Ctx.t/0` diff --git a/propagators/opentelemetry_process_propagator/mix.exs b/propagators/opentelemetry_process_propagator/mix.exs index 723e40ff..6241157c 100644 --- a/propagators/opentelemetry_process_propagator/mix.exs +++ b/propagators/opentelemetry_process_propagator/mix.exs @@ -45,7 +45,7 @@ defmodule OpentelemetryProcessPropagator.MixProject do end) |> Enum.concat([ {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.34", only: :dev, runtime: false}, + {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:opentelemetry, "~> 1.0", only: [:dev, :test]}, {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]} ]) diff --git a/propagators/opentelemetry_process_propagator/mix.lock b/propagators/opentelemetry_process_propagator/mix.lock index a926ecdb..4e1d52eb 100644 --- a/propagators/opentelemetry_process_propagator/mix.lock +++ b/propagators/opentelemetry_process_propagator/mix.lock @@ -1,22 +1,22 @@ %{ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, - "chatterbox": {:hex, :ts_chatterbox, "0.13.0", "6f059d97bcaa758b8ea6fffe2b3b81362bd06b639d3ea2bb088335511d691ebf", [:rebar3], [{:hpack, "~>0.2.3", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "b93d19104d86af0b3f2566c4cba2a57d2e06d103728246ba1ac6c3c0ff010aa7"}, + "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, - "gproc": {:hex, :gproc, "0.8.0", "cea02c578589c61e5341fce149ea36ccef236cc2ecac8691fba408e7ea77ec2f", [:rebar3], [], "hexpm", "580adafa56463b75263ef5a5df4c86af321f68694e7786cb057fd805d1e2a7de"}, - "grpcbox": {:hex, :grpcbox, "0.16.0", "b83f37c62d6eeca347b77f9b1ec7e9f62231690cdfeb3a31be07cd4002ba9c82", [:rebar3], [{:acceptor_pool, "~>1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~>0.13.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", "294df743ae20a7e030889f00644001370a4f7ce0121f3bbdaf13cf3169c62913"}, - "hpack": {:hex, :hpack_erl, "0.2.3", "17670f83ff984ae6cd74b1c456edde906d27ff013740ee4d9efaa4f1bf999633", [:rebar3], [], "hexpm", "06f580167c4b8b8a6429040df36cc93bba6d571faeaec1b28816523379cbb23a"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.3.1", "f0a342a74379e3540a634e7047967733da4bc8b873ec9026e224b2bd7369b1fc", [:rebar3], [{:opentelemetry_api, "~> 1.2.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "de476b2ac4faad3e3fe3d6e18b35dec9cb338c3b9910c2ce9317836dacad3483"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.2", "693f47b0d8c76da2095fe858204cfd6350c27fe85d00e4b763deecc9588cf27a", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "dc77b9a00f137a858e60a852f14007bb66eda1ffbeb6c05d5fe6c9e678b05e9d"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.6.0", "f4fbf69aa9f1541b253813221b82b48a9863bc1570d8ecc517bc510c0d1d3d8c", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.3", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.2", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "1802d1dca297e46f21e5832ecf843c451121e875f73f04db87355a6cb2ba1710"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, + "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, + "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, + "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.19.0", "c76c4c5d79ee79a2b11c84f910c825d6f024a78427c854f515748e9bd025e987", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "4083b4a298add534c96125337cb01161c358bb32dd870d5a893aae685fd91d70"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/propagators/opentelemetry_process_propagator/src/opentelemetry_process_propagator.erl b/propagators/opentelemetry_process_propagator/src/opentelemetry_process_propagator.erl index ce579a1f..de5af060 100644 --- a/propagators/opentelemetry_process_propagator/src/opentelemetry_process_propagator.erl +++ b/propagators/opentelemetry_process_propagator/src/opentelemetry_process_propagator.erl @@ -15,7 +15,10 @@ fetch_parent_ctx(MaxDepth) -> -spec fetch_parent_ctx(non_neg_integer(), atom()) -> otel_ctx:t() | undefined. fetch_parent_ctx(MaxDepth, Key) -> - Pids = pids(Key, pdict(self())), + Pids = case get(Key) of + List when is_list(List) -> List; + _ -> [] + end, inspect_parent(undefined, lists:sublist(Pids, MaxDepth)). inspect_parent(Ctx, _Pids) when Ctx =/= undefined -> @@ -30,28 +33,35 @@ inspect_parent(_Ctx, [Pid | Rest]) -> inspect_parent(OtelCtx, []) end. --spec fetch_ctx(pid()) -> otel_ctx:t() | undefined. -fetch_ctx(Pid) -> - case pdict(Pid) of - undefined -> - undefined; - Dictionary -> - otel_ctx(Dictionary) - end. - --spec pdict(pid() | atom()) -> [{term(), term()}] | undefined. -pdict(Name) when is_atom(Name) -> +-spec fetch_ctx(pid() | atom()) -> otel_ctx:t() | undefined. +fetch_ctx(Name) when is_atom(Name) -> case whereis(Name) of undefined -> undefined; Pid -> pdict(Pid) end; -pdict(Pid) -> +fetch_ctx(Pid) when is_pid(Pid) -> + pdict(Pid). + +-spec pdict(pid()) -> otel_ctx:t() | undefined. +-if(?OTP_RELEASE >= 27). +%% Fetching a single key from another process's dictionary was introduced in 26.2, +%% so we can't depend on it until 27. +pdict(Pid) when node(Pid) =:= node() -> + case process_info(Pid, {dictionary, '$__current_otel_ctx'}) of + undefined -> undefined; + {{dictionary, '$__current_otel_ctx'}, Ctx} -> Ctx + end; +pdict(_) -> + undefined. + +-else. +pdict(Pid) when node(Pid) =:= node() -> case process_info(Pid, dictionary) of - {dictionary, Dict} -> - Dict; - undefined -> - undefined - end. + undefined -> undefined; + {dictionary, Dict} -> otel_ctx(Dict) + end; +pdict(_) -> + undefined. -spec otel_ctx([{term(), term()}]) -> otel_ctx:t() | undefined. otel_ctx(Dictionary) -> @@ -61,11 +71,4 @@ otel_ctx(Dictionary) -> {'$__current_otel_ctx', Ctx} -> Ctx end. - -pids(Key, Dictionary) -> - case lists:keyfind(Key, 1, Dictionary) of - false -> - []; - {Key,Pids} -> - Pids - end. +-endif. diff --git a/renovate.json b/renovate.json index 70ca6ac8..88cbafb4 100644 --- a/renovate.json +++ b/renovate.json @@ -7,7 +7,7 @@ { "matchDatasources": ["hex"], "matchUpdateTypes": ["minor", "patch", "pin", "digest"], - "addLabels": ["skip-changelog"] + "addLabels": ["skip-changelog", "chore"] } ] } diff --git a/utilities/opentelemetry_instrumentation_http/CHANGELOG.md b/utilities/opentelemetry_instrumentation_http/CHANGELOG.md deleted file mode 100644 index 6f2fd935..00000000 --- a/utilities/opentelemetry_instrumentation_http/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# Changelog - -## v0.1.0 - -### Changed - -* Expose `extract_headers_attributes/3` and `normalize_header_name/1` functions to extracted selected headers in an attribute map following semantic conventions diff --git a/utilities/opentelemetry_instrumentation_http/docs.config b/utilities/opentelemetry_instrumentation_http/docs.config deleted file mode 100644 index e09daa28..00000000 --- a/utilities/opentelemetry_instrumentation_http/docs.config +++ /dev/null @@ -1,4 +0,0 @@ -{source_url, <<"https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_instrumentation_http">>}. -{extras, [<<"LICENSE">>]}. -{main, <<"opentelemetry_instrumentation_http">>}. -{proglang, erlang}. \ No newline at end of file diff --git a/utilities/opentelemetry_instrumentation_http/docs.sh b/utilities/opentelemetry_instrumentation_http/docs.sh deleted file mode 100755 index eb2b2e05..00000000 --- a/utilities/opentelemetry_instrumentation_http/docs.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -e - -# Setup: -# -# mix escript.install github elixir-lang/ex_doc -# asdf install erlang 24.0.2 -# asdf local erlang 24.0.2 - -rebar3 compile -rebar3 as docs edoc -version=0.1.0 -ex_doc "opentelemetry_instrumentation_http" $version "_build/default/lib/opentelemetry_instrumentation_http/ebin" \ - --source-ref v${version} \ - --config docs.config $@ diff --git a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.app.src b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.app.src deleted file mode 100644 index e16b7193..00000000 --- a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.app.src +++ /dev/null @@ -1,17 +0,0 @@ -{application, opentelemetry_instrumentation_http, [ - {description, "OpenTelemetry Instrumentation utilities for HTTP libraries"}, - {vsn, "0.1.0"}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]}, - {env, []}, - {modules, []}, - {doc, "doc"}, - {licenses, ["Apache 2.0"]}, - {links, [ - {"GitHub", - "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_instrumentation_http"} - ]} -]}. diff --git a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl deleted file mode 100644 index 3cb59460..00000000 --- a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl +++ /dev/null @@ -1,110 +0,0 @@ --module(opentelemetry_instrumentation_http). - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). --endif. - --export([ - extract_headers_attributes/3, - normalize_header_name/1 -]). - --spec normalize_header_name(string() | binary()) -> Result when - Result :: binary() | {error, binary(), RestData} | {incomplete, binary(), binary()}, - RestData :: unicode:latin1_chardata() | unicode:chardata() | unicode:external_chardata(). -normalize_header_name(Header) when is_binary(Header) -> - normalize_header_name(binary_to_list(Header)); -normalize_header_name(Header) when is_list(Header) -> - unicode:characters_to_binary(string:replace(string:to_lower(Header), "-", "_", all)). - --spec extract_headers_attributes( - request | response, - Headers :: - #{binary() | string() => binary() | [binary()]} - | [{binary() | string(), binary() | [binary()]}], - HeadersToExtract :: [binary()] -) -> #{atom() => [binary()]}. -extract_headers_attributes(_Context, _Headers, []) -> - #{}; -extract_headers_attributes(Context, Headers, HeadersToExtract) when is_list(Headers) -> - lists:foldr( - fun({HeaderName, HeaderValue}, Extracted) -> - extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) - end, - #{}, - Headers - ); -extract_headers_attributes(Context, Headers, HeadersToExtract) when is_map(Headers) -> - maps:fold( - fun(HeaderName, HeaderValue, Extracted) -> - extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) - end, - #{}, - Headers - ). - -extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) -> - NormalizedHeaderName = normalize_header_name(HeaderName), - case lists:member(NormalizedHeaderName, HeadersToExtract) of - false -> Extracted; - true -> set_header_attribute(Context, NormalizedHeaderName, HeaderValue, Extracted) - end. - -set_header_attribute(Context, Header, Value, HeadersAttributes) when is_list(Value) -> - maps:put(attribute_name(Context, Header), Value, HeadersAttributes); -set_header_attribute(Context, Header, Value, HeadersAttributes) -> - maps:update_with( - attribute_name(Context, Header), fun(V) -> [Value | V] end, [Value], HeadersAttributes - ). - -attribute_name(request, HeaderName) -> - binary_to_atom(<<"http.request.header.", HeaderName/binary>>); -attribute_name(response, HeaderName) -> - binary_to_atom(<<"http.response.header.", HeaderName/binary>>). - --ifdef(TEST). - -normalize_header_name_test_() -> - [ - ?_assertEqual(normalize_header_name(<<"Content-Type">>), <<"content_type">>), - ?_assertEqual(normalize_header_name("Some-Header-NAME"), <<"some_header_name">>) - ]. - -extract_headers_attributes_test_() -> - [ - ?_assertEqual(extract_headers_attributes(request, [], []), #{}), - ?_assertEqual(extract_headers_attributes(response, #{}, []), #{}), - ?_assertEqual( - extract_headers_attributes( - request, - #{ - <<"Foo">> => <<"1">>, - "Bar-Baz" => [<<"2">>, <<"3">>], - "To-Not-Extract" => <<"4">> - }, - [<<"foo">>, <<"bar_baz">>] - ), - #{ - 'http.request.header.foo' => [<<"1">>], - 'http.request.header.bar_baz' => [<<"2">>, <<"3">>] - } - ), - ?_assertEqual( - extract_headers_attributes( - response, - [ - {<<"Foo">>, <<"1">>}, - {"Bar-Baz", <<"2">>}, - {"To-Not-Extract", <<"3">>}, - {<<"foo">>, <<"4">>} - ], - [<<"foo">>, <<"bar_baz">>] - ), - #{ - 'http.response.header.foo' => [<<"1">>, <<"4">>], - 'http.response.header.bar_baz' => [<<"2">>] - } - ) - ]. - --endif. diff --git a/utilities/opentelemetry_telemetry/mix.exs b/utilities/opentelemetry_telemetry/mix.exs index 16f580b1..68f34751 100644 --- a/utilities/opentelemetry_telemetry/mix.exs +++ b/utilities/opentelemetry_telemetry/mix.exs @@ -45,7 +45,7 @@ defmodule OpentelemetryTelemetry.MixProject do end) |> Enum.concat([ {:dialyxir, "~> 1.4.0", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.34", only: :dev, runtime: false}, + {:ex_doc, "~> 0.38", only: :dev, runtime: false}, {:opentelemetry, "~> 1.4", only: [:dev, :test]}, {:opentelemetry_exporter, "~> 1.7", only: [:dev, :test]} ]) diff --git a/utilities/opentelemetry_telemetry/mix.lock b/utilities/opentelemetry_telemetry/mix.lock index 170c9c21..222c7184 100644 --- a/utilities/opentelemetry_telemetry/mix.lock +++ b/utilities/opentelemetry_telemetry/mix.lock @@ -2,22 +2,22 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"}, "gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"}, "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "opentelemetry": {:hex, :opentelemetry, "1.4.0", "f928923ed80adb5eb7894bac22e9a198478e6a8f04020ae1d6f289fdcad0b498", [:rebar3], [{:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "50b32ce127413e5d87b092b4d210a3449ea80cd8224090fe68d73d576a3faa15"}, - "opentelemetry_api": {:hex, :opentelemetry_api, "1.3.0", "03e2177f28dd8d11aaa88e8522c81c2f6a788170fe52f7a65262340961e663f9", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "b9e5ff775fd064fa098dba3c398490b77649a352b40b0b730a6b7dc0bdd68858"}, - "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.7.0", "dec4e90c0667cf11a3642f7fe71982dbc0c6bfbb8725a0b13766830718cf0d98", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.4.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "d0f25f6439ec43f2561537c3fabbe177b38547cddaa3a692cbb8f4770dbefc1e"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "opentelemetry": {:hex, :opentelemetry, "1.5.0", "7dda6551edfc3050ea4b0b40c0d2570423d6372b97e9c60793263ef62c53c3c2", [:rebar3], [{:opentelemetry_api, "~> 1.4", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "cdf4f51d17b592fc592b9a75f86a6f808c23044ba7cf7b9534debbcc5c23b0ee"}, + "opentelemetry_api": {:hex, :opentelemetry_api, "1.4.0", "63ca1742f92f00059298f478048dfb826f4b20d49534493d6919a0db39b6db04", [:mix, :rebar3], [], "hexpm", "3dfbbfaa2c2ed3121c5c483162836c4f9027def469c41578af5ef32589fcfc58"}, + "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.8.0", "5d546123230771ef4174e37bedfd77e3374913304cd6ea3ca82a2add49cd5d56", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.4.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "a1f9f271f8d3b02b81462a6bfef7075fd8457fdb06adff5d2537df5e2264d9af"}, "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tls_certificate_check": {:hex, :tls_certificate_check, "1.22.1", "0f450cc1568a67a65ce5e15df53c53f9a098c3da081c5f126199a72505858dc1", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3092be0babdc0e14c2e900542351e066c0fa5a9cf4b3597559ad1e67f07938c0"}, + "tls_certificate_check": {:hex, :tls_certificate_check, "1.27.0", "2c1c7fc922a329b9eb45ddf39113c998bbdeb28a534219cd884431e2aee1811e", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "51a5ad3dbd72d4694848965f3b5076e8b55d70eb8d5057fcddd536029ab8a23c"}, } diff --git a/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src b/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src index 4fafbf11..9a9193ce 100644 --- a/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src +++ b/utilities/opentelemetry_telemetry/src/opentelemetry_telemetry.app.src @@ -1,6 +1,6 @@ {application, opentelemetry_telemetry, [{description, "Telemetry to OpenTelemetry Bridge"}, - {vsn, "1.1.1"}, + {vsn, "1.1.2"}, {registered, []}, {applications, [kernel, diff --git a/utilities/opentelemetry_telemetry/src/otel_telemetry.erl b/utilities/opentelemetry_telemetry/src/otel_telemetry.erl index b4671a5a..820c9f1f 100644 --- a/utilities/opentelemetry_telemetry/src/otel_telemetry.erl +++ b/utilities/opentelemetry_telemetry/src/otel_telemetry.erl @@ -112,7 +112,11 @@ pop_from_tracer_stack(TracerId) -> undefined; [SpanCtxSet | Rest] -> erlang:put({otel_telemetry, TracerId}, Rest), - SpanCtxSet + SpanCtxSet; + [] -> + ?LOG_DEBUG("`opentelemetry_telemetry` span ctx tracer stack for " + "TracerId ~p in Pid ~p is empty.", [TracerId, self()]), + undefined end. handle_event(_Event, diff --git a/utilities/opentelemetry_instrumentation_http/.gitignore b/utilities/otel_http/.gitignore similarity index 100% rename from utilities/opentelemetry_instrumentation_http/.gitignore rename to utilities/otel_http/.gitignore diff --git a/utilities/otel_http/CHANGELOG.md b/utilities/otel_http/CHANGELOG.md new file mode 100644 index 00000000..6e974bb1 --- /dev/null +++ b/utilities/otel_http/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## v0.2.0 + +### Breaking + +* Package renamed to `otel_http` from `opentelemetry_instrumentation_http` + +### Features + +* Adds several header extraction and manipulation functions for common tasks + in instrumentation libraries: + * `extract_client_info/1` + * `extract_client_info/2` + * `extract_scheme/1` + * `extract_scheme/2` + * `extract_server_info/1` + * `extract_server_info/2` +* Generate OTP27 docs + +## v0.1.0 + +### Changed + +* Expose `extract_headers_attributes/3` and `normalize_header_name/1` functions to extracted selected headers in an attribute map following semantic conventions diff --git a/utilities/opentelemetry_instrumentation_http/LICENSE b/utilities/otel_http/LICENSE similarity index 100% rename from utilities/opentelemetry_instrumentation_http/LICENSE rename to utilities/otel_http/LICENSE diff --git a/utilities/opentelemetry_instrumentation_http/README.md b/utilities/otel_http/README.md similarity index 53% rename from utilities/opentelemetry_instrumentation_http/README.md rename to utilities/otel_http/README.md index a88f8202..74a96f36 100644 --- a/utilities/opentelemetry_instrumentation_http/README.md +++ b/utilities/otel_http/README.md @@ -1,22 +1,21 @@ -# opentelemetry_instrumentation_http +# otel_http [![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_instrumentation_http)](https://hex.pm/packages/opentelemetry_instrumentation_http) +[![Hex.pm](https://img.shields.io/hexpm/v/otel_http)](https://hex.pm/packages/otel_http) ![Build Status](https://github.com/open-telemetry/opentelemetry-erlang-contrib/workflows/Erlang/badge.svg) - ## Installation ```erlang {deps, [ - {opentelemetry_instrumentation_http, "~> 0.1"} + {otel_http, "~> 0.2"} ]} ``` + ```elixir def deps do [ - {:opentelemetry_instrumentation_http, "~> 0.1"} + {:otel_http, "~> 0.2"} ] end ``` - diff --git a/utilities/opentelemetry_instrumentation_http/rebar.config b/utilities/otel_http/rebar.config similarity index 62% rename from utilities/opentelemetry_instrumentation_http/rebar.config rename to utilities/otel_http/rebar.config index 6e60337b..c2eb0158 100644 --- a/utilities/opentelemetry_instrumentation_http/rebar.config +++ b/utilities/otel_http/rebar.config @@ -7,16 +7,6 @@ rebar3_hex ]}. {profiles, [ - {docs, [ - {deps, [edown]}, - {edoc_opts, [ - {preprocess, true}, - {doclet, edoc_doclet_chunks}, - {layout, edoc_layout_chunks}, - {dir, "_build/default/lib/opentelemetry_instrumentation_http/doc"}, - {subpackages, true} - ]} - ]}, {test, [ {erl_opts, [nowarn_export_all]}, {deps, []}, @@ -35,3 +25,15 @@ {cover_enabled, true}. {cover_export_enabled, true}. {covertool, [{coverdata_files, ["ct.coverdata"]}]}. + +{plugins, [rebar3_ex_doc]}. + +{hex, [ + {doc, #{provider => ex_doc}} +]}. + +{ex_doc, [ + {source_url, <<"https://github.com/opentelemetry-erlang-contrib/utilities/otel_http">>}, + {extras, [<<"README.md">>, <<"CHANGELOG.md">>, <<"LICENSE">>]}, + {main, <<"readme">>} +]}. \ No newline at end of file diff --git a/utilities/opentelemetry_instrumentation_http/rebar.lock b/utilities/otel_http/rebar.lock similarity index 100% rename from utilities/opentelemetry_instrumentation_http/rebar.lock rename to utilities/otel_http/rebar.lock diff --git a/utilities/otel_http/src/otel_http.app.src b/utilities/otel_http/src/otel_http.app.src new file mode 100644 index 00000000..60b124b0 --- /dev/null +++ b/utilities/otel_http/src/otel_http.app.src @@ -0,0 +1,22 @@ +{application, otel_http, [ + {description, "OpenTelemetry Instrumentation utilities for HTTP libraries"}, + {vsn, "0.2.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {exclude_paths, ["rebar.lock"]}, + {env, []}, + {modules, []}, + {doc, "doc"}, + {licenses, ["Apache-2.0"]}, + {links, [ + {"GitHub", + "https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/otel_http"}, + {"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"} + ]} +]}. diff --git a/utilities/otel_http/src/otel_http.erl b/utilities/otel_http/src/otel_http.erl new file mode 100644 index 00000000..2bd79340 --- /dev/null +++ b/utilities/otel_http/src/otel_http.erl @@ -0,0 +1,458 @@ +-module(otel_http). + +-if(?OTP_RELEASE >= 27). +-define(MODULEDOC(Str), -moduledoc(Str)). +-define(DOC(Str), -doc(Str)). +-else. +-define(MODULEDOC(Str), -compile([])). +-define(DOC(Str), -compile([])). +-endif. + +-export([ + extract_headers_attributes/3, + extract_client_info/1, + extract_client_info/2, + extract_ip_port/1, + extract_scheme/1, + extract_scheme/2, + extract_server_info/1, + extract_server_info/2, + normalize_header_name/1, + parse_forwarded_header/1 +]). + + +-type client_info() :: #{ip => binary() | undefined, port => integer()}. +-type header_name() :: binary() | string(). +-type header_value() :: binary(). +-type header() :: {header_name(), header_value()}. +-type headers_map() :: #{header_name() => header_value()}. +-type header_sort_fun() :: fun((Header1 :: header_name(), Header2 :: header_name()) -> boolean()). +-type server_info() :: #{address => binary() | undefined, port => integer() | undefined}. + +-export_type([ + client_info/0, + header_name/0, + header_value/0, + headers_map/0, + header_sort_fun/0, + server_info/0]). + +?MODULEDOC(""" +`otel_http` provides utility functions for +common otel http-related instrumentation operations such as extraction +of schemes, client and server info, and header operations. +"""). + +?DOC(""" +Extract the original client request protocol scheme from request headers. + +For lists of headers, the scheme is extracted from the first header +which can contain the scheme. For maps of headers, the map is converted +to a list and a sort function applied according to the semantic convention's +priority order. See the documentation for `extract_scheme/2` for more +information. +"""). + +-spec extract_scheme([header()] | headers_map()) -> http | https | undefined. +extract_scheme([]) -> + undefined; +extract_scheme(Map) when is_map(Map) and map_size(Map) == 0 -> + undefined; +extract_scheme(Headers) when is_list(Headers) -> + extract_scheme(Headers, fun list_scheme_headers_sort/2); +extract_scheme(Headers) when is_map(Headers) -> + extract_scheme(maps:to_list(Headers), fun map_scheme_headers_sort/2). + +?DOC(""" +Extract the original client request protocol scheme from request headers. +Users may supply their own sort function for prioritizing a particular header +over others. + +The default sort order per SemConv gives equal priority to `forwarded` +and `x-forwarded-proto` headers, followed by the `:scheme` HTTP2 header. +"""). +-spec extract_scheme(Headers, Fun) -> http | https | undefined + when Fun :: header_sort_fun(), Headers :: [header()]. +extract_scheme(Headers, SortFun) when is_list(Headers) -> + SortedHeaders = lists:sort(SortFun, Headers), + reduce_while( + SortedHeaders, undefined, fun(Header, Acc) -> + case extract_scheme_from_header(Header) of + undefined -> + {cont, Acc}; + <<"http">> -> + {halt, http}; + <<"https">> -> + {halt, https}; + _ -> + {halt, undefined} + end + end + ). + +list_scheme_headers_sort(_, _) -> true. + +map_scheme_headers_sort(HeaderName1, HeaderName2) -> + HN1Priority = scheme_header_priority(HeaderName1), + HN2Priority = scheme_header_priority(HeaderName2), + case {HN1Priority, HN2Priority} of + {H1, H2} when H1 =< H2 -> + true; + {H1, H2} when H1 > H2 -> + false + end. + +% Define header priority +scheme_header_priority({HeaderName, _Value}) -> + Priority = + case HeaderName of + <<"forwarded">> -> 1; + <<"x-forwarded-proto">> -> 1; + <<":scheme">> -> 2; + % Default priority for other headers + _ -> 4 + end, + Priority. + +extract_scheme_from_header({<<"forwarded">>, Value}) -> + DirectiveMap = parse_forwarded_header(Value), + SchemeValue = maps:get(<<"proto">>, DirectiveMap, []), + case SchemeValue of + [] -> + undefined; + [Scheme | _Rest] -> + Scheme; + _ -> + undefined + end; +extract_scheme_from_header({_Other, Value}) -> + Value. + +?DOC(""" +Extract the original client request protocol scheme from request headers. + +For lists of headers, the address and port are extracted from the first header +which can contain that vbalue. For maps of headers, the map is converted +to a list and a sort function applied according to the semantic convention's +priority order. See the documentation for `extract_server_info/2` for more +information. +"""). + +-spec extract_server_info([header()] | headers_map()) -> server_info(). +extract_server_info([]) -> + #{address => undefined, port => undefined}; +extract_server_info(Map) when is_map(Map) and map_size(Map) == 0 -> + #{address => undefined, port => undefined}; +extract_server_info(Headers) when is_list(Headers) -> + extract_server_info(Headers, fun list_server_headers_sort/2); +extract_server_info(Headers) when is_map(Headers) -> + extract_server_info(maps:to_list(Headers), fun map_server_headers_sort/2). + +?DOC(""" +Extract the original server address and port from request headers. +Users may supply their own sort function for prioritizing a particular header +over others. + +The default sort order per SemConv gives equal priority to `forwarded` +and `x-forwarded-host` headers, followed by the `:authority` HTTP2 header +and then `host`. +"""). +-spec extract_server_info(Headers, Fun) -> server_info() + when Fun :: header_sort_fun(), Headers :: [header()]. +extract_server_info(Headers, SortFun) -> + SortedHeaders = lists:sort(SortFun, Headers), + reduce_while( + SortedHeaders, #{address => undefined, port => undefined}, fun(Header, Acc) -> + case extract_server_info_from_header(Header) of + {undefined, undefined} -> + {cont, Acc}; + {Address, Port} -> + {halt, #{address => Address, port => Port}} + end + end + ). + +list_server_headers_sort(_, _) -> true. + +map_server_headers_sort(HeaderName1, HeaderName2) -> + HN1Priority = server_header_priority(HeaderName1), + HN2Priority = server_header_priority(HeaderName2), + case {HN1Priority, HN2Priority} of + {H1, H2} when H1 =< H2 -> + true; + {H1, H2} when H1 > H2 -> + false + end. + +server_header_priority({HeaderName, _Value}) -> + Priority = + case HeaderName of + <<"forwarded">> -> 1; + <<"x-forwarded-host">> -> 1; + <<":authority">> -> 2; + <<"host">> -> 3; + % Default priority for other headers + _ -> 4 + end, + Priority. + +extract_server_info_from_header({<<"forwarded">>, Value}) -> + DirectiveMap = parse_forwarded_header(Value), + HostValue = maps:get(<<"host">>, DirectiveMap, []), + case HostValue of + [] -> + {undefined, undefined}; + [LeftMostHost | _Rest] -> + case string:split(LeftMostHost, ":") of + [Host] -> + {Host, undefined}; + [Host, Port] -> + {Host, extract_port(Port)}; + _ -> + {undefined, undefined} + end + end; +extract_server_info_from_header({_Other, Value}) -> + case string:split(Value, ":") of + [Host] -> + {Host, undefined}; + [Host, Port] -> + {Host, extract_port(Port)}; + _ -> + {undefined, undefined} + end. + +?DOC(""" +Extract the original client request information from request headers. + +For lists of headers, the ip and port are extracted from the first header +which can contain the scheme. For maps of headers, the map is converted +to a list and a sort function applied according to the semantic convention's +priority order. See the documentation for `extract_client_info/2` for more +information. +"""). +-spec extract_client_info([header()] | headers_map()) -> client_info(). +extract_client_info([]) -> + #{ip => undefined, port => undefined}; +extract_client_info(Map) when is_map(Map) and map_size(Map) == 0 -> + #{ip => undefined, port => undefined}; +extract_client_info(Headers) when is_list(Headers) -> + extract_client_info(Headers, fun list_client_headers_sort/2); +extract_client_info(Headers) when is_map(Headers) -> + extract_client_info(maps:to_list(Headers), fun map_client_headers_sort/2). + +?DOC(""" +Extract the original server address and port from request headers. +Users may supply their own sort function for prioritizing a particular header +over others. + +The default sort order per SemConv gives equal priority to `forwarded` +and `x-forwarded-for` headers. +"""). +-spec extract_client_info(Headers, Fun) -> server_info() + when Fun :: header_sort_fun(), Headers :: [header()]. +extract_client_info(Headers, SortFun) -> + SortedHeaders = lists:sort(SortFun, Headers), + reduce_while( + SortedHeaders, #{ip => undefined, port => undefined}, fun(Header, Acc) -> + case extract_client_info_from_header(Header) of + {undefined, undefined} -> + {cont, Acc}; + {Ip, Port} -> + {halt, #{ip => Ip, port => Port}} + end + end + ). + +extract_client_info_from_header({<<"forwarded">>, Value}) -> + DirectiveMap = parse_forwarded_header(Value), + ForValue = maps:get(<<"for">>, DirectiveMap, []), + case ForValue of + [] -> + {undefined, undefined}; + [LeftMostFor | _Rest] -> + extract_ip_port(LeftMostFor) + end; +extract_client_info_from_header({_Other, Value}) -> + extract_ip_port(Value). + +map_client_headers_sort(HeaderName1, HeaderName2) -> + HN1Priority = client_header_priority(HeaderName1), + HN2Priority = client_header_priority(HeaderName2), + case {HN1Priority, HN2Priority} of + {H1, H2} when H1 =< H2 -> + true; + {H1, H2} when H1 > H2 -> + false + end. + +list_client_headers_sort(_, _) -> true. + +% Define header priority +client_header_priority({HeaderName, _Value}) -> + Priority = + case HeaderName of + <<"forwarded">> -> 1; + <<"x-forwarded-for">> -> 1; + % Default priority for other headers + _ -> 4 + end, + Priority. + +?DOC(""" +Parse a `forwarded` header to a map of directives. +"""). +-spec parse_forwarded_header(header()) -> + #{binary() => [header_value()]}. +parse_forwarded_header(Header) -> + KvpList = string:split(Header, <<";">>, all), + Grouped = lists:foldl(fun group_by/2, #{}, KvpList), + Grouped. + +group_by(Kvp, Acc) -> + SplitDirectives = string:split(Kvp, <<",">>, all), + lists:foldr( + fun(ProcessedKvp, A) -> + case string:split(string:trim(ProcessedKvp), <<"=">>, all) of + [Directive, Value] -> + TrimmedValue = string:trim(Value), + update_group(string:trim(Directive), TrimmedValue, A); + _Malformed -> + A + end + end, + Acc, + SplitDirectives + ). + +update_group(Key, Value, Acc) -> + case maps:get(Key, Acc, []) of + List -> Acc#{Key => [Value | List]} + end. + +extract_ip_port(IpStr) when is_binary(IpStr) -> + extract_ip_port(binary_to_list(IpStr)); +extract_ip_port(IpStr) when is_list(IpStr) -> + case re:split(IpStr, "[\]\[/\/\"]", [{return, list}, trim]) of + [[], [], Ip] -> + case inet:parse_ipv6strict_address(Ip) of + {ok, IpV6} -> + {list_to_binary(inet:ntoa(IpV6)), undefined}; + _ -> + {undefined, undefined} + end; + [[], [], Ip, Port] -> + case inet:parse_ipv6strict_address(Ip) of + {ok, IpV6} -> + {list_to_binary(inet:ntoa(IpV6)), extract_port(string:trim(Port, leading, ":"))}; + _ -> + {undefined, undefined} + end; + [IpV4Str] -> + [LeftMostIpV4Str | _] = string:split(IpV4Str, <<",">>), + case string:split(LeftMostIpV4Str, ":") of + [Ip, Port] -> + case inet:parse_ipv4strict_address(Ip) of + {ok, IpV4} -> + {list_to_binary(inet:ntoa(IpV4)), extract_port(Port)}; + _ -> + {undefined, undefined} + end; + [Ip] -> + case inet:parse_ipv4strict_address(Ip) of + {ok, IpV4} -> + {list_to_binary(inet:ntoa(IpV4)), undefined}; + _ -> + {undefined, undefined} + end; + _ -> + {undefined, undefined} + end; + _Other -> + {undefined, undefined} + end; +extract_ip_port(_) -> + {undefined, undefined}. + +extract_port(PortStr) -> + case string:to_integer(PortStr) of + {error, _} -> + undefined; + {Port, _} when Port =< 65535 -> + Port; + _ -> + undefined + end. + +reduce_while([], Acc, _Fun) -> + Acc; +reduce_while([H | T], Acc, Fun) -> + case Fun(H, Acc) of + {halt, NewAcc} -> NewAcc; + {cont, NewAcc} -> reduce_while(T, NewAcc, Fun) + end. + +?DOC(""" +Normalizes a header name to a lowercase binary. +"""). +-spec normalize_header_name(string() | binary()) -> Result when + Result :: binary() | {error, binary(), RestData} | {incomplete, binary(), binary()}, + RestData :: unicode:latin1_chardata() | unicode:chardata() | unicode:external_chardata(). +normalize_header_name(Header) when is_binary(Header) -> + normalize_header_name(binary_to_list(Header)); +normalize_header_name(Header) when is_list(Header) -> + unicode:characters_to_binary(string:to_lower(Header)). + +?DOC(""" +Extracts a map of request or response header attributes for a given +set of attributes and list of headers to extract. + +NOTE: keys are generated as atoms for efficiency purposes so take care +that header names have a defined cardinality set. These values should never +be dynamic. +"""). +-spec extract_headers_attributes( + request | response, + Headers :: + #{header_name() => header_value() | [header_value()]} + | [{header_name(), header_value() | [header_value()]}], + HeadersToExtract :: [header_name()] +) -> #{atom() => [header_value()]}. +extract_headers_attributes(_Context, _Headers, []) -> + #{}; +extract_headers_attributes(Context, Headers, HeadersToExtract) when is_list(Headers) -> + lists:foldr( + fun({HeaderName, HeaderValue}, Extracted) -> + extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) + end, + #{}, + Headers + ); +extract_headers_attributes(Context, Headers, HeadersToExtract) when is_map(Headers) -> + maps:fold( + fun(HeaderName, HeaderValue, Extracted) -> + extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) + end, + #{}, + Headers + ). + +extract_if_match(Context, HeaderName, HeaderValue, Extracted, HeadersToExtract) -> + NormalizedHeaderName = normalize_header_name(HeaderName), + case lists:member(NormalizedHeaderName, HeadersToExtract) of + false -> Extracted; + true -> set_header_attribute(Context, NormalizedHeaderName, HeaderValue, Extracted) + end. + +set_header_attribute(Context, Header, Value, HeadersAttributes) when is_list(Value) -> + maps:put(attribute_name(Context, Header), Value, HeadersAttributes); +set_header_attribute(Context, Header, Value, HeadersAttributes) -> + maps:update_with( + attribute_name(Context, Header), fun(V) -> [Value | V] end, [Value], HeadersAttributes + ). + +attribute_name(request, HeaderName) -> + binary_to_atom(<<"http.request.header.", HeaderName/binary>>); +attribute_name(response, HeaderName) -> + binary_to_atom(<<"http.response.header.", HeaderName/binary>>). diff --git a/utilities/otel_http/test/otel_http_SUITE.erl b/utilities/otel_http/test/otel_http_SUITE.erl new file mode 100644 index 00000000..d893f6b9 --- /dev/null +++ b/utilities/otel_http/test/otel_http_SUITE.erl @@ -0,0 +1,254 @@ +-module(otel_http_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +all() -> + [ + header_name_normalization, + extract_headers_attributes, + extracts_client_info_from_headers, + extracts_scheme_from_headers, + extracts_server_info_from_headers, + extracting_ip_and_port_addresses, + parse_forwarded_headers + ]. + +header_name_normalization(_Config) -> + ?assertEqual( + otel_http:normalize_header_name(<<"Content-Type">>), + <<"content-type">> + ), + ?assertEqual( + otel_http:normalize_header_name("Some-Header-NAME"), + <<"some-header-name">> + ), + ok. + +extract_headers_attributes(_Config) -> + ?assertEqual(otel_http:extract_headers_attributes(request, [], []), #{}), + ?assertEqual(otel_http:extract_headers_attributes(response, #{}, []), #{}), + ?assertEqual( + otel_http:extract_headers_attributes( + request, + #{ + <<"Foo">> => <<"1">>, + <<"Bar-Baz">> => [<<"2">>, <<"3">>], + <<"To-Not-Extract">> => <<"4">> + }, + [<<"foo">>, <<"bar-baz">>] + ), + #{ + 'http.request.header.foo' => [<<"1">>], + 'http.request.header.bar-baz' => [<<"2">>, <<"3">>] + } + ), + ?assertEqual( + otel_http:extract_headers_attributes( + response, + [ + {<<"Foo">>, <<"1">>}, + {"Bar-Baz", <<"2">>}, + {"To-Not-Extract", <<"3">>}, + {<<"foo">>, <<"4">>} + ], + [<<"foo">>, <<"bar-baz">>] + ), + #{ + 'http.response.header.foo' => [<<"1">>, <<"4">>], + 'http.response.header.bar-baz' => [<<"2">>] + } + ), + ok. + +parse_forwarded_headers(_Config) -> + ?assertEqual( + #{ + <<"host">> => [<<"developer.mozilla.org:4321">>], + <<"for">> => [<<"192.0.2.60">>, <<"\"[2001:db8:cafe::17]\"">>], + <<"proto">> => [<<"http">>], + <<"by">> => [<<"203.0.113.43">>] + }, + otel_http:parse_forwarded_header( + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + ) + ). + +extracting_ip_and_port_addresses(_Config) -> + ?assertEqual( + {<<"192.0.2.60">>, undefined}, + otel_http:extract_ip_port(<<"192.0.2.60">>) + ), + ?assertEqual( + {<<"192.0.2.60">>, 443}, + otel_http:extract_ip_port(<<"192.0.2.60:443">>) + ), + ?assertEqual( + {<<"192.0.2.60">>, undefined}, + otel_http:extract_ip_port(<<"192.0.2.60:junk">>) + ), + ?assertEqual( + {<<"2001:db8:cafe::17">>, undefined}, + otel_http:extract_ip_port(<<"\"[2001:db8:cafe::17]\"">>) + ), + ?assertEqual( + {<<"2001:db8:cafe::17">>, 8000}, + otel_http:extract_ip_port(<<"\"[2001:db8:cafe::17]:8000\"">>) + ), + ?assertEqual( + {<<"::">>, undefined}, + otel_http:extract_ip_port(<<"\"[::]:99999\"">>) + ), + ?assertEqual( + {<<"2001:db8:cafe::17">>, undefined}, + otel_http:extract_ip_port(<<"\"[2001:db8:cafe::17]:junk\"">>) + ). + +extracts_client_info_from_headers(_Config) -> + ?assertEqual( + #{ip => <<"192.0.2.60">>, port => undefined}, + otel_http:extract_client_info(#{ + <<"forwarded">> => + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }) + ), + ?assertEqual( + #{ip => <<"2001:db8:cafe::17">>, port => undefined}, + otel_http:extract_client_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org:4321; for=\"[2001:db8:cafe::17]\", for=192.0.2.60; proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + #{ip => <<"2001:db8:cafe::17">>, port => 9678}, + otel_http:extract_client_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org:4321;for=\"[2001:db8:cafe::17]:9678\",for=192.0.2.60;proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + #{ip => <<"23.0.2.1">>, port => 2121}, + otel_http:extract_client_info([ + {<<"x-forwarded-for">>, <<"23.0.2.1:2121,25.2.2.2">>} + ]) + ), + ?assertEqual( + #{ip => <<"192.0.2.60">>, port => undefined}, + otel_http:extract_client_info(#{ + <<"x-forwarded-for">> => <<"23.0.2.1:2121">>, + <<"forwarded">> => + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }) + ), + ?assertEqual( + #{ip => <<"192.0.2.60">>, port => undefined}, + otel_http:extract_client_info(#{ + <<"forwarded">> => + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>, + <<"x-forwarded-for">> => <<"23.0.2.1:2121">> + }) + ), + ?assertEqual( + #{ip => <<"23.0.2.1">>, port => 2121}, + otel_http:extract_client_info([ + {<<"x-forwarded-for">>, <<"23.0.2.1:2121, 10.100.10.10">>}, + {<<"forwarded">>, + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + #{ip => <<"192.0.2.60">>, port => undefined}, + otel_http:extract_client_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>}, + {<<"x-forwarded-for">>, <<"23.0.2.1:2121,10.100.10.10">>} + ]) + ), + ?assertEqual( + #{ip => <<"27.27.27.27">>, port => 2222}, + otel_http:extract_client_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>}, + {<<"x-forwarded-for">>, <<"23.0.2.1:2121">>}, + {<<"x-real-client-ip">>, <<"27.27.27.27:2222">>} + ], + fun(Header1, _Header2) -> + Header1 == <<"x-real-client-ip">> + end) + ). + + +extracts_server_info_from_headers(_Config) -> + ?assertEqual( + #{address => <<"developer.mozilla.org">>, port => 4321}, + otel_http:extract_server_info(#{ + <<"forwarded">> => + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }) + ), + ?assertEqual( + #{address => <<"developer.mozilla.org">>, port => undefined}, + otel_http:extract_server_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }]) + ), + ?assertEqual( + #{address => <<"d1.mozilla.org">>, port => undefined}, + otel_http:extract_server_info([ + {<<"host">>, <<"d1.mozilla.org">>}, + {<<"forwarded">>, + <<"host=developer.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + #{address => <<"developer.mozilla.org">>, port => undefined}, + otel_http:extract_server_info([ + {<<"x-forwarded-host">>, <<"developer.mozilla.org">>}, + {<<"forwarded">>, + <<"host=d1.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + #{address => <<"developer.mozilla.org">>, port => undefined}, + otel_http:extract_server_info([ + {<<"forwarded">>, + <<"host=developer.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>}, + {<<"host">>, <<"d1.mozilla.org">>} + ]) + ). + + extracts_scheme_from_headers(_Config) -> + ?assertEqual( + http, + otel_http:extract_scheme(#{ + <<"forwarded">> => + <<"host=developer.mozilla.org:4321; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }) + ), + ?assertEqual( + http, + otel_http:extract_scheme([ + {<<"forwarded">>, + <<"host=developer.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">> + }]) + ), + ?assertEqual( + https, + otel_http:extract_scheme([ + {<<"x-forwarded-proto">>, <<"https">>}, + {<<"forwarded">>, + <<"host=developer.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>} + ]) + ), + ?assertEqual( + https, + otel_http:extract_scheme([ + {<<":scheme">>, <<"https">>}, + {<<"forwarded">>, + <<"host=d1.mozilla.org; for=192.0.2.60, for=\"[2001:db8:cafe::17]\";proto=http;by=203.0.113.43">>} + ]) + ).