diff --git a/.clang-tidy b/.clang-tidy index 9611954a59..0492066a96 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 +# Checks: > -*, @@ -38,4 +39,30 @@ Checks: > -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-avoid-non-const-global-variables, - -cppcoreguidelines-pro-* + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-static-cast-downcast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + modernize-*, + -modernize-avoid-c-arrays, + -modernize-concat-nested-namespaces, + -modernize-macro-to-enum, + -modernize-raw-string-literal, + -modernize-return-braced-init-list, + -modernize-type-traits, + -modernize-use-auto, + -modernize-use-nodiscard, + -modernize-use-scoped-lock, + -modernize-use-trailing-return-type, + -modernize-deprecated-headers, + -modernize-loop-convert, + -modernize-pass-by-value, + -modernize-use-equals-default, + -modernize-use-equals-delete, + -modernize-make-unique, + -modernize-make-shared, + diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 55ce42a59b..61ba188891 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97d89c74c..1983f747fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -53,6 +53,28 @@ jobs: run: | ./ci/do_ci.sh cmake.test + cmake_api_only_test: + name: CMake API only test + runs-on: ubuntu-24.04 + env: + CXX_STANDARD: '17' + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: 'recursive' + - name: setup + run: | + sudo -E ./ci/setup_ci_environment.sh + sudo -E apt-get install -y zlib1g-dev + - name: run cmake API only test + run: | + ./ci/do_ci.sh cmake.api_only.test + cmake_fetch_content_test: name: CMake FetchContent usage with opentelemetry-cpp runs-on: ubuntu-24.04 @@ -62,7 +84,7 @@ jobs: BUILD_TYPE: 'Release' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -92,7 +114,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -130,7 +152,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -168,7 +190,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -206,7 +228,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -237,7 +259,7 @@ jobs: CXX_STANDARD: '14' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -268,7 +290,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -304,7 +326,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -335,7 +357,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -362,7 +384,7 @@ jobs: runs-on: windows-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -380,7 +402,7 @@ jobs: runs-on: windows-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -400,7 +422,7 @@ jobs: runs-on: windows-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -420,7 +442,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -446,7 +468,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -464,7 +486,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -491,7 +513,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -515,7 +537,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -539,7 +561,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -573,7 +595,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -597,7 +619,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -631,7 +653,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -661,7 +683,7 @@ jobs: CXX_STANDARD: '14' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -682,7 +704,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -704,7 +726,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -728,7 +750,7 @@ jobs: if: false steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -752,7 +774,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -778,7 +800,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -804,7 +826,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -830,7 +852,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -856,7 +878,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -882,7 +904,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -908,7 +930,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -934,7 +956,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -960,7 +982,7 @@ jobs: runs-on: macos-15 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -987,7 +1009,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1020,7 +1042,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1035,7 +1057,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1048,7 +1070,7 @@ jobs: runs-on: windows-2022 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1068,7 +1090,7 @@ jobs: runs-on: windows-2022 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1090,7 +1112,7 @@ jobs: runs-on: windows-2022 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1110,7 +1132,7 @@ jobs: runs-on: windows-2022 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1130,7 +1152,7 @@ jobs: if: false steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1148,7 +1170,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1177,7 +1199,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1194,7 +1216,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1209,7 +1231,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1227,7 +1249,7 @@ jobs: runs-on: windows-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1243,7 +1265,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -1287,7 +1309,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index d95edcab17..08b66a0699 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,15 +17,15 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 328 + warning_limit: 216 - cmake_options: all-options-abiv2-preview - warning_limit: 338 + warning_limit: 226 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/cmake_install.yml b/.github/workflows/cmake_install.yml index a8481ecc58..c8c5ccbe40 100644 --- a/.github/workflows/cmake_install.yml +++ b/.github/workflows/cmake_install.yml @@ -19,7 +19,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -41,7 +41,7 @@ jobs: CXX_STANDARD: '20' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -64,7 +64,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -96,7 +96,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -126,7 +126,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -159,7 +159,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -191,7 +191,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -230,7 +230,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -270,7 +270,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -302,7 +302,7 @@ jobs: BUILD_TYPE: 'Debug' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 166f552d43..739c723e8f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,7 +23,7 @@ jobs: CXX_STANDARD: '17' steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -56,7 +56,7 @@ jobs: run: | sudo -E ./ci/install_thirdparty.sh --install-dir /usr/local --tags-file third_party_release --packages "ryml" - name: Initialize CodeQL - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: languages: cpp config: | @@ -71,4 +71,4 @@ jobs: "${GITHUB_WORKSPACE}" cmake --build . --parallel - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index 335f61d89d..93a857bfea 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index 40bc4e1e6b..db6e9837f6 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -13,7 +13,7 @@ jobs: timeout-minutes: 300 steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -22,14 +22,14 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up QEMU - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - name: Build Image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: builder: ${{ steps.buildx.outputs.name }} context: ci/ diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 7fd9e5ef6c..870f1e0f1e 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # v1.9.0 + - uses: fossas/fossa-action@29693cc50323968e039056be419b32989fc5880c # v2.0.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index e7e832f0bb..11946dc0a5 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 4d8cfca765..319fdcfea1 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -20,7 +20,7 @@ jobs: id-token: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit @@ -47,6 +47,6 @@ jobs: # 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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: sarif_file: results.sarif diff --git a/.github/workflows/project_management_comment.yml b/.github/workflows/project_management_comment.yml index c74a3e5c72..1c7b1065a0 100644 --- a/.github/workflows/project_management_comment.yml +++ b/.github/workflows/project_management_comment.yml @@ -15,7 +15,7 @@ jobs: issues: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/project_management_issue_open.yml b/.github/workflows/project_management_issue_open.yml index ddd405fff0..793be64e18 100644 --- a/.github/workflows/project_management_issue_open.yml +++ b/.github/workflows/project_management_issue_open.yml @@ -14,7 +14,7 @@ jobs: issues: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit diff --git a/.github/workflows/publish-to-bcr.yml b/.github/workflows/publish-to-bcr.yml index 0850c7e9b9..e68fe88bdd 100644 --- a/.github/workflows/publish-to-bcr.yml +++ b/.github/workflows/publish-to-bcr.yml @@ -25,7 +25,7 @@ jobs: contents: write id-token: write attestations: write - uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@c316f1611511a40423572303f66c80bb30bfe2f8 # v1.4.1 + uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@ad6879fd015a01c3e3b1b318fe3c061f928ac2fb # v1.4.2 with: tag_name: ${{ inputs.tag_name || github.event.release.tag_name }} registry_fork: ${{ inputs.registry_fork || 'open-telemetry/bazel-central-registry' }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d88faeb8bf..b03a689fdd 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 with: stale-issue-message: "This issue was marked as stale due to lack of activity." days-before-issue-stale: 60 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c462112a7..ad43a3e6e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,25 +15,85 @@ Increment the: ## [Unreleased] -* [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example - [#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065) +* docs: update supported development platforms + [#4260](https://github.com/open-telemetry/opentelemetry-cpp/pull/4260) + +* [RELEASE] Bump main branch to 1.29.0-dev + [#4259](https://github.com/open-telemetry/opentelemetry-cpp/pull/4259) + +* [API] Add `trace::GetSpanContext()` to read the active span's `SpanContext` + from a `Context` without the `DefaultSpan` allocation that + `GetSpan(context)->GetContext()` incurs, and use it at existing call sites. + [#4254](https://github.com/open-telemetry/opentelemetry-cpp/pull/4254) + +## [1.28.0] 2026-07-16 * [RELEASE] Bump main branch to 1.28.0-dev [#4081](https://github.com/open-telemetry/opentelemetry-cpp/pull/4081) -* [CODE HEALTH] Fix IWYU Clang22 warnings +* Bump step-security/harden-runner from 2.19.1 to 2.19.2 + [#4082](https://github.com/open-telemetry/opentelemetry-cpp/pull/4082) + +* Bump step-security/harden-runner from 2.19.2 to 2.19.3 + [#4084](https://github.com/open-telemetry/opentelemetry-cpp/pull/4084) + +* Fix IWYU Clang22 warnings [#4083](https://github.com/open-telemetry/opentelemetry-cpp/pull/4083) -* [EXPORTER] Spec-compliant uint64_t attribute encoding in OTLP - [#4090](https://github.com/open-telemetry/opentelemetry-cpp/pull/4090) +* Bump github/codeql-action from 4.35.4 to 4.35.5 + [#4087](https://github.com/open-telemetry/opentelemetry-cpp/pull/4087) * [CODE HEALTH] Remove unused alias declarations [#4091](https://github.com/open-telemetry/opentelemetry-cpp/pull/4091) +* Bump codecov/codecov-action from 6.0.0 to 6.0.1 + [#4092](https://github.com/open-telemetry/opentelemetry-cpp/pull/4092) + * [SDK] MeterProvider: do not warn in destructor after explicit Shutdown [#4085](https://github.com/open-telemetry/opentelemetry-cpp/pull/4085) -* [CODE HEALTH] Remove last unused nostd namespace alias in otlp_populate +* Fix: preserve delta start timestamp in fast path + [#4069](https://github.com/open-telemetry/opentelemetry-cpp/pull/4069) + +* Bump step-security/harden-runner from 2.19.3 to 2.19.4 + [#4098](https://github.com/open-telemetry/opentelemetry-cpp/pull/4098) + +* Bump docker/build-push-action from 7.1.0 to 7.2.0 + [#4097](https://github.com/open-telemetry/opentelemetry-cpp/pull/4097) + +* Bump actions/stale from 10.2.0 to 10.3.0 + [#4096](https://github.com/open-telemetry/opentelemetry-cpp/pull/4096) + +* docs: update custom log handler example + [#4089](https://github.com/open-telemetry/opentelemetry-cpp/pull/4089) + +* [BUILD] Suppress deprecated warnings in generated semconv metric headers + [#4088](https://github.com/open-telemetry/opentelemetry-cpp/pull/4088) + +* Bump docker/setup-buildx-action from 4.0.0 to 4.1.0 + [#4103](https://github.com/open-telemetry/opentelemetry-cpp/pull/4103) + +* Bump github/codeql-action from 4.35.5 to 4.36.0 + [#4102](https://github.com/open-telemetry/opentelemetry-cpp/pull/4102) + +* Bump docker/setup-qemu-action from 4.0.0 to 4.1.0 + [#4107](https://github.com/open-telemetry/opentelemetry-cpp/pull/4107) + +* [CI] add cmake gcc maintainer abiv2 job + [#4105](https://github.com/open-telemetry/opentelemetry-cpp/pull/4105) + +* [DOC] Fix typos in SDK header comments + [#4111](https://github.com/open-telemetry/opentelemetry-cpp/pull/4111) + +* [CI] update codeql to cover all options with abiv2 + [#4106](https://github.com/open-telemetry/opentelemetry-cpp/pull/4106) + +* [EXPORTER] Convert uint64_t attribute values exceeding + INT64_MAX to string per OTel spec + [#4090](https://github.com/open-telemetry/opentelemetry-cpp/pull/4090) + +* [CODE HEALTH] Remove last unused nostd namespace alias + in otlp_populate_attribute_utils [#4114](https://github.com/open-telemetry/opentelemetry-cpp/pull/4114) * [CODE HEALTH] Move curl_http_test classes into anonymous namespace @@ -42,12 +102,24 @@ Increment the: * [CODE HEALTH] Move simple_log_record_processor_test into anonymous namespace [#4116](https://github.com/open-telemetry/opentelemetry-cpp/pull/4116) +* Bump actions/checkout from 6.0.2 to 6.0.3 + [#4118](https://github.com/open-telemetry/opentelemetry-cpp/pull/4118) + +* Bump github/codeql-action from 4.36.0 to 4.36.1 + [#4117](https://github.com/open-telemetry/opentelemetry-cpp/pull/4117) + * [CODE HEALTH] Move registry.cc propagator builders into anonymous namespace [#4121](https://github.com/open-telemetry/opentelemetry-cpp/pull/4121) * [CODE HEALTH] Move sdk_builder.cc builders into anonymous namespace [#4122](https://github.com/open-telemetry/opentelemetry-cpp/pull/4122) +* Bump github/codeql-action from 4.36.1 to 4.36.2 + [#4123](https://github.com/open-telemetry/opentelemetry-cpp/pull/4123) + +* [CODE HEALTH] Fix clang-tidy bugprone-exception-escape in sum_aggregation.cc + [#4109](https://github.com/open-telemetry/opentelemetry-cpp/pull/4109) + * [CODE HEALTH] Move logger_sdk_test classes into anonymous namespace [#4124](https://github.com/open-telemetry/opentelemetry-cpp/pull/4124) @@ -57,65 +129,284 @@ Increment the: * [CODE HEALTH] Move func_grpc_main classes into anonymous namespace [#4129](https://github.com/open-telemetry/opentelemetry-cpp/pull/4129) -* [CONFIGURATION] Implement missing minimum_severity and trace_based for - LoggerConfig declarative configuration +* [CONFIGURATION] Implementing minimum_severity and trace_based + attributes for LoggerConfig [#4131](https://github.com/open-telemetry/opentelemetry-cpp/pull/4131) -* [CODE HEALTH] Fix clang-tidy bugprone-exception-escape in ostream exporters - [#4137](https://github.com/open-telemetry/opentelemetry-cpp/pull/4137) +* [SDK] Support TracerConfigurator updates + [#4065](https://github.com/open-telemetry/opentelemetry-cpp/pull/4065) -* [API] (ABI v2) `Logger::EmitLogRecord(...)` templates now apply the - `Enabled` filter chain when a `Severity` is in args. v1 behavior is - unchanged. - [#4079](https://github.com/open-telemetry/opentelemetry-cpp/pull/4079) +* Bump codecov/codecov-action from 6.0.1 to 7.0.0 + [#4143](https://github.com/open-telemetry/opentelemetry-cpp/pull/4143) -* [API/SDK] (ABI v2) Add `Logger::CreateLogRecord` virtual taking - `const nostd::variant &` - for explicit-context record creation. `Logger::EmitLogRecord(args...)` - also detects a `Context`, `SpanContext` - or `TraceId` + `SpanId` [+ `TraceFlags`] in args and routes filtering. - [#4079](https://github.com/open-telemetry/opentelemetry-cpp/pull/4079) +* Bump bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml + [#4142](https://github.com/open-telemetry/opentelemetry-cpp/pull/4142) -* [SDK] Add `LogRecordProcessor::HasEnabledFilter()` so the SDK Logger can - include processor-level filtering in its extended-enabled cache. Defaults - to `true`. Built-in `SimpleLogRecordProcessor` and - `BatchLogRecordProcessor` override to `false` since they use the default - Enabled. - [#4079](https://github.com/open-telemetry/opentelemetry-cpp/pull/4079) +* [CODE HEALTH] Fix clang-tidy bugprone-exception-escape warnings in API + [#3964](https://github.com/open-telemetry/opentelemetry-cpp/pull/3964) -* [API/SDK] Replace `Context`-only signatures on - `LogRecordProcessor::Enabled`, - `LogRecordProcessor::EnabledImplementation`, - `Logger::EnabledImplementation` (v2), and `Logger::CreateLogRecord` (v2) - with `nostd::variant`. +* [API+SDK] apply filtering EmitLogRecord [#4079](https://github.com/open-telemetry/opentelemetry-cpp/pull/4079) -* [CI] iwyu and clang-tidy: use install_thirdparty.sh for third-party +* [CI] iwyu clang tidy install thirdparty script to unify dev container [#4136](https://github.com/open-telemetry/opentelemetry-cpp/pull/4136) -* [CONFIGURATION] File configuration: declarative resource detection types - [#4148](https://github.com/open-telemetry/opentelemetry-cpp/pull/4148) +* [CONTEXT] Fix env carrier spec compliance + [#4141](https://github.com/open-telemetry/opentelemetry-cpp/pull/4141) -* [SDK] Per-instrument creation time as delta first-interval start_ts +* [SDK] Delta temporality: use per-instrument creation time for first interval start_ts [#4144](https://github.com/open-telemetry/opentelemetry-cpp/pull/4144) -* [SDK] Add ComposableSampler and CompositeSampler with the consistent - probability sampling variants (AlwaysOn, AlwaysOff, TraceIdRatioBased, - ParentThreshold, RuleBased) - [#4028](https://github.com/open-telemetry/opentelemetry-cpp/issues/4028) +* Bump bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml + [#4147](https://github.com/open-telemetry/opentelemetry-cpp/pull/4147) + +* [EXPORTER] Add custom HTTP client + [#4071](https://github.com/open-telemetry/opentelemetry-cpp/pull/4071) + +* docs: add CNCF Slack join instructions + [#4153](https://github.com/open-telemetry/opentelemetry-cpp/pull/4153) + +* [SDK] Add ComposableSampler interface and CompositeSampler + [#4133](https://github.com/open-telemetry/opentelemetry-cpp/pull/4133) + +* Bump bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml + [#4150](https://github.com/open-telemetry/opentelemetry-cpp/pull/4150) + +* [CONFIGURATION] Declarative resource detection types + [#4148](https://github.com/open-telemetry/opentelemetry-cpp/pull/4148) * [BUILD] Fix protobuf build failure [#4154](https://github.com/open-telemetry/opentelemetry-cpp/pull/4154) -* [SEMANTIC CONVENTIONS] Generate event name constants (e.g. `semconv::exception::kException`) +* [SEMANTIC CONVENTIONS] Upgrade to semconv 1.42.0 + [#4156](https://github.com/open-telemetry/opentelemetry-cpp/pull/4156) + +* Bump rules_cc from 0.2.18 to 0.2.19 + [#4120](https://github.com/open-telemetry/opentelemetry-cpp/pull/4120) + +* [CMAKE] upgrade grpc and protobuf + [#4167](https://github.com/open-telemetry/opentelemetry-cpp/pull/4167) + +* Bump actions/checkout from 6.0.3 to 7.0.0 + [#4169](https://github.com/open-telemetry/opentelemetry-cpp/pull/4169) + +* [BENCHMARK] add span otlp recordable benchmark + [#4165](https://github.com/open-telemetry/opentelemetry-cpp/pull/4165) + +* [METRICS] Enable synchronous gauge delta temporality + and add tests for multi reader path + [#4159](https://github.com/open-telemetry/opentelemetry-cpp/pull/4159) + +* Add preview support for bound instruments + [#4042](https://github.com/open-telemetry/opentelemetry-cpp/pull/4042) + +* [API/SDK] add `WITH_LOG_FILTERING_PREVIEW` option + [#4160](https://github.com/open-telemetry/opentelemetry-cpp/pull/4160) + +* [SEMANTIC CONVENTIONS] semconv generate events [#4171](https://github.com/open-telemetry/opentelemetry-cpp/pull/4171) * [EXPORTER] Handle OTLP partial success response - [#4104](https://github.com/open-telemetry/opentelemetry-cpp/pull/4104) + [#4104](https://github.com/open-telemetry/openelemetry-cpp/pull/4104) + +* Bump rules_cc from 0.2.19 to 0.2.20 + [#4173](https://github.com/open-telemetry/opentelemetry-cpp/pull/4173) + +* [CI] pin ci workflow Bazel on MacOS runner to macos-15 + [#4176](https://github.com/open-telemetry/opentelemetry-cpp/pull/4176) + +* [ADMIN] Revert log filtering preview option + [#4174](https://github.com/open-telemetry/opentelemetry-cpp/pull/4174) + +* [TEST] fix metrics simple example + [#4175](https://github.com/open-telemetry/opentelemetry-cpp/pull/4175) + +* [EXPORTER] Replace new operator with std::make_unique + [#4181](https://github.com/open-telemetry/opentelemetry-cpp/pull/4181) + +* [DOC] update ABI policy docs to clarify breaking changes + [#4180](https://github.com/open-telemetry/opentelemetry-cpp/pull/4180) + +* [CODE HEALTH] Fix clang-tidy bugprone-exception-escape in ostream exporters + [#4137](https://github.com/open-telemetry/opentelemetry-cpp/pull/4137) + +* [CMAKE] Upgrade ryml to 0.15.2 + [#4185](https://github.com/open-telemetry/opentelemetry-cpp/pull/4185) * [CONFIGURATION] Apply default sampler when none is specified [#4170](https://github.com/open-telemetry/opentelemetry-cpp/pull/4170) +* [CMAKE] Upgrade curl to 8.21.0 + [#4187](https://github.com/open-telemetry/opentelemetry-cpp/pull/4187) + +* Bump actions/cache from 5.0.5 to 6.0.0 + [#4177](https://github.com/open-telemetry/opentelemetry-cpp/pull/4177) + +* Bump actions/cache from 6.0.0 to 6.1.0 + [#4189](https://github.com/open-telemetry/opentelemetry-cpp/pull/4189) + +* [API] fix the trace state regex to comply with the w3c trace context level 2 + [#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194) + +* [CODE HEALTH] Move trace and baggage propagation test classes into anonymous namespace + [#4199](https://github.com/open-telemetry/opentelemetry-cpp/pull/4199) + +* [CMAKE] Add pkg-config support for OpenTelemetry proto installation + [#4192](https://github.com/open-telemetry/opentelemetry-cpp/pull/4192) + +* [CMAKE] Fix CMake WITH_API_ONLY option + [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) + +* [CODE HEALTH] Move context propagation test classes into anonymous namespace + [#4200](https://github.com/open-telemetry/opentelemetry-cpp/pull/4200) + +* [CODE HEALTH] Fix clang-tidy bugprone-unused-local-non-trivial-variable warnings + [#4202](https://github.com/open-telemetry/opentelemetry-cpp/pull/4202) + +* [SDK] LogRecord attribute limits enforcement + [#4157](https://github.com/open-telemetry/opentelemetry-cpp/pull/4157) + +* [SDK] Rename ComposableTraceIdRatioBasedSampler to ComposableProbabilitySampler + [#4210](https://github.com/open-telemetry/opentelemetry-cpp/pull/4210) + +* Bump docker/build-push-action from 7.2.0 to 7.3.0 + [#4204](https://github.com/open-telemetry/opentelemetry-cpp/pull/4204) + +* Bump docker/setup-qemu-action from 4.1.0 to 4.2.0 + [#4205](https://github.com/open-telemetry/opentelemetry-cpp/pull/4205) + +* Bump docker/setup-buildx-action from 4.1.0 to 4.2.0 + [#4207](https://github.com/open-telemetry/opentelemetry-cpp/pull/4207) + +* Bump rules_cc from 0.2.20 to 0.2.21 + [#4214](https://github.com/open-telemetry/opentelemetry-cpp/pull/4214) + +* Bump github/codeql-action/upload-sarif from 4.36.2 to 4.36.3 + [#4213](https://github.com/open-telemetry/opentelemetry-cpp/pull/4213) + +* Bump fossas/fossa-action from 1.9.0 to 2.0.0 + [#4212](https://github.com/open-telemetry/opentelemetry-cpp/pull/4212) + +* Bump github/codeql-action/init from 4.36.2 to 4.36.3 + [#4209](https://github.com/open-telemetry/opentelemetry-cpp/pull/4209) + +* [CODE HEALTH] Move api/test and sdk/test classes into anonymous namespace + [#4217](https://github.com/open-telemetry/opentelemetry-cpp/pull/4217) + +* [CODE HEALTH] Fix clang-tidy bugprone-unchecked-string-to-number-conversion warnings + [#4216](https://github.com/open-telemetry/opentelemetry-cpp/pull/4216) + +* [CODE HEALTH] Fix clang-tidy misc-override-with-different-visibility warnings + [#4215](https://github.com/open-telemetry/opentelemetry-cpp/pull/4215) + +* [BUILD] add missing link dependency for bazel + [#4220](https://github.com/open-telemetry/opentelemetry-cpp/pull/4220) + +* [CODE HEALTH] Fix clang-tidy `bugprone-throwing-static-initialization` warnings + [#4206](https://github.com/open-telemetry/opentelemetry-cpp/pull/4206) + +* Bump rules_cc from 0.2.21 to 0.2.22 + [#4223](https://github.com/open-telemetry/opentelemetry-cpp/pull/4223) + +* Bump step-security/harden-runner from 2.19.4 to 2.20.0 + [#4222](https://github.com/open-telemetry/opentelemetry-cpp/pull/4222) + +* Bump bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml + [#4221](https://github.com/open-telemetry/opentelemetry-cpp/pull/4221) + +* [CODE HEALTH] Move ext and exporters test classes into anonymous namespace + [#4225](https://github.com/open-telemetry/opentelemetry-cpp/pull/4225) + +* Bump github/codeql-action/upload-sarif from 4.36.3 to 4.37.0 + [#4227](https://github.com/open-telemetry/opentelemetry-cpp/pull/4227) + +* Bump github/codeql-action/init from 4.36.3 to 4.37.0 + [#4229](https://github.com/open-telemetry/opentelemetry-cpp/pull/4229) + +* [SEMANTIC CONVENTIONS] Upgrade to semconv 1.43.0 + [#4230](https://github.com/open-telemetry/opentelemetry-cpp/pull/4230) + +* [CODE HEALTH] Move otlp, zipkin and prometheus test classes into anonymous namespace + [#4231](https://github.com/open-telemetry/opentelemetry-cpp/pull/4231) + +* [BAZEL] Upgrade to rapidyaml 1.15.2 + [#4234](https://github.com/open-telemetry/opentelemetry-cpp/pull/4234) + +* [BENCHMARK] add SpanData recordable benchmark and unify common utils + [#4203](https://github.com/open-telemetry/opentelemetry-cpp/pull/4203) + +* [CI] Add `modernize-` and `cppcoreguidelines-pro-` checks to clang-tidy (#4238) + [#4238](https://github.com/open-telemetry/opentelemetry-cpp/pull/4238) + +* [SDK] Apply metric cardinality limits to non-overflow attribute sets and + reserve the overflow point separately. + [#4236](https://github.com/open-telemetry/opentelemetry-cpp/pull/4236) + +* [CONFIGURATION] fix yaml configuration of the logger configurator + [#4241](https://github.com/open-telemetry/opentelemetry-cpp/pull/4241) + +* [SDK] Support LoggerConfigurator updates and example + [#4224](https://github.com/open-telemetry/opentelemetry-cpp/pull/4224) + +* Bump actions/stale from 10.3.0 to 10.4.0 + [#4242](https://github.com/open-telemetry/opentelemetry-cpp/pull/4242) + +* [CMAKE] Remove environment variable support from the add external components script + [#4211](https://github.com/open-telemetry/opentelemetry-cpp/pull/4211) + +* [ETW] Make spans own their names + [#4247](https://github.com/open-telemetry/opentelemetry-cpp/pull/4247) + +* [SDK] Span limits configuration + [#4232](https://github.com/open-telemetry/opentelemetry-cpp/pull/4232) + +* [CONFIGURATION] Define and set spec default values in configuration objects + [#4244](https://github.com/open-telemetry/opentelemetry-cpp/pull/4244) + +* [CMAKE] Upgrade to protobuf 35.1 and grpc 1.82.1 + [#4255](https://github.com/open-telemetry/opentelemetry-cpp/pull/4255) + +* [RELEASE] Release opentelemetry-cpp 1.28.0 + [#4233](https://github.com/open-telemetry/opentelemetry-cpp/pull/4233) + +Important changes: + +* [API] fix the trace state regex to comply with the w3c trace context level 2 (#4194) + [#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194) + + * [API] Fix `TraceState::IsValidKey()` to comply with the W3C Trace Context + Level 2, where keys containing `@` and keys with more than 241 characters + before `@` or more than 14 characters after `@` are now accepted. + Only the total 256-character key length limit is enforced. + **Note**: this is a correctness fix to an inline API header; the observable + behavior of `IsValidKey`, `IsValidKeyRegEx`, and `IsValidKeyNonRegEx` + changes (see `docs/abi-policy.md`). + +* [API+SDK] apply filtering EmitLogRecord (#4079) + [#4079](https://github.com/open-telemetry/opentelemetry-cpp/pull/4079) + + * [API] (ABI v2) `Logger::EmitLogRecord(...)` templates now apply the + `Enabled` filter chain when a `Severity` is in args. v1 behavior is + unchanged. + + * [API/SDK] (ABI v2) Add `Logger::CreateLogRecord` virtual taking + `const nostd::variant &` + for explicit-context record creation. `Logger::EmitLogRecord(args...)` + also detects a `Context`, `SpanContext` + or `TraceId` + `SpanId` [+ `TraceFlags`] in args and routes filtering. + + * [SDK] Add `LogRecordProcessor::HasEnabledFilter()` so the SDK Logger can + include processor-level filtering in its extended-enabled cache. Defaults + to `true`. Built-in `SimpleLogRecordProcessor` and + `BatchLogRecordProcessor` override to `false` since they use the default + Enabled. + + * [API/SDK] Replace `Context`-only signatures on + `LogRecordProcessor::Enabled`, + `LogRecordProcessor::EnabledImplementation`, + `Logger::EnabledImplementation` (v2), and `Logger::CreateLogRecord` (v2) + with `nostd::variant`. + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 1431475bf6..c6f255c49c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20.0") cmake_policy(SET CMP0117 NEW) endif() -set(OPENTELEMETRY_VERSION_NUMBER "1.28.0") +set(OPENTELEMETRY_VERSION_NUMBER "1.29.0") set(OPENTELEMETRY_VERSION_SUFFIX "-dev") set(OPENTELEMETRY_VERSION "${OPENTELEMETRY_VERSION_NUMBER}${OPENTELEMETRY_VERSION_SUFFIX}") @@ -269,7 +269,9 @@ if(WITH_GSL) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/ms-gsl.cmake") endif() -find_package(Threads) +if(NOT WITH_API_ONLY) + find_package(Threads) +endif() function(set_target_version target_name) if(OTELCPP_VERSIONED_LIBS) @@ -306,13 +308,36 @@ cmake_dependent_option( "WITH_OTLP_HTTP OR WITH_ELASTICSEARCH OR WITH_ZIPKIN OR BUILD_W3CTRACECONTEXT_TEST OR WITH_EXAMPLES_HTTP" OFF) +# +# Locally override options if WITH_API_ONLY is set to prevent third party +# dependencies from being imported +# + +if(WITH_API_ONLY) + set(WITH_EXAMPLES OFF) + set(WITH_EXAMPLES_HTTP OFF) + set(WITH_OTLP_GRPC OFF) + set(WITH_OTLP_HTTP OFF) + set(WITH_OTLP_FILE OFF) + set(WITH_ZIPKIN OFF) + set(WITH_PROMETHEUS OFF) + set(WITH_ELASTICSEARCH OFF) + set(WITH_OPENTRACING OFF) + set(WITH_ETW OFF) + set(WITH_CONFIGURATION OFF) + set(WITH_HTTP_CLIENT_CURL OFF) + set(WITH_OTLP_HTTP_COMPRESSION OFF) + set(WITH_BENCHMARK OFF) + set(WITH_FUNC_TESTS OFF) + set(BUILD_TESTING OFF) + set(BUILD_W3CTRACECONTEXT_TEST OFF) +endif() + # # Do we need ZLIB ? # -if((NOT WITH_API_ONLY) - AND WITH_HTTP_CLIENT_CURL - AND WITH_OTLP_HTTP_COMPRESSION) +if(WITH_HTTP_CLIENT_CURL AND WITH_OTLP_HTTP_COMPRESSION) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/zlib.cmake") endif() @@ -320,7 +345,7 @@ endif() # Do we need CURL ? # -if((NOT WITH_API_ONLY) AND WITH_HTTP_CLIENT_CURL) +if(WITH_HTTP_CLIENT_CURL) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/curl.cmake") endif() @@ -366,7 +391,7 @@ else() set(USE_NLOHMANN_JSON OFF) endif() -if((NOT WITH_API_ONLY) AND USE_NLOHMANN_JSON) +if(USE_NLOHMANN_JSON) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/nlohmann-json.cmake") endif() @@ -374,7 +399,7 @@ endif() # Do we need RapidYaml ? # -if((NOT WITH_API_ONLY) AND WITH_CONFIGURATION) +if(WITH_CONFIGURATION) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/ryml.cmake") endif() @@ -640,11 +665,11 @@ endif() add_subdirectory(api) -if(WITH_OPENTRACING) - add_subdirectory(opentracing-shim) -endif() - if(NOT WITH_API_ONLY) + if(WITH_OPENTRACING) + add_subdirectory(opentracing-shim) + endif() + set(BUILD_TESTING ${BUILD_TESTING}) add_subdirectory(sdk) @@ -663,11 +688,12 @@ if(NOT WITH_API_ONLY) if(WITH_FUNC_TESTS) add_subdirectory(functional) endif() + + include( + "${opentelemetry-cpp_SOURCE_DIR}/cmake/opentelemetry-build-external-component.cmake" + ) endif() -include( - "${opentelemetry-cpp_SOURCE_DIR}/cmake/opentelemetry-build-external-component.cmake" -) include("${opentelemetry-cpp_SOURCE_DIR}/cmake/patch-imported-config.cmake") if(OPENTELEMETRY_INSTALL) diff --git a/MODULE.bazel b/MODULE.bazel index 07e443a3c3..e46bf14c66 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,7 +3,7 @@ module( name = "opentelemetry-cpp", - version = "1.28.0-dev", + version = "1.29.0-dev", compatibility_level = 0, repo_name = "io_opentelemetry_cpp", ) @@ -18,8 +18,8 @@ bazel_dep(name = "opentracing-cpp", version = "1.6.0", repo_name = "com_github_o bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "prometheus-cpp", version = "1.3.0.bcr.2", repo_name = "com_github_jupp0r_prometheus_cpp") bazel_dep(name = "protobuf", version = "32.0", repo_name = "com_google_protobuf") -bazel_dep(name = "rapidyaml", version = "0.12.1") -bazel_dep(name = "rules_cc", version = "0.2.20") +bazel_dep(name = "rapidyaml", version = "0.15.2") +bazel_dep(name = "rules_cc", version = "0.2.22") bazel_dep(name = "rules_foreign_cc", version = "0.15.1") bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "zlib", version = "1.3.2") diff --git a/README.md b/README.md index 3890fad172..a5ad3a79cb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ C++ standards: * ISO/IEC 14882:2014 (C++14) * ISO/IEC 14882:2017 (C++17) * ISO/IEC 14882:2020 (C++20) +* ISO/IEC 14882:2024 (C++23) Any exceptions to this are noted in the individual `README.md` files. @@ -37,16 +38,16 @@ of the current project. ## Supported Development Platforms - Our CI pipeline builds and tests on following `x86-64` platforms: +Our CI pipeline builds and tests on the following platforms: -| Platform | Build type | -|---------------------------------------------------------------------|---------------| -| ubuntu-22.04 (GCC 10, GCC 12, Clang 14) | CMake, Bazel | -| ubuntu-20.04 (GCC 9.4.0 - default compiler) | CMake, Bazel | -| ubuntu-20.04 (GCC 9.4.0 with -std=c++14/17/20 flags) | CMake, Bazel | -| macOS 12.7 (Xcode 14.2) | Bazel | -| Windows Server 2019 (Visual Studio Enterprise 2019) | CMake, Bazel | -| Windows Server 2022 (Visual Studio Enterprise 2022) | CMake | +| Platform | Architecture | Build type | +|---------------------|--------------|--------------| +| Ubuntu 22.04 | x86-64 | CMake | +| Ubuntu 24.04 | x86-64 | CMake, Bazel | +| macOS 14 | arm64 | CMake | +| macOS 15 | arm64 | Bazel | +| Windows Server 2022 | x86-64 | CMake, Bazel | +| Windows Server 2025 | x86-64 | CMake | In general, the code shipped from this repository should build on all platforms having C++ compiler with [supported C++ standards](#supported-c-versions). diff --git a/api/include/opentelemetry/baggage/baggage_context.h b/api/include/opentelemetry/baggage/baggage_context.h index e3eedf019f..69d42040c5 100644 --- a/api/include/opentelemetry/baggage/baggage_context.h +++ b/api/include/opentelemetry/baggage/baggage_context.h @@ -13,7 +13,7 @@ OPENTELEMETRY_BEGIN_NAMESPACE namespace baggage { -static const std::string kBaggageHeader = "baggage"; +constexpr char kBaggageHeader[] = "baggage"; inline nostd::shared_ptr GetBaggage(const context::Context &context) noexcept { diff --git a/api/include/opentelemetry/common/key_value_iterable_view.h b/api/include/opentelemetry/common/key_value_iterable_view.h index 750d4207d0..c11e5b5ed7 100644 --- a/api/include/opentelemetry/common/key_value_iterable_view.h +++ b/api/include/opentelemetry/common/key_value_iterable_view.h @@ -39,7 +39,8 @@ std::false_type is_key_value_iterable_impl(...); template struct is_key_value_iterable { - static const bool value = decltype(detail::is_key_value_iterable_impl(std::declval()))::value; + static constexpr bool value = + decltype(detail::is_key_value_iterable_impl(std::declval()))::value; }; } // namespace detail diff --git a/api/include/opentelemetry/semconv/db_attributes.h b/api/include/opentelemetry/semconv/db_attributes.h index f9fc8ed8ff..ceacc11b73 100644 --- a/api/include/opentelemetry/semconv/db_attributes.h +++ b/api/include/opentelemetry/semconv/db_attributes.h @@ -47,10 +47,31 @@ static constexpr const char *kDbCollectionName = "db.collection.name"; static constexpr const char *kDbNamespace = "db.namespace"; /** - The number of queries included in a batch operation. + The number of database operations included in a batch operation.

- Operations are only considered batches when they contain two or more operations, and so @code - db.operation.batch.size @endcode SHOULD never be @code 1 @endcode. + Except for empty batch requests described below, a batch operation contains two + or more database operations explicitly submitted as separate operations in a single + client call, protocol message, or database command. +

+ Requests to batch APIs that contain only one operation SHOULD be modeled as single + operations, not as batch operations. +

+ A database call is not a batch operation solely because one operation accepts + multiple operands, such as keys, rows, documents, points, or other data elements, + including Redis @code MGET @endcode with + multiple keys. +

+ In batch APIs that execute the same parameterized operation with parameter sets, + each parameter set represents one database operation for determining whether the + request is a batch operation. Requests with only one parameter set SHOULD be modeled + as single operations, not as batch operations. +

+ @code db.operation.batch.size @endcode SHOULD be set to the number of operations in the batch. + It SHOULD NOT be set for non-batch operations. +

+ A request to execute a batch operation with no operations SHOULD also be treated + as a batch operation, and @code db.operation.batch.size @endcode SHOULD be set to @code 0 + @endcode. */ static constexpr const char *kDbOperationBatchSize = "db.operation.batch.size"; diff --git a/api/include/opentelemetry/semconv/incubating/azure_attributes.h b/api/include/opentelemetry/semconv/incubating/azure_attributes.h index b8fa13674f..f42a2faf22 100644 --- a/api/include/opentelemetry/semconv/incubating/azure_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/azure_attributes.h @@ -62,6 +62,13 @@ static constexpr const char *kAzureCosmosdbRequestBodySize = "azure.cosmosdb.req static constexpr const char *kAzureCosmosdbResponseSubStatusCode = "azure.cosmosdb.response.sub_status_code"; +/** + The name of the Azure resource + group the resource belongs to. + */ +static constexpr const char *kAzureResourceGroupName = "azure.resource_group.name"; + /** Azure diff --git a/api/include/opentelemetry/semconv/incubating/cicd_metrics.h b/api/include/opentelemetry/semconv/incubating/cicd_metrics.h index 86a9c09bbe..11ffef5f73 100644 --- a/api/include/opentelemetry/semconv/incubating/cicd_metrics.h +++ b/api/include/opentelemetry/semconv/incubating/cicd_metrics.h @@ -178,7 +178,7 @@ CreateAsyncDoubleMetricCicdSystemErrors(metrics::Meter *meter) static constexpr const char *kMetricCicdWorkerCount = "cicd.worker.count"; static constexpr const char *descrMetricCicdWorkerCount = "The number of workers on the CI/CD system by state."; -static constexpr const char *unitMetricCicdWorkerCount = "{count}"; +static constexpr const char *unitMetricCicdWorkerCount = "{worker}"; static inline nostd::unique_ptr> CreateSyncInt64MetricCicdWorkerCount(metrics::Meter *meter) diff --git a/api/include/opentelemetry/semconv/incubating/cloud_attributes.h b/api/include/opentelemetry/semconv/incubating/cloud_attributes.h index 80f22a3d8d..7c7bdfcb5c 100644 --- a/api/include/opentelemetry/semconv/incubating/cloud_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/cloud_attributes.h @@ -21,6 +21,8 @@ namespace cloud /** The cloud account ID the resource is assigned to. +

+ For Azure, this is the subscription ID. */ static constexpr const char *kCloudAccountId = "cloud.account.id"; diff --git a/api/include/opentelemetry/semconv/incubating/db_attributes.h b/api/include/opentelemetry/semconv/incubating/db_attributes.h index 8be4d501e9..41f4b43cb5 100644 --- a/api/include/opentelemetry/semconv/incubating/db_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/db_attributes.h @@ -344,10 +344,31 @@ static constexpr const char *kDbNamespace = "db.namespace"; OPENTELEMETRY_DEPRECATED static constexpr const char *kDbOperation = "db.operation"; /** - The number of queries included in a batch operation. + The number of database operations included in a batch operation.

- Operations are only considered batches when they contain two or more operations, and so @code - db.operation.batch.size @endcode SHOULD never be @code 1 @endcode. + Except for empty batch requests described below, a batch operation contains two + or more database operations explicitly submitted as separate operations in a single + client call, protocol message, or database command. +

+ Requests to batch APIs that contain only one operation SHOULD be modeled as single + operations, not as batch operations. +

+ A database call is not a batch operation solely because one operation accepts + multiple operands, such as keys, rows, documents, points, or other data elements, + including Redis @code MGET @endcode with + multiple keys. +

+ In batch APIs that execute the same parameterized operation with parameter sets, + each parameter set represents one database operation for determining whether the + request is a batch operation. Requests with only one parameter set SHOULD be modeled + as single operations, not as batch operations. +

+ @code db.operation.batch.size @endcode SHOULD be set to the number of operations in the batch. + It SHOULD NOT be set for non-batch operations. +

+ A request to execute a batch operation with no operations SHOULD also be treated + as a batch operation, and @code db.operation.batch.size @endcode SHOULD be set to @code 0 + @endcode. */ static constexpr const char *kDbOperationBatchSize = "db.operation.batch.size"; diff --git a/api/include/opentelemetry/semconv/incubating/k8s_attributes.h b/api/include/opentelemetry/semconv/incubating/k8s_attributes.h index 43dcdf61b9..98a0dca0b3 100644 --- a/api/include/opentelemetry/semconv/incubating/k8s_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/k8s_attributes.h @@ -129,10 +129,10 @@ static constexpr const char *kK8sCronjobUid = "k8s.cronjob.uid"; /** The annotation placed on the DaemonSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

Examples:

  • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.daemonset.annotation.replicas @endcode attribute with value @code "1" @endcode.
  • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

    Examples:

    • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.daemonset.annotation.replicas @endcode attribute with value @code "1" @endcode.
    • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.daemonset.annotation.data @endcode attribute with value @code "" @endcode.
    */ @@ -142,7 +142,7 @@ static constexpr const char *kK8sDaemonsetAnnotation = "k8s.daemonset.annotation The label placed on the DaemonSet, the @code @endcode being the label name, the value being the label value, even if the value is empty.

    Examples:

    • A label @code app @endcode with value @code guestbook @endcode SHOULD be recorded as the @code k8s.daemonset.label.app - @endcode attribute with value @code "guestbook" @endcode.
    • A label @code data @endcode + @endcode attribute with value @code "guestbook" @endcode.
    • A label @code injected @endcode with empty string value SHOULD be recorded as the @code k8s.daemonset.label.injected @endcode attribute with value @code "" @endcode.
    @@ -161,10 +161,10 @@ static constexpr const char *kK8sDaemonsetUid = "k8s.daemonset.uid"; /** The annotation placed on the Deployment, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

    Examples:

    • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.deployment.annotation.replicas @endcode attribute with value @code "1" @endcode.
    • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

      Examples:

      • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.deployment.annotation.replicas @endcode attribute with value @code "1" @endcode.
      • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.deployment.annotation.data @endcode attribute with value @code "" @endcode.
      */ @@ -172,8 +172,8 @@ static constexpr const char *kK8sDeploymentAnnotation = "k8s.deployment.annotati /** The label placed on the Deployment, the @code @endcode being the label name, the value being - the label value, even if the value is empty.

      Examples:

      • A label @code replicas - @endcode with value @code 0 @endcode SHOULD be recorded as the @code k8s.deployment.label.app + the label value, even if the value is empty.

        Examples:

        • A label @code app @endcode + with value @code guestbook @endcode SHOULD be recorded as the @code k8s.deployment.label.app @endcode attribute with value @code "guestbook" @endcode.
        • A label @code injected @endcode with empty string value SHOULD be recorded as the @code k8s.deployment.label.injected @endcode attribute with value @code "" @endcode.
        • @@ -237,11 +237,11 @@ static constexpr const char *kK8sHugepageSize = "k8s.hugepage.size"; /** The annotation placed on the Job, the @code @endcode being the annotation name, the value - being the annotation value, even if the value is empty.

          Examples:

          • A label @code + being the annotation value, even if the value is empty.

            Examples:

            • An annotation @code number @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.job.annotation.number @endcode attribute with value @code "1" @endcode.
            • A label @code - data @endcode with empty string value SHOULD be recorded as the @code k8s.job.annotation.data - @endcode attribute with value @code "" @endcode.
            • + k8s.job.annotation.number @endcode attribute with value @code "1" @endcode.
            • An annotation + @code data @endcode with empty string value SHOULD be recorded as the @code + k8s.job.annotation.data @endcode attribute with value @code "" @endcode.
            */ static constexpr const char *kK8sJobAnnotation = "k8s.job.annotation"; @@ -250,9 +250,9 @@ static constexpr const char *kK8sJobAnnotation = "k8s.job.annotation"; The label placed on the Job, the @code @endcode being the label name, the value being the label value, even if the value is empty.

            Examples:

            • A label @code jobtype @endcode with value @code ci @endcode SHOULD be recorded as the @code k8s.job.label.jobtype @endcode - attribute with value @code "ci" @endcode.
            • A label @code data @endcode with empty string - value SHOULD be recorded as the @code k8s.job.label.automated @endcode attribute with value @code - "" @endcode.
            • + attribute with value @code "ci" @endcode.
            • A label @code automated @endcode with empty + string value SHOULD be recorded as the @code k8s.job.label.automated @endcode attribute with value + @code "" @endcode.
            */ static constexpr const char *kK8sJobLabel = "k8s.job.label"; @@ -269,10 +269,10 @@ static constexpr const char *kK8sJobUid = "k8s.job.uid"; /** The annotation placed on the Namespace, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

            Examples:

            • A label @code - ttl @endcode with value @code 0 @endcode SHOULD be recorded as the @code - k8s.namespace.annotation.ttl @endcode attribute with value @code "0" @endcode.
            • A label - @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

              Examples:

              • An annotation + @code ttl @endcode with value @code 0 @endcode SHOULD be recorded as the @code + k8s.namespace.annotation.ttl @endcode attribute with value @code "0" @endcode.
              • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.namespace.annotation.data @endcode attribute with value @code "" @endcode.
              */ @@ -564,10 +564,10 @@ static constexpr const char *kK8sPodUid = "k8s.pod.uid"; /** The annotation placed on the ReplicaSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

              Examples:

              • A label @code - replicas @endcode with value @code 0 @endcode SHOULD be recorded as the @code - k8s.replicaset.annotation.replicas @endcode attribute with value @code "0" @endcode.
              • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                Examples:

                • An annotation + @code replicas @endcode with value @code 0 @endcode SHOULD be recorded as the @code + k8s.replicaset.annotation.replicas @endcode attribute with value @code "0" @endcode.
                • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.replicaset.annotation.data @endcode attribute with value @code "" @endcode.
                */ @@ -748,10 +748,10 @@ static constexpr const char *kK8sServiceUid = "k8s.service.uid"; /** The annotation placed on the StatefulSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                Examples:

                • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.statefulset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                  Examples:

                  • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.statefulset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                  • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.statefulset.annotation.data @endcode attribute with value @code "" @endcode.
                  */ @@ -759,11 +759,11 @@ static constexpr const char *kK8sStatefulsetAnnotation = "k8s.statefulset.annota /** The label placed on the StatefulSet, the @code @endcode being the label name, the value - being the label value, even if the value is empty.

                  Examples:

                  • A label @code replicas - @endcode with value @code 0 @endcode SHOULD be recorded as the @code k8s.statefulset.label.app - @endcode attribute with value @code "guestbook" @endcode.
                  • A label @code injected @endcode - with empty string value SHOULD be recorded as the @code k8s.statefulset.label.injected @endcode - attribute with value @code "" @endcode.
                  • + being the label value, even if the value is empty.

                    Examples:

                    • A label @code app + @endcode with value @code guestbook @endcode SHOULD be recorded as the @code + k8s.statefulset.label.app @endcode attribute with value @code "guestbook" @endcode.
                    • A + label @code injected @endcode with empty string value SHOULD be recorded as the @code + k8s.statefulset.label.injected @endcode attribute with value @code "" @endcode.
                    */ static constexpr const char *kK8sStatefulsetLabel = "k8s.statefulset.label"; diff --git a/api/include/opentelemetry/semconv/incubating/network_attributes.h b/api/include/opentelemetry/semconv/incubating/network_attributes.h index 34c6a880e1..0ec6e6f624 100644 --- a/api/include/opentelemetry/semconv/incubating/network_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/network_attributes.h @@ -64,7 +64,9 @@ static constexpr const char *kNetworkConnectionType = "network.connection.type"; static constexpr const char *kNetworkInterfaceName = "network.interface.name"; /** - The network IO operation direction. + The direction of traffic from the perspective of the observing host's physical or virtual network + interface. It should not be used to represent the logical direction of a stateful connection or + network flow. */ static constexpr const char *kNetworkIoDirection = "network.io.direction"; diff --git a/api/include/opentelemetry/semconv/incubating/otel_metrics.h b/api/include/opentelemetry/semconv/incubating/otel_metrics.h index 093af10f53..fe0ef1591e 100644 --- a/api/include/opentelemetry/semconv/incubating/otel_metrics.h +++ b/api/include/opentelemetry/semconv/incubating/otel_metrics.h @@ -963,7 +963,9 @@ CreateAsyncDoubleMetricOtelSdkSpanEndedCount(metrics::Meter *meter) /** The number of created spans with @code recording=true @endcode for which the end operation has not - been called yet.

                    updowncounter + been called yet.

                    Non-recording spans are not counted, hence @code otel.span.sampling_result + @endcode can only take values @code RECORD_ONLY @endcode and @code RECORD_AND_SAMPLE @endcode, not + @code DROP @endcode.

                    updowncounter */ static constexpr const char *kMetricOtelSdkSpanLive = "otel.sdk.span.live"; static constexpr const char *descrMetricOtelSdkSpanLive = diff --git a/api/include/opentelemetry/semconv/incubating/telemetry_attributes.h b/api/include/opentelemetry/semconv/incubating/telemetry_attributes.h index 05ef72558a..87ca0ec518 100644 --- a/api/include/opentelemetry/semconv/incubating/telemetry_attributes.h +++ b/api/include/opentelemetry/semconv/incubating/telemetry_attributes.h @@ -69,6 +69,8 @@ static constexpr const char *kGo = "go"; static constexpr const char *kJava = "java"; +static constexpr const char *kKotlin = "kotlin"; + static constexpr const char *kNodejs = "nodejs"; static constexpr const char *kPhp = "php"; diff --git a/api/include/opentelemetry/semconv/k8s_attributes.h b/api/include/opentelemetry/semconv/k8s_attributes.h index 934e668e3e..9ec73f0621 100644 --- a/api/include/opentelemetry/semconv/k8s_attributes.h +++ b/api/include/opentelemetry/semconv/k8s_attributes.h @@ -98,10 +98,10 @@ static constexpr const char *kK8sCronjobUid = "k8s.cronjob.uid"; /** The annotation placed on the DaemonSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                    Examples:

                    • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.daemonset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                    • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                      Examples:

                      • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.daemonset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                      • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.daemonset.annotation.data @endcode attribute with value @code "" @endcode.
                      */ @@ -111,7 +111,7 @@ static constexpr const char *kK8sDaemonsetAnnotation = "k8s.daemonset.annotation The label placed on the DaemonSet, the @code @endcode being the label name, the value being the label value, even if the value is empty.

                      Examples:

                      • A label @code app @endcode with value @code guestbook @endcode SHOULD be recorded as the @code k8s.daemonset.label.app - @endcode attribute with value @code "guestbook" @endcode.
                      • A label @code data @endcode + @endcode attribute with value @code "guestbook" @endcode.
                      • A label @code injected @endcode with empty string value SHOULD be recorded as the @code k8s.daemonset.label.injected @endcode attribute with value @code "" @endcode.
                      @@ -130,10 +130,10 @@ static constexpr const char *kK8sDaemonsetUid = "k8s.daemonset.uid"; /** The annotation placed on the Deployment, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                      Examples:

                      • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.deployment.annotation.replicas @endcode attribute with value @code "1" @endcode.
                      • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                        Examples:

                        • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.deployment.annotation.replicas @endcode attribute with value @code "1" @endcode.
                        • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.deployment.annotation.data @endcode attribute with value @code "" @endcode.
                        */ @@ -141,8 +141,8 @@ static constexpr const char *kK8sDeploymentAnnotation = "k8s.deployment.annotati /** The label placed on the Deployment, the @code @endcode being the label name, the value being - the label value, even if the value is empty.

                        Examples:

                        • A label @code replicas - @endcode with value @code 0 @endcode SHOULD be recorded as the @code k8s.deployment.label.app + the label value, even if the value is empty.

                          Examples:

                          • A label @code app @endcode + with value @code guestbook @endcode SHOULD be recorded as the @code k8s.deployment.label.app @endcode attribute with value @code "guestbook" @endcode.
                          • A label @code injected @endcode with empty string value SHOULD be recorded as the @code k8s.deployment.label.injected @endcode attribute with value @code "" @endcode.
                          • @@ -162,11 +162,11 @@ static constexpr const char *kK8sDeploymentUid = "k8s.deployment.uid"; /** The annotation placed on the Job, the @code @endcode being the annotation name, the value - being the annotation value, even if the value is empty.

                            Examples:

                            • A label @code + being the annotation value, even if the value is empty.

                              Examples:

                              • An annotation @code number @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.job.annotation.number @endcode attribute with value @code "1" @endcode.
                              • A label @code - data @endcode with empty string value SHOULD be recorded as the @code k8s.job.annotation.data - @endcode attribute with value @code "" @endcode.
                              • + k8s.job.annotation.number @endcode attribute with value @code "1" @endcode.
                              • An annotation + @code data @endcode with empty string value SHOULD be recorded as the @code + k8s.job.annotation.data @endcode attribute with value @code "" @endcode.
                              */ static constexpr const char *kK8sJobAnnotation = "k8s.job.annotation"; @@ -175,9 +175,9 @@ static constexpr const char *kK8sJobAnnotation = "k8s.job.annotation"; The label placed on the Job, the @code @endcode being the label name, the value being the label value, even if the value is empty.

                              Examples:

                              • A label @code jobtype @endcode with value @code ci @endcode SHOULD be recorded as the @code k8s.job.label.jobtype @endcode - attribute with value @code "ci" @endcode.
                              • A label @code data @endcode with empty string - value SHOULD be recorded as the @code k8s.job.label.automated @endcode attribute with value @code - "" @endcode.
                              • + attribute with value @code "ci" @endcode.
                              • A label @code automated @endcode with empty + string value SHOULD be recorded as the @code k8s.job.label.automated @endcode attribute with value + @code "" @endcode.
                              */ static constexpr const char *kK8sJobLabel = "k8s.job.label"; @@ -194,10 +194,10 @@ static constexpr const char *kK8sJobUid = "k8s.job.uid"; /** The annotation placed on the Namespace, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                              Examples:

                              • A label @code - ttl @endcode with value @code 0 @endcode SHOULD be recorded as the @code - k8s.namespace.annotation.ttl @endcode attribute with value @code "0" @endcode.
                              • A label - @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                                Examples:

                                • An annotation + @code ttl @endcode with value @code 0 @endcode SHOULD be recorded as the @code + k8s.namespace.annotation.ttl @endcode attribute with value @code "0" @endcode.
                                • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.namespace.annotation.data @endcode attribute with value @code "" @endcode.
                                */ @@ -326,10 +326,10 @@ static constexpr const char *kK8sPodUid = "k8s.pod.uid"; /** The annotation placed on the ReplicaSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                                Examples:

                                • A label @code - replicas @endcode with value @code 0 @endcode SHOULD be recorded as the @code - k8s.replicaset.annotation.replicas @endcode attribute with value @code "0" @endcode.
                                • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                                  Examples:

                                  • An annotation + @code replicas @endcode with value @code 0 @endcode SHOULD be recorded as the @code + k8s.replicaset.annotation.replicas @endcode attribute with value @code "0" @endcode.
                                  • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.replicaset.annotation.data @endcode attribute with value @code "" @endcode.
                                  */ @@ -358,10 +358,10 @@ static constexpr const char *kK8sReplicasetUid = "k8s.replicaset.uid"; /** The annotation placed on the StatefulSet, the @code @endcode being the annotation name, the - value being the annotation value, even if the value is empty.

                                  Examples:

                                  • A label @code - replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code - k8s.statefulset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                                  • A - label @code data @endcode with empty string value SHOULD be recorded as the @code + value being the annotation value, even if the value is empty.

                                    Examples:

                                    • An annotation + @code replicas @endcode with value @code 1 @endcode SHOULD be recorded as the @code + k8s.statefulset.annotation.replicas @endcode attribute with value @code "1" @endcode.
                                    • An + annotation @code data @endcode with empty string value SHOULD be recorded as the @code k8s.statefulset.annotation.data @endcode attribute with value @code "" @endcode.
                                    */ @@ -369,11 +369,11 @@ static constexpr const char *kK8sStatefulsetAnnotation = "k8s.statefulset.annota /** The label placed on the StatefulSet, the @code @endcode being the label name, the value - being the label value, even if the value is empty.

                                    Examples:

                                    • A label @code replicas - @endcode with value @code 0 @endcode SHOULD be recorded as the @code k8s.statefulset.label.app - @endcode attribute with value @code "guestbook" @endcode.
                                    • A label @code injected @endcode - with empty string value SHOULD be recorded as the @code k8s.statefulset.label.injected @endcode - attribute with value @code "" @endcode.
                                    • + being the label value, even if the value is empty.

                                      Examples:

                                      • A label @code app + @endcode with value @code guestbook @endcode SHOULD be recorded as the @code + k8s.statefulset.label.app @endcode attribute with value @code "guestbook" @endcode.
                                      • A + label @code injected @endcode with empty string value SHOULD be recorded as the @code + k8s.statefulset.label.injected @endcode attribute with value @code "" @endcode.
                                      */ static constexpr const char *kK8sStatefulsetLabel = "k8s.statefulset.label"; diff --git a/api/include/opentelemetry/semconv/schema_url.h b/api/include/opentelemetry/semconv/schema_url.h index f0285a5057..d173828de5 100644 --- a/api/include/opentelemetry/semconv/schema_url.h +++ b/api/include/opentelemetry/semconv/schema_url.h @@ -19,6 +19,6 @@ namespace semconv /** * The URL of the OpenTelemetry schema for these keys and values. */ -static constexpr const char *kSchemaUrl = "https://opentelemetry.io/schemas/1.42.0"; +static constexpr const char *kSchemaUrl = "https://opentelemetry.io/schemas/1.43.0"; } // namespace semconv OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/semconv/telemetry_attributes.h b/api/include/opentelemetry/semconv/telemetry_attributes.h index 05ef72558a..87ca0ec518 100644 --- a/api/include/opentelemetry/semconv/telemetry_attributes.h +++ b/api/include/opentelemetry/semconv/telemetry_attributes.h @@ -69,6 +69,8 @@ static constexpr const char *kGo = "go"; static constexpr const char *kJava = "java"; +static constexpr const char *kKotlin = "kotlin"; + static constexpr const char *kNodejs = "nodejs"; static constexpr const char *kPhp = "php"; diff --git a/api/include/opentelemetry/trace/context.h b/api/include/opentelemetry/trace/context.h index 146f0d65f9..90ef5adc41 100644 --- a/api/include/opentelemetry/trace/context.h +++ b/api/include/opentelemetry/trace/context.h @@ -23,6 +23,33 @@ inline nostd::shared_ptr GetSpan(const context::Context &context) noexcept return nostd::shared_ptr(new DefaultSpan(SpanContext::GetInvalid())); } +// Get the SpanContext of the active span from explicit context. Falls back to a SpanContext +// stored directly in the context, and returns an invalid SpanContext if neither is present. +inline SpanContext GetSpanContext(const context::Context &context) noexcept +{ + if (!context.HasKey(kSpanKey)) + { + return SpanContext::GetInvalid(); + } + + const context::ContextValue context_value = context.GetValue(kSpanKey); + + // Get the span metadata from the active span in the context. + if (const nostd::shared_ptr *maybe_span = + nostd::get_if>(&context_value)) + { + return *maybe_span ? (*maybe_span)->GetContext() : SpanContext::GetInvalid(); + } + // Get the span metadata directly from a SpanContext in the context. + // TODO: This path is unused and may be removed in the future. + if (const nostd::shared_ptr *maybe_span_context = + nostd::get_if>(&context_value)) + { + return *maybe_span_context ? **maybe_span_context : SpanContext::GetInvalid(); + } + return SpanContext::GetInvalid(); +} + // Check if the context is from a root span inline bool IsRootSpan(const context::Context &context) noexcept { diff --git a/api/include/opentelemetry/trace/propagation/b3_propagator.h b/api/include/opentelemetry/trace/propagation/b3_propagator.h index a13d2f0767..1202cbe7de 100644 --- a/api/include/opentelemetry/trace/propagation/b3_propagator.h +++ b/api/include/opentelemetry/trace/propagation/b3_propagator.h @@ -139,7 +139,7 @@ class B3Propagator : public B3PropagatorExtractor void Inject(context::propagation::TextMapCarrier &carrier, const context::Context &context) noexcept override { - SpanContext span_context = trace::GetSpan(context)->GetContext(); + SpanContext span_context = trace::GetSpanContext(context); if (!span_context.IsValid()) { return; @@ -171,7 +171,7 @@ class B3PropagatorMultiHeader : public B3PropagatorExtractor void Inject(context::propagation::TextMapCarrier &carrier, const context::Context &context) noexcept override { - SpanContext span_context = GetSpan(context)->GetContext(); + SpanContext span_context = GetSpanContext(context); if (!span_context.IsValid()) { return; diff --git a/api/include/opentelemetry/trace/propagation/http_trace_context.h b/api/include/opentelemetry/trace/propagation/http_trace_context.h index 76b94284ef..65c6e511f1 100644 --- a/api/include/opentelemetry/trace/propagation/http_trace_context.h +++ b/api/include/opentelemetry/trace/propagation/http_trace_context.h @@ -41,7 +41,7 @@ class HttpTraceContext : public context::propagation::TextMapPropagator void Inject(context::propagation::TextMapCarrier &carrier, const context::Context &context) noexcept override { - SpanContext span_context = trace::GetSpan(context)->GetContext(); + SpanContext span_context = trace::GetSpanContext(context); if (!span_context.IsValid()) { return; @@ -191,6 +191,7 @@ class HttpTraceContext : public context::propagation::TextMapPropagator return ExtractContextFromTraceHeaders(trace_parent, trace_state); } +public: bool Fields(nostd::function_ref callback) const noexcept override { return (callback(kTraceParent) && callback(kTraceState)); diff --git a/api/include/opentelemetry/trace/propagation/jaeger.h b/api/include/opentelemetry/trace/propagation/jaeger.h index ac843f97b6..a4ff4ebc9d 100644 --- a/api/include/opentelemetry/trace/propagation/jaeger.h +++ b/api/include/opentelemetry/trace/propagation/jaeger.h @@ -23,7 +23,7 @@ class JaegerPropagator : public context::propagation::TextMapPropagator void Inject(context::propagation::TextMapCarrier &carrier, const context::Context &context) noexcept override { - SpanContext span_context = trace::GetSpan(context)->GetContext(); + SpanContext span_context = trace::GetSpanContext(context); if (!span_context.IsValid()) { return; diff --git a/api/include/opentelemetry/trace/span_context_kv_iterable_view.h b/api/include/opentelemetry/trace/span_context_kv_iterable_view.h index d6234ef1f3..cd014a23fb 100644 --- a/api/include/opentelemetry/trace/span_context_kv_iterable_view.h +++ b/api/include/opentelemetry/trace/span_context_kv_iterable_view.h @@ -48,7 +48,7 @@ std::false_type is_span_context_kv_iterable_impl(...); template struct is_span_context_kv_iterable { - static const bool value = + static constexpr bool value = decltype(detail::is_span_context_kv_iterable_impl(std::declval()))::value; }; } // namespace detail diff --git a/api/include/opentelemetry/trace/trace_state.h b/api/include/opentelemetry/trace/trace_state.h index d50354dbc5..5cab875852 100644 --- a/api/include/opentelemetry/trace/trace_state.h +++ b/api/include/opentelemetry/trace/trace_state.h @@ -205,9 +205,9 @@ class OPENTELEMETRY_EXPORT TraceState /** Returns whether key is a valid key. See https://www.w3.org/TR/trace-context/#key * Identifiers MUST begin with a lowercase letter or a digit, and can only contain * lowercase letters (a-z), digits (0-9), underscores (_), dashes (-), asterisks (*), - * and forward slashes (/). - * For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the vendor name. - * + * forward slashes (/), and at signs (@). + * An at sign (@) is treated as a regular character (keychar) with no structural meaning. + * Total key length must not exceed 256 characters. */ static bool IsValidKey(nostd::string_view key) noexcept { @@ -255,15 +255,9 @@ class OPENTELEMETRY_EXPORT TraceState try { # endif - static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/]{0,255}$"); - static std::regex reg_key_multitenant( - "^[a-z0-9][a-z0-9*_\\-/]{0,240}(@)[a-z0-9][a-z0-9*_\\-/]{0,13}$"); + static std::regex reg_key("^[a-z0-9][a-z0-9*_\\-/@]{0,255}$"); std::string key_s(key.data(), key.size()); - if (std::regex_match(key_s, reg_key) || std::regex_match(key_s, reg_key_multitenant)) - { - return true; - } - return false; + return std::regex_match(key_s, reg_key); # if OPENTELEMETRY_HAVE_EXCEPTIONS } catch (const std::regex_error &) @@ -300,18 +294,12 @@ class OPENTELEMETRY_EXPORT TraceState return false; } - int ats = 0; - for (const char c : key) { if (!IsLowerCaseAlphaOrDigit(c) && c != '_' && c != '-' && c != '@' && c != '*' && c != '/') { return false; } - if ((c == '@') && (++ats > 1)) - { - return false; - } } return true; } diff --git a/api/include/opentelemetry/version.h b/api/include/opentelemetry/version.h index 574d1df49f..6e05c89eb0 100644 --- a/api/include/opentelemetry/version.h +++ b/api/include/opentelemetry/version.h @@ -12,9 +12,9 @@ #endif // NOLINTBEGIN(cppcoreguidelines-macro-to-enum) -#define OPENTELEMETRY_VERSION "1.28.0-dev" +#define OPENTELEMETRY_VERSION "1.29.0-dev" #define OPENTELEMETRY_VERSION_MAJOR 1 -#define OPENTELEMETRY_VERSION_MINOR 28 +#define OPENTELEMETRY_VERSION_MINOR 29 #define OPENTELEMETRY_VERSION_PATCH 0 // NOLINTEND(cppcoreguidelines-macro-to-enum) diff --git a/api/test/baggage/baggage_benchmark.cc b/api/test/baggage/baggage_benchmark.cc index 3901bfecaa..a0a7c13264 100644 --- a/api/test/baggage/baggage_benchmark.cc +++ b/api/test/baggage/baggage_benchmark.cc @@ -102,7 +102,7 @@ void BM_BaggageToHeaderTenEntries(benchmark::State &state) auto baggage = Baggage::FromHeader(header_with_custom_entries(kNumEntries)); while (state.KeepRunning()) { - auto new_baggage = baggage->ToHeader(); + benchmark::DoNotOptimize(baggage->ToHeader()); } } BENCHMARK(BM_BaggageToHeaderTenEntries); @@ -112,7 +112,7 @@ void BM_BaggageToHeader180Entries(benchmark::State &state) auto baggage = Baggage::FromHeader(header_with_custom_entries(Baggage::kMaxKeyValuePairs)); while (state.KeepRunning()) { - auto new_baggage = baggage->ToHeader(); + benchmark::DoNotOptimize(baggage->ToHeader()); } } BENCHMARK(BM_BaggageToHeader180Entries); diff --git a/api/test/baggage/propagation/baggage_propagator_test.cc b/api/test/baggage/propagation/baggage_propagator_test.cc index 6335b2984b..3fc63bc4db 100644 --- a/api/test/baggage/propagation/baggage_propagator_test.cc +++ b/api/test/baggage/propagation/baggage_propagator_test.cc @@ -20,6 +20,9 @@ using namespace opentelemetry; using namespace opentelemetry::baggage::propagation; +namespace +{ + class BaggageCarrierTest : public context::propagation::TextMapCarrier { public: @@ -74,13 +77,13 @@ TEST(BaggagePropagatorTest, ExtractAndInjectBaggage) for (const auto &baggage : baggages) { BaggageCarrierTest carrier1; - carrier1.headers_[baggage::kBaggageHeader.data()] = baggage.first; - context::Context ctx1 = context::Context{}; - context::Context ctx2 = format.Extract(carrier1, ctx1); + carrier1.headers_[baggage::kBaggageHeader] = baggage.first; + context::Context ctx1 = context::Context{}; + context::Context ctx2 = format.Extract(carrier1, ctx1); BaggageCarrierTest carrier2; format.Inject(carrier2, ctx2); - EXPECT_EQ(carrier2.headers_[baggage::kBaggageHeader.data()], baggage.second); + EXPECT_EQ(carrier2.headers_[baggage::kBaggageHeader], baggage.second); std::vector fields; format.Fields([&fields](nostd::string_view field) { @@ -88,7 +91,7 @@ TEST(BaggagePropagatorTest, ExtractAndInjectBaggage) return true; }); EXPECT_EQ(fields.size(), 1); - EXPECT_EQ(fields[0], baggage::kBaggageHeader.data()); + EXPECT_EQ(fields[0], baggage::kBaggageHeader); } } @@ -103,20 +106,22 @@ TEST(BaggagePropagatorTest, InjectEmptyHeader) { // Test empty baggage in context BaggageCarrierTest carrier1; - carrier1.headers_[baggage::kBaggageHeader.data()] = ""; - context::Context ctx1 = context::Context{}; - context::Context ctx2 = format.Extract(carrier1, ctx1); + carrier1.headers_[baggage::kBaggageHeader] = ""; + context::Context ctx1 = context::Context{}; + context::Context ctx2 = format.Extract(carrier1, ctx1); format.Inject(carrier, ctx2); EXPECT_EQ(carrier.headers_.find(baggage::kBaggageHeader), carrier.headers_.end()); } { // Invalid baggage in context BaggageCarrierTest carrier1; - carrier1.headers_[baggage::kBaggageHeader.data()] = "InvalidBaggageData"; - context::Context ctx1 = context::Context{}; - context::Context ctx2 = format.Extract(carrier1, ctx1); + carrier1.headers_[baggage::kBaggageHeader] = "InvalidBaggageData"; + context::Context ctx1 = context::Context{}; + context::Context ctx2 = format.Extract(carrier1, ctx1); format.Inject(carrier, ctx2); EXPECT_EQ(carrier.headers_.find(baggage::kBaggageHeader), carrier.headers_.end()); } } + +} // namespace diff --git a/api/test/context/propagation/composite_propagator_test.cc b/api/test/context/propagation/composite_propagator_test.cc index 49b1668cb5..b2745aa331 100644 --- a/api/test/context/propagation/composite_propagator_test.cc +++ b/api/test/context/propagation/composite_propagator_test.cc @@ -38,6 +38,9 @@ static std::string Hex(const T &id_item) return std::string(buf, sizeof(buf)); } +namespace +{ + class TextMapCarrierTest : public context::propagation::TextMapCarrier { public: @@ -147,3 +150,5 @@ TEST_F(CompositePropagatorTest, Inject) EXPECT_EQ(fields[1], trace::propagation::kTraceState); EXPECT_EQ(fields[2], trace::propagation::kB3CombinedHeader); } + +} // namespace diff --git a/api/test/context/propagation/environment_carrier_test.cc b/api/test/context/propagation/environment_carrier_test.cc index 666630d01d..ef62cdb110 100644 --- a/api/test/context/propagation/environment_carrier_test.cc +++ b/api/test/context/propagation/environment_carrier_test.cc @@ -56,6 +56,9 @@ static std::string Hex(const T &id_item) return std::string(buf, sizeof(buf)); } +namespace +{ + class EnvironmentCarrierTest : public ::testing::Test { protected: @@ -306,3 +309,5 @@ TEST_F(EnvironmentCarrierTest, RoundTrip) EXPECT_EQ(extracted_span->GetContext().IsSampled(), span_context.IsSampled()); EXPECT_TRUE(extracted_span->GetContext().IsRemote()); } + +} // namespace diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 9f0bfcf481..932a28455c 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -263,6 +263,7 @@ TEST(Logger, EventLogMethodOverloads) // Define a basic Logger class class TestLogger : public Logger { +public: const nostd::string_view GetName() noexcept override { return "test logger"; } using Logger::CreateLogRecord; @@ -459,6 +460,7 @@ class EnablementAwareTestLogger : public Logger // Define a basic LoggerProvider class that returns an instance of the logger class defined above class TestProvider : public LoggerProvider { +public: nostd::shared_ptr GetLogger(nostd::string_view /* logger_name */, nostd::string_view /* library_name */, nostd::string_view /* library_version */, diff --git a/api/test/logs/provider_test.cc b/api/test/logs/provider_test.cc index 83c66a7a07..817092b4b6 100644 --- a/api/test/logs/provider_test.cc +++ b/api/test/logs/provider_test.cc @@ -30,6 +30,7 @@ namespace nostd = opentelemetry::nostd; class TestProvider : public LoggerProvider { +public: nostd::shared_ptr GetLogger( nostd::string_view /* logger_name */, nostd::string_view /* library_name */, diff --git a/api/test/nostd/shared_ptr_test.cc b/api/test/nostd/shared_ptr_test.cc index de57293e09..782eb193f4 100644 --- a/api/test/nostd/shared_ptr_test.cc +++ b/api/test/nostd/shared_ptr_test.cc @@ -13,6 +13,9 @@ using opentelemetry::nostd::shared_ptr; +namespace +{ + class A { public: @@ -237,3 +240,5 @@ TEST(SharedPtrTest, Sort) { SharedPtrTest_Sort(); } + +} // namespace diff --git a/api/test/nostd/unique_ptr_test.cc b/api/test/nostd/unique_ptr_test.cc index 8307135355..83562092cc 100644 --- a/api/test/nostd/unique_ptr_test.cc +++ b/api/test/nostd/unique_ptr_test.cc @@ -10,6 +10,9 @@ using opentelemetry::nostd::unique_ptr; +namespace +{ + class A { public: @@ -174,3 +177,5 @@ TEST(UniquePtrTest, Comparison) EXPECT_EQ(ptr3, nullptr); EXPECT_EQ(nullptr, ptr3); } + +} // namespace diff --git a/api/test/nostd/variant_test.cc b/api/test/nostd/variant_test.cc index 5ebadd6eac..220f5b4bed 100644 --- a/api/test/nostd/variant_test.cc +++ b/api/test/nostd/variant_test.cc @@ -8,6 +8,9 @@ namespace nostd = opentelemetry::nostd; +namespace +{ + class DestroyCounter { public: @@ -120,3 +123,5 @@ TEST(VariantTest, Construction) nostd::variant v{"abc"}; EXPECT_EQ(v.index(), 1); } + +} // namespace diff --git a/api/test/singleton/singleton_test.cc b/api/test/singleton/singleton_test.cc index 66722fe96f..944f0d6b83 100644 --- a/api/test/singleton/singleton_test.cc +++ b/api/test/singleton/singleton_test.cc @@ -163,6 +163,9 @@ static void reset_counts() unknown_span_count = 0; } +namespace +{ + class MyTracer : public trace::Tracer { public: @@ -458,3 +461,5 @@ TEST(SingletonTest, Uniqueness) EXPECT_EQ(span_h_f2_count, 0); EXPECT_EQ(unknown_span_count, 0); } + +} // namespace diff --git a/api/test/trace/context_test.cc b/api/test/trace/context_test.cc index 1081fb0e7a..b72f41abf1 100644 --- a/api/test/trace/context_test.cc +++ b/api/test/trace/context_test.cc @@ -69,6 +69,48 @@ TEST(TraceContextTest, GetSpan) } } +TEST(TraceContextTest, GetSpanContext) +{ + { + context_api::Context context; + EXPECT_FALSE(trace_api::GetSpanContext(context).IsValid()); + } + + { + context_api::Context context; + auto input_span = MakeValidSpan(); + auto context_with_span = trace_api::SetSpan(context, input_span); + auto span_context = trace_api::GetSpanContext(context_with_span); + EXPECT_TRUE(span_context.IsValid()); + EXPECT_EQ(span_context, input_span->GetContext()); + } + + { + context_api::Context context; + auto context_with_null_span = + context.SetValue(trace_api::kSpanKey, nostd::shared_ptr{}); + EXPECT_FALSE(trace_api::GetSpanContext(context_with_null_span).IsValid()); + } + + { + context_api::Context context; + const auto input_span_context = MakeValidSpan()->GetContext(); + auto context_with_span_context = context.SetValue( + trace_api::kSpanKey, + nostd::shared_ptr{new trace_api::SpanContext{input_span_context}}); + auto span_context = trace_api::GetSpanContext(context_with_span_context); + EXPECT_TRUE(span_context.IsValid()); + EXPECT_EQ(span_context, input_span_context); + } + + { + context_api::Context context; + auto context_with_null_span_context = + context.SetValue(trace_api::kSpanKey, nostd::shared_ptr{}); + EXPECT_FALSE(trace_api::GetSpanContext(context_with_null_span_context).IsValid()); + } +} + TEST(TraceContextTest, SetSpan) { context_api::Context context; diff --git a/api/test/trace/propagation/b3_propagation_test.cc b/api/test/trace/propagation/b3_propagation_test.cc index f34dad12ad..fefe2effdd 100644 --- a/api/test/trace/propagation/b3_propagation_test.cc +++ b/api/test/trace/propagation/b3_propagation_test.cc @@ -27,6 +27,9 @@ using namespace opentelemetry; +namespace +{ + class TextMapCarrierTest : public context::propagation::TextMapCarrier { public: @@ -219,3 +222,5 @@ TEST(B3PropagationTest, GetCurrentSpanMultiHeader) EXPECT_EQ(carrier.headers_["X-B3-SpanId"], "0102030405060708"); EXPECT_EQ(carrier.headers_["X-B3-Sampled"], "1"); } + +} // namespace diff --git a/api/test/trace/propagation/http_text_format_test.cc b/api/test/trace/propagation/http_text_format_test.cc index 78ae5f6ef4..6c105a82ce 100644 --- a/api/test/trace/propagation/http_text_format_test.cc +++ b/api/test/trace/propagation/http_text_format_test.cc @@ -32,6 +32,9 @@ using namespace opentelemetry; +namespace +{ + class TextMapCarrierTest : public context::propagation::TextMapCarrier { public: @@ -266,3 +269,5 @@ TEST(GlobalPropagator, SetAndGet) EXPECT_EQ(fields[0], trace::propagation::kTraceParent); EXPECT_EQ(fields[1], trace::propagation::kTraceState); } + +} // namespace diff --git a/api/test/trace/propagation/jaeger_propagation_test.cc b/api/test/trace/propagation/jaeger_propagation_test.cc index e7e7feffee..0b101fbe57 100644 --- a/api/test/trace/propagation/jaeger_propagation_test.cc +++ b/api/test/trace/propagation/jaeger_propagation_test.cc @@ -30,6 +30,9 @@ using namespace opentelemetry; +namespace +{ + class TextMapCarrierTest : public context::propagation::TextMapCarrier { public: @@ -204,3 +207,5 @@ TEST(JaegerPropagatorTest, DoNotInjectInvalidContext) format.Inject(carrier, ctx); EXPECT_TRUE(carrier.headers_.count("uber-trace-id") == 0); } + +} // namespace diff --git a/api/test/trace/provider_test.cc b/api/test/trace/provider_test.cc index 441d7f226d..9745c51191 100644 --- a/api/test/trace/provider_test.cc +++ b/api/test/trace/provider_test.cc @@ -17,7 +17,7 @@ namespace nostd = opentelemetry::nostd; class TestProvider : public TracerProvider { - +public: #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::shared_ptr GetTracer( nostd::string_view /* name */, diff --git a/api/test/trace/trace_state_test.cc b/api/test/trace/trace_state_test.cc index 294e88905a..284006fd1a 100644 --- a/api/test/trace/trace_state_test.cc +++ b/api/test/trace/trace_state_test.cc @@ -62,6 +62,9 @@ TEST(TraceStateTest, ValidateHeaderParsing) {"k1=v1,k2=v2,invalidmember", ""}, {"1a-2f@foo=bar1,a*/foo-_/bar=bar4", "1a-2f@foo=bar1,a*/foo-_/bar=bar4"}, {"1a-2f@foo=bar1,*/foo-_/bar=bar4", ""}, + {"foo@@bar=1,baz=2", "foo@@bar=1,baz=2"}, + {"foo@bar@baz=1", "foo@bar@baz=1"}, + {"@foo=1", ""}, {",k1=v1", "k1=v1"}, {",", ""}, {",=,", ""}, @@ -71,6 +74,12 @@ TEST(TraceStateTest, ValidateHeaderParsing) { EXPECT_EQ(create_ts_return_header(testcase.input), testcase.expected); } + // Key length boundaries: 256-char key valid, 257-char invalid + EXPECT_NE(create_ts_return_header(std::string(256, 'z') + "=1"), ""); + EXPECT_EQ(create_ts_return_header(std::string(257, 'z') + "=1"), ""); + // Old vendor-section limits (241 before @, 14 after) are gone; only total length matters + EXPECT_NE(create_ts_return_header(std::string(242, 't') + "@v=1"), ""); // 244-char key with @ + EXPECT_NE(create_ts_return_header("t@" + std::string(15, 'v') + "=1"), ""); // 15 chars after @ } TEST(TraceStateTest, ExceedsMaxKeyValuePairs) @@ -165,10 +174,23 @@ TEST(TraceStateTest, GetAllEntries) TEST(TraceStateTest, IsValidKey) { EXPECT_TRUE(TraceState::IsValidKey("valid-key23/*")); + // @ is a regular keychar per W3C Trace Context Level 2 + EXPECT_TRUE(TraceState::IsValidKey("foo@")); + EXPECT_TRUE(TraceState::IsValidKey("foo@@bar")); + EXPECT_TRUE(TraceState::IsValidKey("foo@bar@baz")); + EXPECT_FALSE(TraceState::IsValidKey("@foo")); // must start with lcalpha or DIGIT EXPECT_FALSE(TraceState::IsValidKey("Invalid_key")); EXPECT_FALSE(TraceState::IsValidKey("invalid$Key&")); EXPECT_FALSE(TraceState::IsValidKey("")); EXPECT_FALSE(TraceState::IsValidKey(kLongString)); + // Key length boundary: exactly 256 chars valid, 257 invalid + EXPECT_TRUE(TraceState::IsValidKey(std::string(256, 'z'))); + EXPECT_FALSE(TraceState::IsValidKey(std::string(257, 'z'))); + // Old vendor-section limits (241 before @, 14 after) are gone; only total length matters + EXPECT_TRUE( + TraceState::IsValidKey(std::string(241, 't') + "@" + std::string(14, 'v'))); // 256 chars + EXPECT_TRUE(TraceState::IsValidKey(std::string(242, 't') + "@v")); // 244 chars + EXPECT_TRUE(TraceState::IsValidKey("t@" + std::string(15, 'v'))); // 17 chars } TEST(TraceStateTest, IsValidValue) diff --git a/buildscripts/semantic-convention/generate.sh b/buildscripts/semantic-convention/generate.sh index 4c93eb08a3..7caa66af92 100755 --- a/buildscripts/semantic-convention/generate.sh +++ b/buildscripts/semantic-convention/generate.sh @@ -16,10 +16,10 @@ ROOT_DIR="${SCRIPT_DIR}/../../" # freeze the spec & generator tools versions to make the generation reproducible # repository: https://github.com/open-telemetry/semantic-conventions -SEMCONV_VERSION=1.42.0 +SEMCONV_VERSION=1.43.0 # repository: https://github.com/open-telemetry/weaver -WEAVER_VERSION=0.23.0 +WEAVER_VERSION=0.24.2 SEMCONV_VERSION_TAG=v$SEMCONV_VERSION WEAVER_VERSION_TAG=v$WEAVER_VERSION diff --git a/ci/do_ci.sh b/ci/do_ci.sh index c6b85c988b..cc825c37f1 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -538,7 +538,17 @@ elif [[ "$1" == "cmake.fetch_content.test" ]]; then cmake --build . "${CMAKE_BUILD_ARGS[@]}" ctest --output-on-failure exit 0 - +elif [[ "$1" == "cmake.api_only.test" ]]; then + cd "${BUILD_DIR}" + rm -rf * + cmake "${CMAKE_OPTIONS[@]}" \ + -C ${SRC_DIR}/test_common/cmake/all-options-abiv1-preview.cmake \ + -DOPENTELEMETRY_INSTALL=OFF \ + -DOPENTELEMETRY_CPP_SRC_DIR="${SRC_DIR}" \ + "${SRC_DIR}/install/test/cmake/api_only_test" + cmake --build . "${CMAKE_BUILD_ARGS[@]}" + ctest --output-on-failure + exit 0 elif [[ "$1" == "cmake.test_example_plugin" ]]; then # Build the plugin cd "${BUILD_DIR}" diff --git a/cmake/grpc.cmake b/cmake/grpc.cmake index a420ccb353..3b0cf2af4f 100644 --- a/cmake/grpc.cmake +++ b/cmake/grpc.cmake @@ -23,7 +23,6 @@ if(NOT gRPC_FOUND) "third_party/abseil-cpp" "third_party/protobuf" "third_party/cares/cares" - "third_party/boringssl-with-bazel" ) set(gRPC_PROVIDER "fetch_repository") @@ -38,6 +37,7 @@ if(NOT gRPC_FOUND) set(gRPC_BUILD_GRPC_PYTHON_PLUGIN OFF CACHE BOOL "" FORCE) set(gRPC_BUILD_GRPC_RUBY_PLUGIN OFF CACHE BOOL "" FORCE) set(gRPC_BUILD_GRPCPP_OTEL_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_SSL_PROVIDER "package" CACHE STRING "" FORCE) set(gRPC_ZLIB_PROVIDER "package" CACHE STRING "" FORCE) set(gRPC_RE2_PROVIDER "module" CACHE STRING "" FORCE) set(RE2_BUILD_TESTING OFF CACHE BOOL "" FORCE) diff --git a/cmake/opentelemetry-build-external-component.cmake b/cmake/opentelemetry-build-external-component.cmake index 74b6515209..0c4fd39c12 100644 --- a/cmake/opentelemetry-build-external-component.cmake +++ b/cmake/opentelemetry-build-external-component.cmake @@ -13,37 +13,16 @@ endfunction() # Enable building external components through otel-cpp build # The config options are # - OPENTELEMETRY_EXTERNAL_COMPONENT_PATH - Setting local paths of the external -# component as env variable. Multiple paths can be set by separating them with. -# - OPENTELEMETRY_EXTERNAL_COMPONENT_URL Setting github-repo of external component -# as env variable +# component. Multiple paths can be set by separating them with semicolons. -# Add custom vendor component from local path, or GitHub repo -# Prefer CMake option, then env variable, then URL. +# Add custom vendor component from local path +# CMake option. if(OPENTELEMETRY_EXTERNAL_COMPONENT_PATH) + message(STATUS "Building external components from local path(s): ${OPENTELEMETRY_EXTERNAL_COMPONENT_PATH}") # Add custom component path to build tree and consolidate binary artifacts in # current project binary output directory. foreach(DIR IN LISTS OPENTELEMETRY_EXTERNAL_COMPONENT_PATH) get_directory_name_in_path(${DIR} EXTERNAL_EXPORTER_DIR_NAME) add_subdirectory(${DIR} ${PROJECT_BINARY_DIR}/external/${EXTERNAL_EXPORTER_DIR_NAME}) endforeach() -elseif(DEFINED ENV{OPENTELEMETRY_EXTERNAL_COMPONENT_PATH}) - # Add custom component path to build tree and consolidate binary artifacts in - # current project binary output directory. - set(OPENTELEMETRY_EXTERNAL_COMPONENT_PATH_VAR $ENV{OPENTELEMETRY_EXTERNAL_COMPONENT_PATH}) - foreach(DIR IN LISTS OPENTELEMETRY_EXTERNAL_COMPONENT_PATH_VAR) - get_directory_name_in_path(${DIR} EXTERNAL_EXPORTER_DIR_NAME) - add_subdirectory(${DIR} ${PROJECT_BINARY_DIR}/external/${EXTERNAL_EXPORTER_DIR_NAME}) - endforeach() -elseif(DEFINED $ENV{OPENTELEMETRY_EXTERNAL_COMPONENT_URL}) - # This option requires CMake 3.11+: add standard remote repo to build tree. - include(FetchContent) - FetchContent_Declare( - external - GIT_REPOSITORY $ENV{OPENTELEMETRY_EXTERNAL_COMPONENT_URL} - GIT_TAG "main") - FetchContent_GetProperties(external) - if(NOT external_POPULATED) - FetchContent_Populate(external) - add_subdirectory(${external_SOURCE_DIR}) - endif() endif() diff --git a/cmake/opentelemetry-proto.cmake b/cmake/opentelemetry-proto.cmake index c77a87488d..4c512071e9 100644 --- a/cmake/opentelemetry-proto.cmake +++ b/cmake/opentelemetry-proto.cmake @@ -395,15 +395,6 @@ endif() set_target_properties(opentelemetry_proto PROPERTIES EXPORT_NAME proto) patch_protobuf_targets(opentelemetry_proto) -if(OPENTELEMETRY_INSTALL) - install( - DIRECTORY ${GENERATED_PROTOBUF_PATH}/opentelemetry - DESTINATION include - COMPONENT exporters_otlp_common - FILES_MATCHING - PATTERN "*.h") -endif() - if(TARGET protobuf::libprotobuf) target_link_libraries(opentelemetry_proto PUBLIC protobuf::libprotobuf) else() # cmake 3.8 or lower @@ -424,3 +415,30 @@ if(BUILD_SHARED_LIBS) set_property(TARGET ${proto_target} PROPERTY POSITION_INDEPENDENT_CODE ON) endforeach() endif() + +if(OPENTELEMETRY_INSTALL) + install( + DIRECTORY ${GENERATED_PROTOBUF_PATH}/opentelemetry + DESTINATION include + COMPONENT exporters_otlp_common + FILES_MATCHING + PATTERN "*.h") + + include(${PROJECT_SOURCE_DIR}/cmake/pkgconfig.cmake) + + opentelemetry_add_pkgconfig( + proto + "OpenTelemetry - Protocol" + "Protocols for spans, metrics, and logs." + "protobuf" + ) + + if(WITH_OTLP_GRPC) + opentelemetry_add_pkgconfig( + proto_grpc + "OpenTelemetry - Protocol (gRPC)" + "Protocols for spans, metrics, and logs over gRPC." + "opentelemetry_proto grpc++ protobuf" + ) + endif() +endif() diff --git a/cmake/pkgconfig.cmake b/cmake/pkgconfig.cmake index 7bff4cb48c..63c1a2affd 100644 --- a/cmake/pkgconfig.cmake +++ b/cmake/pkgconfig.cmake @@ -1,6 +1,8 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 +include_guard(GLOBAL) + # Unlike functions, macros do not introduce a scope. This is an advantage when # trying to set global variables, as we do here. macro (opentelemetry_set_pkgconfig_paths) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 26a3159a10..e1237e5308 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,6 +29,7 @@ add_subdirectory(multithreaded) add_subdirectory(multi_processor) add_subdirectory(environment_carrier) add_subdirectory(tracer_configurator) +add_subdirectory(logger_configurator) add_subdirectory(explicit_parent) if(WITH_EXAMPLES_HTTP) diff --git a/examples/configuration/custom_pull_metric_exporter.h b/examples/configuration/custom_pull_metric_exporter.h index d7fc3b3282..5082b9bf2a 100644 --- a/examples/configuration/custom_pull_metric_exporter.h +++ b/examples/configuration/custom_pull_metric_exporter.h @@ -21,12 +21,12 @@ class CustomPullMetricExporter : public opentelemetry::sdk::metrics::MetricReade opentelemetry::sdk::metrics::AggregationTemporality GetAggregationTemporality( opentelemetry::sdk::metrics::InstrumentType instrument_type) const noexcept override; +private: bool OnForceFlush(std::chrono::microseconds timeout) noexcept override; bool OnShutDown(std::chrono::microseconds timeout) noexcept override; void OnInitialized() noexcept override; -private: std::string comment_; }; diff --git a/examples/configuration/main.cc b/examples/configuration/main.cc index 77bd4c747e..2c37dd3a0f 100644 --- a/examples/configuration/main.cc +++ b/examples/configuration/main.cc @@ -54,11 +54,11 @@ # include "opentelemetry/exporters/prometheus/prometheus_pull_builder.h" #endif -static bool opt_help = false; -static bool opt_debug = false; -static bool opt_test = false; -static bool opt_no_registry = false; -static std::string yaml_file_path = ""; +static bool opt_help = false; +static bool opt_debug = false; +static bool opt_test = false; +static bool opt_no_registry = false; +static std::string yaml_file_path; static std::unique_ptr sdk; diff --git a/examples/grpc/client.cc b/examples/grpc/client.cc index a9e08a9125..c0987ba3f1 100644 --- a/examples/grpc/client.cc +++ b/examples/grpc/client.cc @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include @@ -124,7 +124,7 @@ int main(int argc, char **argv) uint16_t port{default_port}; if (argc > 1) { - port = static_cast(atoi(argv[1])); + port = static_cast(std::strtol(argv[1], nullptr, 10)); } RunClient(port); diff --git a/examples/grpc/server.cc b/examples/grpc/server.cc index 7bd1533316..4044282572 100644 --- a/examples/grpc/server.cc +++ b/examples/grpc/server.cc @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -122,7 +122,7 @@ int main(int argc, char **argv) uint16_t port{default_port}; if (argc > 1) { - port = static_cast(atoi(argv[1])); + port = static_cast(std::strtol(argv[1], nullptr, 10)); } RunServer(port); diff --git a/examples/http/client.cc b/examples/http/client.cc index 529d1b8c75..b919e86430 100644 --- a/examples/http/client.cc +++ b/examples/http/client.cc @@ -21,7 +21,7 @@ #include "tracer_common.h" #include -#include +#include #include #include #include @@ -106,7 +106,7 @@ int main(int argc, char *argv[]) // The port the validation service listens to can be specified via the command line. if (argc > 1) { - port = static_cast(atoi(argv[1])); + port = static_cast(std::strtol(argv[1], nullptr, 10)); } std::string url = "http://" + std::string(default_host) + ":" + std::to_string(port) + diff --git a/examples/http/server.cc b/examples/http/server.cc index 4c689830cc..3452e7a495 100644 --- a/examples/http/server.cc +++ b/examples/http/server.cc @@ -20,8 +20,8 @@ #include "opentelemetry/trace/tracer.h" #include "tracer_common.h" -#include #include +#include #include #include #include @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) // The port the validation service listens to can be specified via the command line. if (argc > 1) { - server_port = static_cast(atoi(argv[1])); + server_port = static_cast(std::strtol(argv[1], nullptr, 10)); } HttpServer http_server(server_name, server_port); diff --git a/examples/logger_configurator/CMakeLists.txt b/examples/logger_configurator/CMakeLists.txt new file mode 100644 index 0000000000..8d89f6b51d --- /dev/null +++ b/examples/logger_configurator/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +add_executable(example_logger_configurator main.cc) +target_link_libraries( + example_logger_configurator + PRIVATE opentelemetry-cpp::logs + opentelemetry-cpp::ostream_log_record_exporter) + +if(BUILD_TESTING) + add_test(NAME examples.logger_configurator + COMMAND "$") +endif() diff --git a/examples/logger_configurator/README.md b/examples/logger_configurator/README.md new file mode 100644 index 0000000000..de50cf77dc --- /dev/null +++ b/examples/logger_configurator/README.md @@ -0,0 +1,420 @@ +# Logger Configurator Example + +This example demonstrates how to set a `LoggerConfigurator` on construction +of the `LoggerProvider` and to update it at runtime using +`LoggerProvider::UpdateLoggerConfigurator` to control the minimum severity +and enabled state of specific loggers without restarting the application or +recreating the loggers. + +`LoggerProvider::UpdateLoggerConfigurator` is thread-safe and updates the +`LoggerConfig` on all existing loggers. + +Three loggers with unique instrumentation scope names are used to simulate +a user application: + +- `my_application`: simulated user application +- `my_library`: simulated user library +- `external_library`: simulated external third-party library + +The example walks through a simulated debugging workflow in four stages: + +- Stage 1: Production baseline. `my_application` and `my_library` loggers + with Warn minmum severity and the + `external_library` logger is disabled. +- Stage 2: User reports unexpected behavior. Lower `my_application` and + `my_library` loggers to Debug and `external_library` logger remains disabled. +- Stage 3: Debug logs implicate `external_library`. Enable its logger at Debug and + Raise `my_application` back to Warn. +- Stage 4: Root cause found and fixed. Restore the production baseline. + +## Build and run + +```sh +~/build/examples/logger_configurator/example_logger_configurator +``` + +**Expected output:** + +Log records are exported to stdout via the `OStreamLogRecordExporter`. + +```sh +Stage 1: production baseline + my_application and my_library at Warn. external_library disabled +{ + timestamp : 0 + observed_timestamp : 1783528026109982345 + severity_num : 13 + severity_text : WARN + body : MyApplication: my_library invoke failed with an error + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_application + version : + schema_url : + attributes : +} + +Stage 2: investigating, loggers lowered to Debug + my_application and my_library at Debug. external_library disabled +{ + timestamp : 0 + observed_timestamp : 1783528026110271494 + severity_num : 5 + severity_text : DEBUG + body : MyApplication: Execute called... + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_application + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026110499958 + severity_num : 5 + severity_text : DEBUG + body : MyModule: Execute called... + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026110864190 + severity_num : 5 + severity_text : DEBUG + body : MyModule: external_library returned an error + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026111027097 + severity_num : 5 + severity_text : DEBUG + body : MyModule: Execute complete + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026111207651 + severity_num : 13 + severity_text : WARN + body : MyApplication: my_library invoke failed with an error + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_application + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026111366894 + severity_num : 5 + severity_text : DEBUG + body : MyApplication: Execute complete + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_application + version : + schema_url : + attributes : +} + +Stage 3: narrowing down, my_library and external_library at Debug + my_library and external_library at Debug. my_application at Warn +{ + timestamp : 0 + observed_timestamp : 1783528026111618733 + severity_num : 5 + severity_text : DEBUG + body : MyModule: Execute called... + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026111778229 + severity_num : 5 + severity_text : DEBUG + body : ExternalModule: Execute called... + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : external_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112008544 + severity_num : 5 + severity_text : DEBUG + body : ExternalModule: executing... + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : external_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112254877 + severity_num : 17 + severity_text : ERROR + body : ExternalModule: error detected! + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : external_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112412571 + severity_num : 5 + severity_text : DEBUG + body : ExternalModule: some error details. + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : external_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112638912 + severity_num : 5 + severity_text : DEBUG + body : ExternalModule: Execute complete + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : external_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112799800 + severity_num : 5 + severity_text : DEBUG + body : MyModule: external_library returned an error + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112858234 + severity_num : 5 + severity_text : DEBUG + body : MyModule: Execute complete + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_library + version : + schema_url : + attributes : +} +{ + timestamp : 0 + observed_timestamp : 1783528026112886063 + severity_num : 13 + severity_text : WARN + body : MyApplication: my_library invoke failed with an error + resource : + telemetry.sdk.version: 1.28.0-dev + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + service.name: logger_configurator_example + attributes : + event_id : 0 + event_name : + trace_id : 00000000000000000000000000000000 + span_id : 0000000000000000 + trace_flags : 00 + scope : + name : my_application + version : + schema_url : + attributes : +} + +Stage 4: external_library error resolved, production baseline restored + my_application and my_library at Warn. external_library disabled +``` diff --git a/examples/logger_configurator/main.cc b/examples/logger_configurator/main.cc new file mode 100644 index 0000000000..e2a414df41 --- /dev/null +++ b/examples/logger_configurator/main.cc @@ -0,0 +1,294 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// This example shows how to use LoggerProvider::UpdateLoggerConfigurator to dynamically control +// the minimum severity level of loggers at runtime. Updating the LoggerConfigurator +// affects all existing and future loggers provided by the LoggerProvider. It is thread-safe and +// can be called concurrently with logger and log record creation. +// +// Three instrumentation scopes are shown: +// 1. "my_application" (example instrumented user application code), +// 2. "my_library" (example instrumented user library code), +// 3. "external_library" (example instrumented third-party dependency). +// +// The example simulates a typical debugging workflow: +// +// Stage 1: Production baseline. my_application and my_library loggers at Warn. +// external_library logger is disabled. +// Stage 2: A user reports unexpected behavior. Lower my_application and my_library loggers to +// Debug. +// external_library logger remains disabled. +// Stage 3: Debug logs implicate external_library. Re-enable its logger at Debug. +// Raise my_application logger back to Warn. +// Stage 4: Root cause found and fixed. Restore the production baseline. + +#include +#include +#include +#include +#include + +#include +#include +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/processor.h" + +#include "opentelemetry/exporters/ostream/log_record_exporter_factory.h" +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/logs/severity.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/logger_config.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/semconv/service_attributes.h" + +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace logs_exporters = opentelemetry::exporter::logs; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace nostd = opentelemetry::nostd; + +namespace +{ + +bool external_library_has_error = true; // simulate an error condition in the external library + +// --------------------------------------------------------------------------- +// EmitLogRecord Helper: For ABIv1 checks Enabled() based on severity of the record and emits. +// For ABI v2 EmitLogRecord applies filtering implicitly based on the LoggerConfig. +// --------------------------------------------------------------------------- +void Log(const nostd::shared_ptr &logger, + logs_api::Severity severity, + nostd::string_view message) noexcept +{ +#if OPENTELEMETRY_ABI_VERSION_NO <= 1 + // EmitLogRecord does not apply the minimum-severity filter in ABI v1. Check Enabled() before + // emitting. + if (logger->Enabled(severity)) + { + logger->EmitLogRecord(severity, message); + } +#else + // EmitLogRecord automatically applies the minimum-severity filter in ABI v2. + logger->EmitLogRecord(severity, message); +#endif +} + +// --------------------------------------------------------------------------- +// Example external library +// --------------------------------------------------------------------------- +namespace external_library +{ +class ExternalModule +{ +public: + ExternalModule() + : logger_(logs_api::Provider::GetLoggerProvider()->GetLogger("ExternalModule", + "external_library")) + {} + + bool Execute() + { + Log(logger_, logs_api::Severity::kDebug, "ExternalModule: Execute called..."); + std::this_thread::sleep_for(std::chrono::microseconds(1)); + + Log(logger_, logs_api::Severity::kDebug, "ExternalModule: executing..."); + std::this_thread::sleep_for(std::chrono::microseconds(1)); + + const bool has_error = external_library_has_error; // simulate an error condition + + if (has_error) + { + Log(logger_, logs_api::Severity::kError, "ExternalModule: error detected!"); + Log(logger_, logs_api::Severity::kDebug, "ExternalModule: some error details."); + } + std::this_thread::sleep_for(std::chrono::microseconds(1)); + Log(logger_, logs_api::Severity::kDebug, "ExternalModule: Execute complete"); + return !has_error; + } + +private: + nostd::shared_ptr logger_; +}; +} // namespace external_library + +// --------------------------------------------------------------------------- +// Example user library +// --------------------------------------------------------------------------- +namespace my_library +{ +class MyModule +{ +public: + MyModule() : logger_(logs_api::Provider::GetLoggerProvider()->GetLogger("MyModule", "my_library")) + {} + + bool Execute() + { + Log(logger_, logs_api::Severity::kDebug, "MyModule: Execute called..."); + + const auto result = external_module_.Execute(); + + if (!result) + { + Log(logger_, logs_api::Severity::kDebug, "MyModule: external_library returned an error"); + } + Log(logger_, logs_api::Severity::kDebug, "MyModule: Execute complete"); + return result; + } + +private: + nostd::shared_ptr logger_; + external_library::ExternalModule external_module_; +}; +} // namespace my_library + +// --------------------------------------------------------------------------- +// Example user application +// --------------------------------------------------------------------------- +class MyApplication +{ +public: + MyApplication() + : logger_( + logs_api::Provider::GetLoggerProvider()->GetLogger("MyApplication", "my_application")) + {} + + void Execute() + { + Log(logger_, logs_api::Severity::kDebug, "MyApplication: Execute called..."); + std::this_thread::sleep_for(std::chrono::microseconds(1)); + + const auto result = my_module_.Execute(); + + if (!result) + { + Log(logger_, logs_api::Severity::kWarn, + "MyApplication: my_library invoke failed with an error"); + } + Log(logger_, logs_api::Severity::kDebug, "MyApplication: Execute complete"); + } + +private: + nostd::shared_ptr logger_; + my_library::MyModule my_module_; +}; + +// --------------------------------------------------------------------------- +// ScopeConfigurator builder helper +// --------------------------------------------------------------------------- + +// Builds a configurator that applies default_config to all scopes, with optional per-scope +// overrides. +std::unique_ptr> MakeLoggerConfigurator( + logs_sdk::LoggerConfig default_config, + std::initializer_list> overrides = {}) +{ + scope_sdk::ScopeConfigurator::Builder builder(default_config); + for (const auto &kv : overrides) + { + const auto &name = kv.first; + const auto &config = kv.second; + builder.AddConditionNameEquals(name, config); + } + return std::make_unique>(builder.Build()); +} + +// Creates a LoggerProvider with an OStreamLogRecordExporter and initial +// ScopeConfigurator. +std::shared_ptr CreateLoggerProvider( + std::unique_ptr> configurator) +{ + auto exporter = logs_exporters::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + + return std::make_shared( + std::move(processor), + opentelemetry::sdk::resource::Resource::Create( + {{opentelemetry::semconv::service::kServiceName, "logger_configurator_example"}}), + std::move(configurator)); +} + +} // namespace + +int main() +{ + // Create LoggerConfig objects for the various configurations we will use in this example. + const bool is_logger_enabled = true; + const bool is_trace_based = false; + + // A LoggerConfig that enables the logger and sets the minimum severity to Warn. + const logs_sdk::LoggerConfig warn_config = + logs_sdk::LoggerConfig::Create(is_logger_enabled, logs_api::Severity::kWarn, is_trace_based); + + // A LoggerConfig that enables the logger and sets the minimum severity to Debug. + const logs_sdk::LoggerConfig debug_config = + logs_sdk::LoggerConfig::Create(is_logger_enabled, logs_api::Severity::kDebug, is_trace_based); + + // A LoggerConfig that disables the logger. + const logs_sdk::LoggerConfig disabled_config = logs_sdk::LoggerConfig::Disabled(); + + // ------------------------------------------------------------------------- + // Stage 1: Production baseline. + // my_application and my_library loggers at Warn. external_library logger is disabled. + // ------------------------------------------------------------------------- + auto sdk_logger_provider = CreateLoggerProvider( + MakeLoggerConfigurator(warn_config, {{"external_library", disabled_config}})); + + logs_api::Provider::SetLoggerProvider( + nostd::shared_ptr(sdk_logger_provider)); + + // Instantiate the application (this creates the loggers for all three scopes). + MyApplication my_app; + + std::cout << "Stage 1: production baseline\n"; + std::cout << " my_application and my_library at Warn. external_library disabled\n"; + my_app.Execute(); + + // ------------------------------------------------------------------------- + // Stage 2: User reports unexpected behavior. + // Lower my_application and my_library to Debug. external_library still disabled. + // ------------------------------------------------------------------------- + std::cout << "\nStage 2: investigating, loggers lowered to Debug\n"; + std::cout << " my_application and my_library at Debug. external_library disabled\n"; + + sdk_logger_provider->UpdateLoggerConfigurator( + MakeLoggerConfigurator(debug_config, {{"external_library", disabled_config}})); + + my_app.Execute(); + + // ------------------------------------------------------------------------- + // Stage 3: Debug logs implicate external_library. + // Enable its logger and my_library logger at Debug. Raise my_application logger back to + // Warn. + // ------------------------------------------------------------------------- + std::cout << "\nStage 3: narrowing down, my_library and external_library at Debug\n"; + std::cout << " my_library and external_library at Debug. my_application at Warn\n"; + + sdk_logger_provider->UpdateLoggerConfigurator(MakeLoggerConfigurator( + warn_config, {{"my_library", debug_config}, {"external_library", debug_config}})); + + my_app.Execute(); + + // ------------------------------------------------------------------------- + // Stage 4: Root cause found and fixed. Restore the production baseline. + // ------------------------------------------------------------------------- + std::cout << "\nStage 4: external_library error resolved, production baseline restored\n"; + std::cout << " my_application and my_library at Warn. external_library disabled\n"; + + external_library_has_error = false; // simulate that the error condition has been fixed + + sdk_logger_provider->UpdateLoggerConfigurator( + MakeLoggerConfigurator(warn_config, {{"external_library", disabled_config}})); + + my_app.Execute(); + + sdk_logger_provider->ForceFlush(); + sdk_logger_provider->Shutdown(); + return 0; +} diff --git a/examples/otlp/file_log_main.cc b/examples/otlp/file_log_main.cc index 253bf37381..0fd20ba1a2 100644 --- a/examples/otlp/file_log_main.cc +++ b/examples/otlp/file_log_main.cc @@ -41,13 +41,10 @@ namespace trace_sdk = opentelemetry::sdk::trace; namespace { -opentelemetry::exporter::otlp::OtlpFileExporterOptions opts; -opentelemetry::exporter::otlp::OtlpFileLogRecordExporterOptions log_opts; - std::shared_ptr tracer_provider; std::shared_ptr logger_provider; -void InitTracer() +void InitTracer(const otlp::OtlpFileExporterOptions &opts) { // Create OTLP exporter instance auto exporter = otlp::OtlpFileExporterFactory::Create(opts); @@ -72,7 +69,7 @@ void CleanupTracer() trace_sdk::Provider::SetTracerProvider(none); } -void InitLogger() +void InitLogger(const otlp::OtlpFileLogRecordExporterOptions &log_opts) { // Create OTLP exporter instance auto exporter = otlp::OtlpFileLogRecordExporterFactory::Create(log_opts); @@ -99,6 +96,8 @@ void CleanupLogger() int main(int argc, char *argv[]) { + otlp::OtlpFileExporterOptions opts; + otlp::OtlpFileLogRecordExporterOptions log_opts; if (argc > 1) { opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions fs_backend; @@ -119,8 +118,8 @@ int main(int argc, char *argv[]) { opts.backend_options = std::ref(std::cout); } - InitLogger(); - InitTracer(); + InitLogger(log_opts); + InitTracer(opts); foo_library(); CleanupTracer(); CleanupLogger(); diff --git a/examples/otlp/file_main.cc b/examples/otlp/file_main.cc index 1d69f64efa..b70cec23a6 100644 --- a/examples/otlp/file_main.cc +++ b/examples/otlp/file_main.cc @@ -27,11 +27,9 @@ namespace otlp = opentelemetry::exporter::otlp; namespace { -opentelemetry::exporter::otlp::OtlpFileExporterOptions opts; - std::shared_ptr provider; -void InitTracer() +void InitTracer(const otlp::OtlpFileExporterOptions &opts) { // Create OTLP exporter instance auto exporter = otlp::OtlpFileExporterFactory::Create(opts); @@ -59,6 +57,7 @@ void CleanupTracer() int main(int argc, char *argv[]) { + otlp::OtlpFileExporterOptions opts; if (argc > 1) { opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions fs_backend; @@ -66,7 +65,7 @@ int main(int argc, char *argv[]) opts.backend_options = fs_backend; } // Removing this line will leave the default noop TracerProvider in place. - InitTracer(); + InitTracer(opts); foo_library(); diff --git a/examples/otlp/file_metric_main.cc b/examples/otlp/file_metric_main.cc index 96aef81051..f02373e401 100644 --- a/examples/otlp/file_metric_main.cc +++ b/examples/otlp/file_metric_main.cc @@ -34,15 +34,10 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace { -otlp_exporter::OtlpFileMetricExporterOptions exporter_options; - -void InitMetrics() +void InitMetrics(const otlp_exporter::OtlpFileMetricExporterOptions &exporter_options) { auto exporter = otlp_exporter::OtlpFileMetricExporterFactory::Create(exporter_options); - std::string version{"1.2.0"}; - std::string schema{"https://opentelemetry.io/schemas/1.2.0"}; - // Initialize and set the global MeterProvider metrics_sdk::PeriodicExportingMetricReaderOptions reader_options; reader_options.export_interval_millis = std::chrono::milliseconds(1000); @@ -69,6 +64,7 @@ void CleanupMetrics() int main(int argc, char *argv[]) { + otlp_exporter::OtlpFileMetricExporterOptions exporter_options; std::string example_type; if (argc > 1) { @@ -81,7 +77,7 @@ int main(int argc, char *argv[]) } } // Removing this line will leave the default noop MetricProvider in place. - InitMetrics(); + InitMetrics(exporter_options); std::string name{"otlp_file_metric_example"}; if (example_type == "counter") diff --git a/examples/otlp/grpc_log_main.cc b/examples/otlp/grpc_log_main.cc index bd7d7fa894..5b8dd003c6 100644 --- a/examples/otlp/grpc_log_main.cc +++ b/examples/otlp/grpc_log_main.cc @@ -39,13 +39,11 @@ namespace trace_sdk = opentelemetry::sdk::trace; namespace { -opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; -opentelemetry::exporter::otlp::OtlpGrpcLogRecordExporterOptions log_opts; - std::shared_ptr tracer_provider; std::shared_ptr logger_provider; -void InitTracer(const std::shared_ptr &shared_client) +void InitTracer(const otlp::OtlpGrpcExporterOptions &opts, + const std::shared_ptr &shared_client) { // Create OTLP exporter instance auto exporter = otlp::OtlpGrpcExporterFactory::Create(opts, shared_client); @@ -70,7 +68,8 @@ void CleanupTracer() trace_sdk::Provider::SetTracerProvider(none); } -void InitLogger(const std::shared_ptr &shared_client) +void InitLogger(const otlp::OtlpGrpcLogRecordExporterOptions &log_opts, + const std::shared_ptr &shared_client) { // Create OTLP exporter instance auto exporter = otlp::OtlpGrpcLogRecordExporterFactory::Create(log_opts, shared_client); @@ -98,6 +97,8 @@ void CleanupLogger() int main(int argc, char *argv[]) { + otlp::OtlpGrpcExporterOptions opts; + otlp::OtlpGrpcLogRecordExporterOptions log_opts; if (argc > 1) { opts.endpoint = argv[1]; @@ -113,8 +114,8 @@ int main(int argc, char *argv[]) std::shared_ptr shared_client = otlp::OtlpGrpcClientFactory::Create(opts); - InitLogger(shared_client); - InitTracer(shared_client); + InitLogger(log_opts, shared_client); + InitTracer(opts, shared_client); foo_library(); CleanupTracer(); CleanupLogger(); diff --git a/examples/otlp/grpc_main.cc b/examples/otlp/grpc_main.cc index 10fde09b76..fb5785c92f 100644 --- a/examples/otlp/grpc_main.cc +++ b/examples/otlp/grpc_main.cc @@ -26,11 +26,9 @@ namespace otlp = opentelemetry::exporter::otlp; namespace { -opentelemetry::exporter::otlp::OtlpGrpcExporterOptions opts; - std::shared_ptr provider; -void InitTracer() +void InitTracer(const otlp::OtlpGrpcExporterOptions &opts) { // Create OTLP exporter instance auto exporter = otlp::OtlpGrpcExporterFactory::Create(opts); @@ -58,6 +56,7 @@ void CleanupTracer() int main(int argc, char *argv[]) { + otlp::OtlpGrpcExporterOptions opts; if (argc > 1) { opts.endpoint = argv[1]; @@ -68,7 +67,7 @@ int main(int argc, char *argv[]) } } // Removing this line will leave the default noop TracerProvider in place. - InitTracer(); + InitTracer(opts); foo_library(); diff --git a/examples/otlp/grpc_metric_main.cc b/examples/otlp/grpc_metric_main.cc index 15bf9755eb..c7782ea842 100644 --- a/examples/otlp/grpc_metric_main.cc +++ b/examples/otlp/grpc_metric_main.cc @@ -42,9 +42,8 @@ namespace otlp_exporter = opentelemetry::exporter::otlp; namespace { -otlp_exporter::OtlpGrpcMetricExporterOptions exporter_options; - -void InitMetrics(std::string &name) +void InitMetrics(const otlp_exporter::OtlpGrpcMetricExporterOptions &exporter_options, + std::string &name) { auto exporter = otlp_exporter::OtlpGrpcMetricExporterFactory::Create(exporter_options); @@ -100,6 +99,7 @@ void CleanupMetrics() int main(int argc, char *argv[]) { + otlp_exporter::OtlpGrpcMetricExporterOptions exporter_options; std::string example_type; if (argc > 1) { @@ -123,7 +123,7 @@ int main(int argc, char *argv[]) std::string name{"otlp_grpc_metric_example"}; - InitMetrics(name); + InitMetrics(exporter_options, name); if (example_type == "counter") { diff --git a/examples/otlp/http_instrumented_main.cc b/examples/otlp/http_instrumented_main.cc index 7a82abde27..17d9da7ccc 100644 --- a/examples/otlp/http_instrumented_main.cc +++ b/examples/otlp/http_instrumented_main.cc @@ -152,15 +152,11 @@ class MyThreadInstrumentation : public opentelemetry::sdk::common::ThreadInstrum #endif /* ENABLE_THREAD_INSTRUMENTATION_PREVIEW */ -opentelemetry::exporter::otlp::OtlpHttpExporterOptions tracer_opts; -opentelemetry::exporter::otlp::OtlpHttpMetricExporterOptions meter_opts; -opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterOptions logger_opts; - std::shared_ptr tracer_provider; std::shared_ptr meter_provider; std::shared_ptr logger_provider; -void InitTracer() +void InitTracer(const opentelemetry::exporter::otlp::OtlpHttpExporterOptions &tracer_opts) { // Create OTLP exporter instance opentelemetry::exporter::otlp::OtlpHttpExporterRuntimeOptions exp_rt_opts; @@ -205,7 +201,7 @@ void CleanupTracer() opentelemetry::sdk::trace::Provider::SetTracerProvider(none); } -void InitMetrics() +void InitMetrics(const opentelemetry::exporter::otlp::OtlpHttpMetricExporterOptions &meter_opts) { // Create OTLP exporter instance opentelemetry::exporter::otlp::OtlpHttpMetricExporterRuntimeOptions exp_rt_opts; @@ -217,9 +213,6 @@ void InitMetrics() auto exporter = opentelemetry::exporter::otlp::OtlpHttpMetricExporterFactory::Create(meter_opts, exp_rt_opts); - std::string version{"1.2.0"}; - std::string schema{"https://opentelemetry.io/schemas/1.2.0"}; - // Initialize and set the global MeterProvider opentelemetry::sdk::metrics::PeriodicExportingMetricReaderOptions reader_options; reader_options.export_interval_millis = std::chrono::milliseconds(1000); @@ -257,7 +250,7 @@ void CleanupMetrics() opentelemetry::sdk::metrics::Provider::SetMeterProvider(none); } -void InitLogger() +void InitLogger(const opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterOptions &logger_opts) { // Create OTLP exporter instance opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterRuntimeOptions exp_rt_opts; @@ -309,6 +302,9 @@ void CleanupLogger() */ int main(int argc, char *argv[]) { + opentelemetry::exporter::otlp::OtlpHttpExporterOptions tracer_opts; + opentelemetry::exporter::otlp::OtlpHttpMetricExporterOptions meter_opts; + opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterOptions logger_opts; if (argc > 1) { tracer_opts.url = argv[1]; @@ -338,9 +334,9 @@ int main(int argc, char *argv[]) std::cout << "Initializing opentelemetry-cpp\n" << std::flush; - InitTracer(); - InitMetrics(); - InitLogger(); + InitTracer(tracer_opts); + InitMetrics(meter_opts); + InitLogger(logger_opts); std::cout << "Application payload\n" << std::flush; diff --git a/examples/otlp/http_log_main.cc b/examples/otlp/http_log_main.cc index 7cc6a0bb4e..1c890cd1db 100644 --- a/examples/otlp/http_log_main.cc +++ b/examples/otlp/http_log_main.cc @@ -44,11 +44,9 @@ namespace internal_log = opentelemetry::sdk::common::internal_log; namespace { -opentelemetry::exporter::otlp::OtlpHttpExporterOptions trace_opts; - std::shared_ptr tracer_provider; -void InitTracer() +void InitTracer(otlp::OtlpHttpExporterOptions &trace_opts) { if (trace_opts.url.size() > 9) { @@ -90,11 +88,9 @@ void CleanupTracer() trace_sdk::Provider::SetTracerProvider(none); } -opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterOptions logger_opts; - std::shared_ptr logger_provider; -void InitLogger() +void InitLogger(otlp::OtlpHttpLogRecordExporterOptions &logger_opts) { std::cout << "Using " << logger_opts.url << " to export log records." << '\n'; logger_opts.console_debug = true; @@ -132,6 +128,8 @@ void CleanupLogger() */ int main(int argc, char *argv[]) { + otlp::OtlpHttpExporterOptions trace_opts; + otlp::OtlpHttpLogRecordExporterOptions logger_opts; if (argc > 1) { trace_opts.url = argv[1]; @@ -158,8 +156,8 @@ int main(int argc, char *argv[]) internal_log::GlobalLogHandler::SetLogLevel(internal_log::LogLevel::Debug); } - InitLogger(); - InitTracer(); + InitLogger(logger_opts); + InitTracer(trace_opts); foo_library(); CleanupTracer(); CleanupLogger(); diff --git a/examples/otlp/http_main.cc b/examples/otlp/http_main.cc index 66b612e3e8..5922b86b6c 100644 --- a/examples/otlp/http_main.cc +++ b/examples/otlp/http_main.cc @@ -30,11 +30,9 @@ namespace internal_log = opentelemetry::sdk::common::internal_log; namespace { -opentelemetry::exporter::otlp::OtlpHttpExporterOptions opts; - std::shared_ptr provider; -void InitTracer() +void InitTracer(const otlp::OtlpHttpExporterOptions &opts) { // Create OTLP exporter instance auto exporter = otlp::OtlpHttpExporterFactory::Create(opts); @@ -70,6 +68,7 @@ void CleanupTracer() */ int main(int argc, char *argv[]) { + otlp::OtlpHttpExporterOptions opts; if (argc > 1) { opts.url = argv[1]; @@ -95,7 +94,7 @@ int main(int argc, char *argv[]) } // Removing this line will leave the default noop TracerProvider in place. - InitTracer(); + InitTracer(opts); foo_library(); diff --git a/examples/otlp/http_metric_main.cc b/examples/otlp/http_metric_main.cc index 59527d4514..12e1076dc2 100644 --- a/examples/otlp/http_metric_main.cc +++ b/examples/otlp/http_metric_main.cc @@ -37,15 +37,10 @@ namespace internal_log = opentelemetry::sdk::common::internal_log; namespace { -otlp_exporter::OtlpHttpMetricExporterOptions exporter_options; - -void InitMetrics() +void InitMetrics(const otlp_exporter::OtlpHttpMetricExporterOptions &exporter_options) { auto exporter = otlp_exporter::OtlpHttpMetricExporterFactory::Create(exporter_options); - std::string version{"1.2.0"}; - std::string schema{"https://opentelemetry.io/schemas/1.2.0"}; - // Initialize and set the global MeterProvider metrics_sdk::PeriodicExportingMetricReaderOptions reader_options; reader_options.export_interval_millis = std::chrono::milliseconds(1000); @@ -83,6 +78,7 @@ void CleanupMetrics() */ int main(int argc, char *argv[]) { + otlp_exporter::OtlpHttpMetricExporterOptions exporter_options; std::string example_type; if (argc > 1) { @@ -115,7 +111,7 @@ int main(int argc, char *argv[]) } // Removing this line will leave the default noop MetricProvider in place. - InitMetrics(); + InitMetrics(exporter_options); std::string name{"otlp_http_metric_example"}; if (example_type == "counter") diff --git a/examples/zipkin/main.cc b/examples/zipkin/main.cc index 1f7f0060c0..d85098ffbb 100644 --- a/examples/zipkin/main.cc +++ b/examples/zipkin/main.cc @@ -28,8 +28,7 @@ namespace resource = opentelemetry::sdk::resource; namespace { -zipkin::ZipkinExporterOptions opts; -void InitTracer() +void InitTracer(const zipkin::ZipkinExporterOptions &opts) { // Create zipkin exporter instance resource::ResourceAttributes attributes = {{"service.name", "zipkin_demo_service"}}; @@ -51,12 +50,13 @@ void CleanupTracer() int main(int argc, char *argv[]) { + zipkin::ZipkinExporterOptions opts; if (argc == 2) { opts.endpoint = argv[1]; } // Removing this line will leave the default noop TracerProvider in place. - InitTracer(); + InitTracer(opts); foo_library(); diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h index 2b975e239c..57db2f6510 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_tracer.h @@ -788,7 +788,7 @@ class Span : public opentelemetry::trace::Span /** * @brief Span name. */ - nostd::string_view name_; + std::string name_; /** * @brief Attribute indicating that the span has ended. @@ -852,7 +852,7 @@ class Span : public opentelemetry::trace::Span * @brief Get Span Name. * @return Span Name. */ - nostd::string_view GetName() const { return name_; } + nostd::string_view GetName() const { return nostd::string_view{name_.data(), name_.size()}; } /** * @brief Span constructor @@ -870,10 +870,10 @@ class Span : public opentelemetry::trace::Span : opentelemetry::trace::Span(), start_time_(std::chrono::system_clock::now()), owner_(owner), + name_(name.data(), name.size()), context_(std::move(spanContext)), parent_(parent) { - name_ = name; UNREFERENCED_PARAMETER(options); } diff --git a/exporters/etw/test/etw_tracer_test.cc b/exporters/etw/test/etw_tracer_test.cc index 7d32a2ffba..c61534c761 100644 --- a/exporters/etw/test/etw_tracer_test.cc +++ b/exporters/etw/test/etw_tracer_test.cc @@ -503,6 +503,20 @@ TEST(ETWTracer, AlwayOffTailSampler) auto tracer = tp.GetTracer(providerName); } +TEST(ETWTracer, SpanOwnsName) +{ + exporter::etw::TracerProvider tp; + auto tracer = tp.GetTracer("SpanOwnsName"); + std::string name = "original"; + auto span = tracer->StartSpan(name); + + name[0] = 'X'; + + auto etw_span = static_cast(span.get()); + EXPECT_EQ(etw_span->GetName(), "original"); + span->End(); +} + TEST(ETWTracer, CustomIdGenerator) { std::string providerName = kGlobalProviderName; // supply unique instrumentation name here diff --git a/exporters/memory/test/in_memory_metric_exporter_test.cc b/exporters/memory/test/in_memory_metric_exporter_test.cc index e0342d33a5..402f48cf35 100644 --- a/exporters/memory/test/in_memory_metric_exporter_test.cc +++ b/exporters/memory/test/in_memory_metric_exporter_test.cc @@ -24,6 +24,9 @@ using opentelemetry::sdk::metrics::ResourceMetrics; using opentelemetry::sdk::metrics::ScopeMetrics; using opentelemetry::sdk::resource::Resource; +namespace +{ + class InMemoryMetricExporterTest : public ::testing::Test { protected: @@ -62,3 +65,5 @@ TEST_F(InMemoryMetricExporterTest, TemporalitySelector) EXPECT_EQ(exporter_->GetAggregationTemporality(InstrumentType::kCounter), AggregationTemporality::kCumulative); } + +} // namespace diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h index 840d7d5b8a..f0bd0dc39e 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_record_exporter.h @@ -37,6 +37,13 @@ class OStreamLogRecordExporter final : public opentelemetry::sdk::logs::LogRecor std::unique_ptr MakeRecordable() noexcept override; + /** + * The ostream exporter uses ReadWriteLogRecord, which applies the configured + * LogRecord attribute limits, so the SDK should push the limits onto each + * record it produces. + */ + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + /** * Exports a span of logs sent from the processor. */ diff --git a/exporters/ostream/test/ostream_span_test.cc b/exporters/ostream/test/ostream_span_test.cc index 10c8a55cd3..a734b0894f 100644 --- a/exporters/ostream/test/ostream_span_test.cc +++ b/exporters/ostream/test/ostream_span_test.cc @@ -43,6 +43,9 @@ namespace exportertrace = opentelemetry::exporter::trace; using Attributes = std::initializer_list>; +namespace +{ + class TestResource : public resource::Resource { public: @@ -411,3 +414,5 @@ TEST(OStreamSpanExporter, PrintSpanToClog) EXPECT_EQ(captured, kDefaultSpanPrinted); } + +} // namespace diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index e28ba407b2..f756afa698 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -47,6 +47,7 @@ cc_library( deps = [ ":otlp_utf8_validity", "//sdk/src/logs", + "//sdk/src/metrics", "//sdk/src/resource", "//sdk/src/trace", "@com_github_opentelemetry_proto//:logs_service_proto_cc", @@ -666,6 +667,23 @@ cc_test( ], ) +cc_test( + name = "otlp_populate_attribute_utils_test", + srcs = [ + "test/otlp_populate_attribute_utils_test.cc", + ], + tags = [ + "otlp", + "test", + ], + deps = [ + ":otlp_recordable", + "//sdk/src/metrics", + "@com_github_opentelemetry_proto//:common_proto_cc", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "otlp_file_client_test", srcs = ["test/otlp_file_client_test.cc"], @@ -1023,6 +1041,7 @@ otel_cc_benchmark( "//sdk/src/metrics", "//sdk/src/resource", "//sdk/src/trace", + "//test_common:headers", "@com_github_opentelemetry_proto//:trace_service_proto_cc", ], ) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 7cbe414785..eef251a1de 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -892,6 +892,16 @@ if(BUILD_TESTING) TEST_PREFIX exporter.otlp. TEST_LIST otlp_log_recordable_test) + add_executable(otlp_populate_attribute_utils_test + test/otlp_populate_attribute_utils_test.cc) + target_link_libraries( + otlp_populate_attribute_utils_test ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable) + gtest_add_tests( + TARGET otlp_populate_attribute_utils_test + TEST_PREFIX exporter.otlp. + TEST_LIST otlp_populate_attribute_utils_test) + add_executable(otlp_metrics_serialization_test test/otlp_metrics_serialization_test.cc) target_link_libraries( @@ -1215,7 +1225,11 @@ if(WITH_BENCHMARK) target_link_libraries( otlp_recordable_benchmark - PRIVATE opentelemetry_otlp_recordable opentelemetry_trace - opentelemetry_metrics opentelemetry_resources benchmark::benchmark + PRIVATE opentelemetry_otlp_recordable + opentelemetry_trace + opentelemetry_metrics + opentelemetry_resources + benchmark::benchmark + opentelemetry_test_common ${CMAKE_THREAD_LIBS_INIT}) endif() diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_file_log_record_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_file_log_record_exporter.h index 7742616351..3584a8b878 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_file_log_record_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_file_log_record_exporter.h @@ -51,6 +51,12 @@ class OtlpFileLogRecordExporter final : public opentelemetry::sdk::logs::LogReco */ std::unique_ptr MakeRecordable() noexcept override; + /** + * The OTLP recordable applies the configured LogRecord attribute limits, so + * the SDK should push the limits onto each record it produces. + */ + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + /** * Exports a vector of log records to the Elasticsearch instance. Guaranteed to return after a * timeout specified from the options passed from the constructor. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h index 2bb4ab6542..94d028978f 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_exporter_options.h @@ -35,7 +35,11 @@ struct OPENTELEMETRY_EXPORT OtlpGrpcExporterOptions : public OtlpGrpcClientOptio OtlpGrpcExporterOptions &operator=(OtlpGrpcExporterOptions &&) = default; ~OtlpGrpcExporterOptions() override; - /** Collection Limits. No limit by default. */ + /** Collection Limits. No limit by default. + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These fields will be + * removed in a future release. + */ std::uint32_t max_attributes = (std::numeric_limits::max)(); std::uint32_t max_events = (std::numeric_limits::max)(); std::uint32_t max_links = (std::numeric_limits::max)(); diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter.h index 888c368b28..9b6dc5c115 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter.h @@ -68,6 +68,12 @@ class OtlpGrpcLogRecordExporter : public opentelemetry::sdk::logs::LogRecordExpo */ std::unique_ptr MakeRecordable() noexcept override; + /** + * The OTLP recordable applies the configured LogRecord attribute limits, so + * the SDK should push the limits onto each record it produces. + */ + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + /** * Exports a vector of log records to the configured gRPC endpoint. Guaranteed to return after a * timeout specified from the options passed to the constructor. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h index 58fad0485e..b146fd3b0a 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h @@ -127,7 +127,11 @@ struct OPENTELEMETRY_EXPORT OtlpHttpExporterOptions /** The backoff will be multiplied by this value after each retry attempt. */ float retry_policy_backoff_multiplier{}; - /** Collection Limits. No limit by default. */ + /** Collection Limits. No limit by default. + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These fields will be + * removed in a future release. + */ std::uint32_t max_attributes = (std::numeric_limits::max)(); std::uint32_t max_events = (std::numeric_limits::max)(); std::uint32_t max_links = (std::numeric_limits::max)(); diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter.h index 4259c28998..7bcc98210e 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter.h @@ -90,6 +90,12 @@ class OtlpHttpLogRecordExporter final : public opentelemetry::sdk::logs::LogReco */ std::unique_ptr MakeRecordable() noexcept override; + /** + * The OTLP recordable applies the configured LogRecord attribute limits, so + * the SDK should push the limits onto each record it produces. + */ + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + /** * Exports a vector of log records to the Elasticsearch instance. Guaranteed to return after a * timeout specified from the options passed from the constructor. diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h index efcb0d846b..1d7f774bd7 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_log_recordable.h @@ -7,6 +7,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/version.h" @@ -96,6 +97,14 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable void SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept override; + /** + * Apply attribute count and value length limits. Must be called before any + * SetAttribute call to take effect. The limits are copied into this + * recordable. + */ + void SetLogRecordLimits( + const opentelemetry::sdk::logs::LogRecordLimits &limits) noexcept override; + /** * Set Resource of this log * @param Resource the resource to set @@ -114,6 +123,12 @@ class OtlpLogRecordable final : public opentelemetry::sdk::logs::Recordable const opentelemetry::sdk::resource::Resource *resource_ = nullptr; const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ = nullptr; + // Stored by value so the recordable does not depend on the limits object + // outliving the LoggerContext that supplied it. Defaults to no limits; the + // LoggerProvider wiring injects the configured limits via SetLogRecordLimits, + // so a recordable used outside a provider does not cap attributes on its own. + opentelemetry::sdk::logs::LogRecordLimits limits_ = + opentelemetry::sdk::logs::LogRecordLimits::NoLimits(); }; } // namespace otlp diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h index 02273938bf..fe4f5ac0e0 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h @@ -3,6 +3,10 @@ #pragma once +#include +#include +#include + #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/attribute_utils.h" @@ -55,23 +59,61 @@ class OtlpPopulateAttributeUtils const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept; - static void PopulateAnyValue(opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::common::AttributeValue &value, - bool allow_bytes) noexcept; + /** + * Populate a proto AnyValue from a non-owning AttributeValue. + * When `max_length` is less than `std::numeric_limits::max()`, + * string alternatives are truncated to at most `max_length` bytes using + * UTF-8-safe truncation (Utf8SafePrefixLength) so the resulting proto + * `string_value` stays valid UTF-8 when the input was. Raw bytes + * (`span` when `allow_bytes` is true) are cut at the raw + * byte boundary since they are not UTF-8 text. Non-string alternatives + * are unaffected. + */ + static void PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::common::AttributeValue &value, + bool allow_bytes, + std::size_t max_length = (std::numeric_limits::max)()) noexcept; + + static void PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes, + std::size_t max_length = (std::numeric_limits::max)()) noexcept; + + static void PopulateAttribute( + opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::common::AttributeValue &value, + bool allow_bytes, + std::size_t max_length = (std::numeric_limits::max)()) noexcept; - static void PopulateAnyValue(opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes) noexcept; + static void PopulateAttribute( + opentelemetry::proto::common::v1::KeyValue *attribute, + nostd::string_view key, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + bool allow_bytes, + std::size_t max_length = (std::numeric_limits::max)()) noexcept; - static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute, - nostd::string_view key, - const opentelemetry::common::AttributeValue &value, - bool allow_bytes) noexcept; + /** + * Byte length of the longest prefix of `value` that fits within `max_bytes` + * without splitting a well-formed UTF-8 multi-byte sequence. A lead byte's + * declared length is only honored when its continuation bytes are present + * and in range (0x80-0xBF); otherwise the lead is treated as a one-byte + * unit, so malformed input degrades to plain byte truncation. The protobuf + * `string` field type requires valid UTF-8, so this utility lets callers + * truncate at a code-point boundary instead of cutting through a multi-byte + * sequence. + */ + static std::size_t Utf8SafePrefixLength(const char *data, + std::size_t size, + std::size_t max_bytes) noexcept; - static void PopulateAttribute(opentelemetry::proto::common::v1::KeyValue *attribute, - nostd::string_view key, - const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes) noexcept; + /// Convenience overload that delegates to the pointer + size variant. + static std::size_t Utf8SafePrefixLength(const std::string &value, std::size_t max_bytes) noexcept + { + return Utf8SafePrefixLength(value.data(), value.size(), max_bytes); + } }; } // namespace otlp diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h index b63bc5ff7d..d4c30483f8 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_recordable.h @@ -23,6 +23,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -37,17 +38,25 @@ namespace otlp class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable { public: + /** + * Construct with optional span limits parameters. + * + * @deprecated Configure span limits via TracerProviderFactory::Create(), SpanLimitsConfiguration, + * or the YAML `tracer_provider.limits` node instead. These optional span limit params will be + * removed in a future release. + */ explicit OtlpRecordable( std::uint32_t max_attributes = (std::numeric_limits::max)(), std::uint32_t max_events = (std::numeric_limits::max)(), std::uint32_t max_links = (std::numeric_limits::max)(), std::uint32_t max_attributes_per_event = (std::numeric_limits::max)(), std::uint32_t max_attributes_per_link = (std::numeric_limits::max)()) - : max_attributes_(max_attributes), - max_events_(max_events), - max_links_(max_links), - max_attributes_per_event_(max_attributes_per_event), - max_attributes_per_link_(max_attributes_per_link) + : span_limits_{max_attributes, + (std::numeric_limits::max)(), + max_events, + max_links, + max_attributes_per_event, + max_attributes_per_link} {} proto::trace::v1::Span &span() noexcept { return span_; } @@ -92,6 +101,17 @@ class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + /** + * Set span limits. + * + * Until the deprecated OTLP exporter option values are removed, the effective limit for each + * field is the minimum (most restrictive) of the deprecated otlp exporter option values (passed + * to the constructor) and the values set by calling this method. + * + * @param limits The span limits to set. + */ + void SetSpanLimits(const opentelemetry::sdk::trace::SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept override; @@ -100,11 +120,8 @@ class OtlpRecordable final : public opentelemetry::sdk::trace::Recordable const opentelemetry::sdk::resource::Resource *resource_ = nullptr; const opentelemetry::sdk::instrumentationscope::InstrumentationScope *instrumentation_scope_ = nullptr; - std::uint32_t max_attributes_; - std::uint32_t max_events_; - std::uint32_t max_links_; - std::uint32_t max_attributes_per_event_; - std::uint32_t max_attributes_per_link_; + opentelemetry::sdk::trace::SpanLimits span_limits_{ + opentelemetry::sdk::trace::SpanLimits::NoLimits()}; }; } // namespace otlp } // namespace exporter diff --git a/exporters/otlp/src/otlp_log_recordable.cc b/exporters/otlp/src/otlp_log_recordable.cc index 4f1877bb41..dc10abfe41 100644 --- a/exporters/otlp/src/otlp_log_recordable.cc +++ b/exporters/otlp/src/otlp_log_recordable.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "opentelemetry/exporters/otlp/otlp_log_recordable.h" +#include #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" @@ -9,6 +10,7 @@ #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/readable_log_record.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/trace/span_id.h" @@ -236,7 +238,20 @@ void OtlpLogRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &tr void OtlpLogRecordable::SetAttribute(opentelemetry::nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { - OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value, true); + if (static_cast(proto_record_.attributes_size()) >= limits_.attribute_count_limit) + { + proto_record_.set_dropped_attributes_count(proto_record_.dropped_attributes_count() + 1); + return; + } + + OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value, true, + limits_.attribute_value_length_limit); +} + +void OtlpLogRecordable::SetLogRecordLimits( + const opentelemetry::sdk::logs::LogRecordLimits &limits) noexcept +{ + limits_ = limits; } void OtlpLogRecordable::SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept diff --git a/exporters/otlp/src/otlp_populate_attribute_utils.cc b/exporters/otlp/src/otlp_populate_attribute_utils.cc index 14259773db..943121e570 100644 --- a/exporters/otlp/src/otlp_populate_attribute_utils.cc +++ b/exporters/otlp/src/otlp_populate_attribute_utils.cc @@ -6,7 +6,8 @@ #endif #include -#include +#include +#include #include #include #include @@ -62,7 +63,8 @@ inline void SetUint64Value(opentelemetry::proto::common::v1::AnyValue *proto_val void OtlpPopulateAttributeUtils::PopulateAnyValue( opentelemetry::proto::common::v1::AnyValue *proto_value, const opentelemetry::common::AttributeValue &value, - bool allow_bytes) noexcept + bool allow_bytes, + std::size_t max_length) noexcept { if (nullptr == proto_value) { @@ -101,48 +103,54 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( } else if (nostd::holds_alternative(value)) { - const char *str_value = nostd::get(value); + const char *str_value = nostd::get(value); + const std::size_t str_len = std::strlen(str_value); + const std::size_t kept_len = Utf8SafePrefixLength(str_value, str_len, max_length); #if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid(str_value)) + // Validity is decided by the original input, so truncation that happens + // to remove invalid bytes does not flip the proto field type. + if (utf8_range::IsStructurallyValid({str_value, str_len})) { - proto_value->set_string_value(str_value); + proto_value->set_string_value(str_value, kept_len); } else { - proto_value->set_bytes_value(str_value, strlen(str_value)); + proto_value->set_bytes_value(str_value, kept_len); } #else - proto_value->set_string_value(str_value); + proto_value->set_string_value(str_value, kept_len); #endif } else if (nostd::holds_alternative(value)) { nostd::string_view str_value = nostd::get(value); + const std::size_t kept_len = + Utf8SafePrefixLength(str_value.data(), str_value.size(), max_length); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid({str_value.data(), str_value.size()})) { - proto_value->set_string_value(str_value.data(), str_value.size()); + proto_value->set_string_value(str_value.data(), kept_len); } else { - proto_value->set_bytes_value(str_value.data(), str_value.size()); + proto_value->set_bytes_value(str_value.data(), kept_len); } #else - proto_value->set_string_value(str_value.data(), str_value.size()); + proto_value->set_string_value(str_value.data(), kept_len); #endif } else if (nostd::holds_alternative>(value)) { + const auto &bytes = nostd::get>(value); if (allow_bytes) { - proto_value->set_bytes_value( - reinterpret_cast(nostd::get>(value).data()), - nostd::get>(value).size()); + const std::size_t kept_len = (std::min)(bytes.size(), max_length); + proto_value->set_bytes_value(reinterpret_cast(bytes.data()), kept_len); } else { auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + for (const auto &val : bytes) { array_value->add_values()->set_int_value(val); } @@ -201,17 +209,18 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( auto array_value = proto_value->mutable_array_value(); for (const auto &val : nostd::get>(value)) { + const std::size_t kept_len = Utf8SafePrefixLength(val.data(), val.size(), max_length); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid({val.data(), val.size()})) { - array_value->add_values()->set_string_value(val.data(), val.size()); + array_value->add_values()->set_string_value(val.data(), kept_len); } else { - array_value->add_values()->set_bytes_value(val.data(), val.size()); + array_value->add_values()->set_bytes_value(val.data(), kept_len); } #else - array_value->add_values()->set_string_value(val.data(), val.size()); + array_value->add_values()->set_string_value(val.data(), kept_len); #endif } } @@ -220,7 +229,8 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( void OtlpPopulateAttributeUtils::PopulateAnyValue( opentelemetry::proto::common::v1::AnyValue *proto_value, const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes) noexcept + bool allow_bytes, + std::size_t max_length) noexcept { if (nullptr == proto_value) { @@ -262,8 +272,8 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( if (allow_bytes) { const std::vector &byte_array = nostd::get>(value); - proto_value->set_bytes_value(reinterpret_cast(byte_array.data()), - byte_array.size()); + const std::size_t kept_len = (std::min)(byte_array.size(), max_length); + proto_value->set_bytes_value(reinterpret_cast(byte_array.data()), kept_len); } else { @@ -277,17 +287,18 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( else if (nostd::holds_alternative(value)) { const std::string &str_value = nostd::get(value); + const std::size_t kept_len = Utf8SafePrefixLength(str_value, max_length); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid(str_value)) { - proto_value->set_string_value(str_value); + proto_value->set_string_value(str_value.data(), kept_len); } else { - proto_value->set_bytes_value(str_value); + proto_value->set_bytes_value(str_value.data(), kept_len); } #else - proto_value->set_string_value(str_value); + proto_value->set_string_value(str_value.data(), kept_len); #endif } else if (nostd::holds_alternative>(value)) @@ -344,17 +355,18 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( auto array_value = proto_value->mutable_array_value(); for (const auto &val : nostd::get>(value)) { + const std::size_t kept_len = Utf8SafePrefixLength(val, max_length); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid(val)) { - array_value->add_values()->set_string_value(val); + array_value->add_values()->set_string_value(val.data(), kept_len); } else { - array_value->add_values()->set_bytes_value(val); + array_value->add_values()->set_bytes_value(val.data(), kept_len); } #else - array_value->add_values()->set_string_value(val); + array_value->add_values()->set_string_value(val.data(), kept_len); #endif } } @@ -364,7 +376,8 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, const opentelemetry::common::AttributeValue &value, - bool allow_bytes) noexcept + bool allow_bytes, + std::size_t max_length) noexcept { if (nullptr == attribute) { @@ -378,7 +391,7 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "AttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value, allow_bytes); + PopulateAnyValue(attribute->mutable_value(), value, allow_bytes, max_length); } /** Maps from C++ attribute into OTLP proto attribute. */ @@ -386,7 +399,8 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes) noexcept + bool allow_bytes, + std::size_t max_length) noexcept { if (nullptr == attribute) { @@ -400,7 +414,7 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "OwnedAttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value, allow_bytes); + PopulateAnyValue(attribute->mutable_value(), value, allow_bytes, max_length); } void OtlpPopulateAttributeUtils::PopulateAttribute( @@ -431,6 +445,16 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( } } +// The UTF-8-safe prefix algorithm now lives in the SDK common layer so the +// in-memory AttributeConverter can share it. This member is kept as a thin +// forwarder for backward compatibility with existing callers and tests. +std::size_t OtlpPopulateAttributeUtils::Utf8SafePrefixLength(const char *data, + std::size_t size, + std::size_t max_bytes) noexcept +{ + return opentelemetry::sdk::common::Utf8SafePrefixLength(data, size, max_bytes); +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/src/otlp_recordable.cc b/exporters/otlp/src/otlp_recordable.cc index 9270e17383..017fc51905 100644 --- a/exporters/otlp/src/otlp_recordable.cc +++ b/exporters/otlp/src/otlp_recordable.cc @@ -1,8 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include +#include #include +#include #include #include "opentelemetry/common/attribute_value.h" @@ -15,6 +16,7 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -114,24 +116,40 @@ void OtlpRecordable::SetResource(const sdk::resource::Resource &resource) noexce resource_ = &resource; } +void OtlpRecordable::SetSpanLimits(const opentelemetry::sdk::trace::SpanLimits &limits) noexcept +{ + // Update the span limits to be the minimum of the current limits and the new limits + // The initial limits are set on construction from the deprecated span limits otlp options. + // TODO: replace with a simple copy once the deprecated options are removed. + span_limits_ = { + (std::min)(span_limits_.attribute_count_limit, limits.attribute_count_limit), + (std::min)(span_limits_.attribute_value_length_limit, limits.attribute_value_length_limit), + (std::min)(span_limits_.event_count_limit, limits.event_count_limit), + (std::min)(span_limits_.link_count_limit, limits.link_count_limit), + (std::min)(span_limits_.event_attribute_count_limit, limits.event_attribute_count_limit), + (std::min)(span_limits_.link_attribute_count_limit, limits.link_attribute_count_limit), + }; +} + void OtlpRecordable::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept { - if (static_cast(span_.attributes_size()) >= max_attributes_) + if (static_cast(span_.attributes_size()) >= span_limits_.attribute_count_limit) { span_.set_dropped_attributes_count(span_.dropped_attributes_count() + 1); return; } auto *attribute = span_.add_attributes(); - OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false, + span_limits_.attribute_value_length_limit); } void OtlpRecordable::AddEvent(nostd::string_view name, common::SystemTimestamp timestamp, const common::KeyValueIterable &attributes) noexcept { - if (static_cast(span_.events_size()) >= max_events_) + if (static_cast(span_.events_size()) >= span_limits_.event_count_limit) { span_.set_dropped_events_count(span_.dropped_events_count() + 1); return; @@ -142,12 +160,14 @@ void OtlpRecordable::AddEvent(nostd::string_view name, event->set_time_unix_nano(timestamp.time_since_epoch().count()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - if (static_cast(event->attributes_size()) >= max_attributes_per_event_) + if (static_cast(event->attributes_size()) >= + span_limits_.event_attribute_count_limit) { event->set_dropped_attributes_count(event->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false, + span_limits_.attribute_value_length_limit); return true; }); } @@ -155,7 +175,7 @@ void OtlpRecordable::AddEvent(nostd::string_view name, void OtlpRecordable::AddLink(const trace::SpanContext &span_context, const common::KeyValueIterable &attributes) noexcept { - if (static_cast(span_.links_size()) >= max_links_) + if (static_cast(span_.links_size()) >= span_limits_.link_count_limit) { span_.set_dropped_links_count(span_.dropped_links_count() + 1); return; @@ -167,12 +187,14 @@ void OtlpRecordable::AddLink(const trace::SpanContext &span_context, trace::SpanId::kSize); link->set_trace_state(span_context.trace_state()->ToHeader()); attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { - if (static_cast(link->attributes_size()) >= max_attributes_per_link_) + if (static_cast(link->attributes_size()) >= + span_limits_.link_attribute_count_limit) { link->set_dropped_attributes_count(link->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false); + OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false, + span_limits_.attribute_value_length_limit); return true; }); } diff --git a/exporters/otlp/test/otlp_file_client_test.cc b/exporters/otlp/test/otlp_file_client_test.cc index 91ddcc944c..0a7e1e84dc 100644 --- a/exporters/otlp/test/otlp_file_client_test.cc +++ b/exporters/otlp/test/otlp_file_client_test.cc @@ -57,6 +57,8 @@ namespace otlp namespace resource = opentelemetry::sdk::resource; +namespace +{ class ProtobufGlobalSymbolGuard { public: @@ -67,6 +69,7 @@ class ProtobufGlobalSymbolGuard ProtobufGlobalSymbolGuard(ProtobufGlobalSymbolGuard &&) = delete; ProtobufGlobalSymbolGuard &operator=(ProtobufGlobalSymbolGuard &&) = delete; }; +} // namespace static std::tm GetLocalTime(std::chrono::system_clock::time_point tp) { @@ -267,8 +270,6 @@ TEST(OtlpFileClientTest, ExportToFileSystemRotateIndexTest) opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; OtlpRecordableUtils::PopulateRequest(MakeSpan(recordable), &request); - std::stringstream output_stream; - // Clear old files { std::fstream clear_file1("otlp_file_client_test_dir/trace-1.jsonl", @@ -398,8 +399,6 @@ TEST(OtlpFileClientTest, ExportToFileSystemRotateByTimeTest) opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; OtlpRecordableUtils::PopulateRequest(MakeSpan(recordable), &request); - std::stringstream output_stream; - opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions backend_opts; backend_opts.file_pattern = "otlp_file_client_test_dir/trace-%Y-%m-%d-%H-%M-%S.jsonl"; backend_opts.alias_pattern = ""; diff --git a/exporters/otlp/test/otlp_file_exporter_test.cc b/exporters/otlp/test/otlp_file_exporter_test.cc index 64d4c4d708..fe38517aed 100644 --- a/exporters/otlp/test/otlp_file_exporter_test.cc +++ b/exporters/otlp/test/otlp_file_exporter_test.cc @@ -59,6 +59,8 @@ namespace otlp namespace trace_api = opentelemetry::trace; namespace resource = opentelemetry::sdk::resource; +namespace +{ class ProtobufGlobalSymbolGuard { public: @@ -69,6 +71,7 @@ class ProtobufGlobalSymbolGuard ProtobufGlobalSymbolGuard(ProtobufGlobalSymbolGuard &&) = delete; ProtobufGlobalSymbolGuard &operator=(ProtobufGlobalSymbolGuard &&) = delete; }; +} // namespace template static nostd::span MakeSpan(T (&array)[N]) @@ -76,6 +79,8 @@ static nostd::span MakeSpan(T (&array)[N]) return nostd::span(array); } +namespace +{ class OtlpFileExporterTestPeer : public ::testing::Test { public: @@ -183,6 +188,7 @@ class OtlpFileExporterTestPeer : public ::testing::Test } } }; +} // namespace TEST(OtlpFileExporterTest, Shutdown) { diff --git a/exporters/otlp/test/otlp_file_log_record_exporter_test.cc b/exporters/otlp/test/otlp_file_log_record_exporter_test.cc index 2d96328d79..75a6697f82 100644 --- a/exporters/otlp/test/otlp_file_log_record_exporter_test.cc +++ b/exporters/otlp/test/otlp_file_log_record_exporter_test.cc @@ -45,6 +45,8 @@ namespace exporter namespace otlp { +namespace +{ class ProtobufGlobalSymbolGuard { public: @@ -55,6 +57,7 @@ class ProtobufGlobalSymbolGuard ProtobufGlobalSymbolGuard(ProtobufGlobalSymbolGuard &&) = delete; ProtobufGlobalSymbolGuard &operator=(ProtobufGlobalSymbolGuard &&) = delete; }; +} // namespace template static nostd::span MakeSpan(T (&array)[N]) @@ -62,6 +65,8 @@ static nostd::span MakeSpan(T (&array)[N]) return nostd::span(array); } +namespace +{ class OtlpFileLogRecordExporterTestPeer : public ::testing::Test { public: @@ -168,6 +173,7 @@ class OtlpFileLogRecordExporterTestPeer : public ::testing::Test } } }; +} // namespace TEST(OtlpFileLogRecordExporterTest, Shutdown) { diff --git a/exporters/otlp/test/otlp_file_metric_exporter_test.cc b/exporters/otlp/test/otlp_file_metric_exporter_test.cc index 98b819b08b..bc6138c9c1 100644 --- a/exporters/otlp/test/otlp_file_metric_exporter_test.cc +++ b/exporters/otlp/test/otlp_file_metric_exporter_test.cc @@ -43,6 +43,8 @@ namespace exporter namespace otlp { +namespace +{ class ProtobufGlobalSymbolGuard { public: @@ -53,6 +55,7 @@ class ProtobufGlobalSymbolGuard ProtobufGlobalSymbolGuard(ProtobufGlobalSymbolGuard &&) = delete; ProtobufGlobalSymbolGuard &operator=(ProtobufGlobalSymbolGuard &&) = delete; }; +} // namespace template static IntegerType JsonToInteger(const nlohmann::json &value) diff --git a/exporters/otlp/test/otlp_grpc_exporter_test.cc b/exporters/otlp/test/otlp_grpc_exporter_test.cc index 7525fc2109..239f788c49 100644 --- a/exporters/otlp/test/otlp_grpc_exporter_test.cc +++ b/exporters/otlp/test/otlp_grpc_exporter_test.cc @@ -453,6 +453,8 @@ TEST_F(OtlpGrpcExporterTestPeer, ConfigRetryGenericValuesFromEnv) # endif // NO_GETENV # ifdef ENABLE_OTLP_RETRY_PREVIEW +namespace +{ struct TestTraceService : public opentelemetry::proto::collector::trace::v1::TraceService::Service { TestTraceService(const std::vector &status_codes) : status_codes_(status_codes) @@ -471,12 +473,16 @@ struct TestTraceService : public opentelemetry::proto::collector::trace::v1::Tra size_t index_ = 0UL; std::vector status_codes_; }; +} // namespace using StatusCodeVector = std::vector; +namespace +{ class OtlpGrpcExporterRetryIntegrationTests : public ::testing::TestWithParam> {}; +} // namespace INSTANTIATE_TEST_SUITE_P( StatusCodes, diff --git a/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index b7a346c866..72adc689ed 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -423,9 +423,14 @@ class OtlpHttpExporterTestPeer : public ::testing::Test [&mock_session, report_trace_id, &received_trace_id_counter]( const std::shared_ptr &callback) { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; - request_body.ParseFromArray( + const bool parsed = request_body.ParseFromArray( &mock_session->GetRequest()->body_[0], static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_spans_size() == 0 || request_body.resource_spans(0).scope_spans_size() == 0 || request_body.resource_spans(0).scope_spans(0).spans_size() == 0) @@ -520,9 +525,14 @@ class OtlpHttpExporterTestPeer : public ::testing::Test [&mock_session, report_trace_id, &received_trace_id_counter]( const std::shared_ptr &callback) { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request_body; - request_body.ParseFromArray( + const bool parsed = request_body.ParseFromArray( &mock_session->GetRequest()->body_[0], static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_spans_size() == 0 || request_body.resource_spans(0).scope_spans_size() == 0 || request_body.resource_spans(0).scope_spans(0).spans_size() == 0) @@ -777,9 +787,12 @@ TEST_F(OtlpHttpExporterTestPeer, ConfigRetryGenericValuesFromEnv) # ifdef ENABLE_OTLP_RETRY_PREVIEW using StatusCodeVector = std::vector; +namespace +{ class OtlpHttpExporterRetryIntegrationTests : public ::testing::TestWithParam> {}; +} // namespace INSTANTIATE_TEST_SUITE_P(StatusCodes, OtlpHttpExporterRetryIntegrationTests, diff --git a/exporters/otlp/test/otlp_http_log_record_exporter_test.cc b/exporters/otlp/test/otlp_http_log_record_exporter_test.cc index 918d21b515..3dbf115b58 100644 --- a/exporters/otlp/test/otlp_http_log_record_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_log_record_exporter_test.cc @@ -462,9 +462,14 @@ class OtlpHttpLogRecordExporterTestPeer : public ::testing::Test [&mock_session, report_trace_id, report_span_id, &received_record_counter]( const std::shared_ptr &callback) { opentelemetry::proto::collector::logs::v1::ExportLogsServiceRequest request_body; - request_body.ParseFromArray( + const bool parsed = request_body.ParseFromArray( &mock_session->GetRequest()->body_[0], static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_logs_size() == 0 || request_body.resource_logs(0).scope_logs_size() == 0 || request_body.resource_logs(0).scope_logs(0).log_records_size() == 0) @@ -590,9 +595,14 @@ class OtlpHttpLogRecordExporterTestPeer : public ::testing::Test [&mock_session, report_trace_id, report_span_id, schema_url, &received_record_counter]( const std::shared_ptr &callback) { opentelemetry::proto::collector::logs::v1::ExportLogsServiceRequest request_body; - request_body.ParseFromArray( + const bool parsed = request_body.ParseFromArray( &mock_session->GetRequest()->body_[0], static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_logs_size() == 0 || request_body.resource_logs(0).scope_logs_size() == 0 || request_body.resource_logs(0).scope_logs(0).log_records_size() == 0) diff --git a/exporters/otlp/test/otlp_http_metric_exporter_test.cc b/exporters/otlp/test/otlp_http_metric_exporter_test.cc index 4934423ca4..787c982bf0 100644 --- a/exporters/otlp/test/otlp_http_metric_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_metric_exporter_test.cc @@ -301,8 +301,14 @@ class OtlpHttpMetricExporterTestPeer : public ::testing::Test const std::shared_ptr &callback) { opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; - request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], - static_cast(mock_session->GetRequest()->body_.size())); + const bool parsed = request_body.ParseFromArray( + &mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics(0).metrics_size() == 0) @@ -522,8 +528,14 @@ class OtlpHttpMetricExporterTestPeer : public ::testing::Test const std::shared_ptr &callback) { opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; - request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], - static_cast(mock_session->GetRequest()->body_.size())); + const bool parsed = request_body.ParseFromArray( + &mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics(0).metrics_size() == 0) @@ -788,8 +800,14 @@ class OtlpHttpMetricExporterTestPeer : public ::testing::Test const std::shared_ptr &callback) { opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest request_body; - request_body.ParseFromArray(&mock_session->GetRequest()->body_[0], - static_cast(mock_session->GetRequest()->body_.size())); + const bool parsed = request_body.ParseFromArray( + &mock_session->GetRequest()->body_[0], + static_cast(mock_session->GetRequest()->body_.size())); + EXPECT_TRUE(parsed); + if (!parsed) + { + return; + } if (request_body.resource_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics_size() == 0 || request_body.resource_metrics(0).scope_metrics(0).metrics_size() == 0) diff --git a/exporters/otlp/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc index b722c6c055..3801afe043 100644 --- a/exporters/otlp/test/otlp_log_recordable_test.cc +++ b/exporters/otlp/test/otlp_log_recordable_test.cc @@ -19,6 +19,7 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/readable_log_record.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/resource/resource.h" @@ -253,11 +254,14 @@ TEST(OtlpLogRecordable, SetEventName) * unsigned int, and uint64_t. To avoid writing test cases for each, we can * use a template approach to test all int types. */ +namespace +{ template struct OtlpLogRecordableIntAttributeTest : public testing::Test { using IntParamType = T; }; +} // namespace using IntTypes = testing::Types; TYPED_TEST_SUITE(OtlpLogRecordableIntAttributeTest, IntTypes); @@ -419,6 +423,135 @@ TEST(OtlpLogRecordable, PopulateRequestSameScope) EXPECT_EQ(req.resource_logs(0).scope_logs(0).log_records_size(), 2); EXPECT_EQ(req.resource_logs(0).scope_logs(0).scope().name(), "lib"); } + +TEST(OtlpLogRecordable, AttributeCountLimitReportsDroppedCount) +{ + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_count_limit = 2; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + rec.SetAttribute("a", static_cast(1)); + rec.SetAttribute("b", static_cast(2)); + rec.SetAttribute("c", static_cast(3)); + rec.SetAttribute("d", static_cast(4)); + + EXPECT_EQ(rec.log_record().attributes_size(), 2); + EXPECT_EQ(rec.log_record().dropped_attributes_count(), 2u); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitTruncatesString) +{ + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 4; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + rec.SetAttribute("k", nostd::string_view("abcdefghij")); + + ASSERT_EQ(rec.log_record().attributes_size(), 1); + EXPECT_EQ(rec.log_record().attributes(0).value().string_value(), "abcd"); + EXPECT_EQ(rec.log_record().dropped_attributes_count(), 0u); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitTruncatesArrayElements) +{ + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 3; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + nostd::string_view values[] = {nostd::string_view("aaaaaa"), nostd::string_view("bb"), + nostd::string_view("cccc")}; + rec.SetAttribute("k", nostd::span(values, 3)); + + ASSERT_EQ(rec.log_record().attributes_size(), 1); + const auto &array = rec.log_record().attributes(0).value().array_value(); + ASSERT_EQ(array.values_size(), 3); + EXPECT_EQ(array.values(0).string_value(), "aaa"); + EXPECT_EQ(array.values(1).string_value(), "bb"); + EXPECT_EQ(array.values(2).string_value(), "ccc"); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitLeavesNonStringTypesUnchanged) +{ + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 1; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + rec.SetAttribute("i", static_cast(1234567890)); + rec.SetAttribute("d", 3.14); + rec.SetAttribute("b", true); + + ASSERT_EQ(rec.log_record().attributes_size(), 3); + EXPECT_EQ(rec.log_record().attributes(0).value().int_value(), 1234567890); + EXPECT_DOUBLE_EQ(rec.log_record().attributes(1).value().double_value(), 3.14); + EXPECT_EQ(rec.log_record().attributes(2).value().bool_value(), true); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitPreservesUtf8MultiByte) +{ + // "h\xC3\xA9llo" is "héllo" in UTF-8; 'é' occupies bytes 1-2. With a 2-byte + // budget the truncator keeps 'h' alone and drops the partial 'é', so the + // resulting protobuf string_value stays valid UTF-8. + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 2; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + rec.SetAttribute("k", nostd::string_view("h\xC3\xA9llo")); + + ASSERT_EQ(rec.log_record().attributes_size(), 1); + EXPECT_EQ(rec.log_record().attributes(0).value().string_value(), "h"); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitKeepsCompletedUtf8Sequence) +{ + // Same input as above, with a 3-byte budget that exactly fits 'h' + 'é'. + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 3; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + rec.SetAttribute("k", nostd::string_view("h\xC3\xA9llo")); + + ASSERT_EQ(rec.log_record().attributes_size(), 1); + EXPECT_EQ(rec.log_record().attributes(0).value().string_value(), "h\xC3\xA9"); +} + +TEST(OtlpLogRecordable, AttributeValueLengthLimitTruncatesBytesAttribute) +{ + opentelemetry::sdk::logs::LogRecordLimits limits; + limits.attribute_value_length_limit = 3; + + OtlpLogRecordable rec; + rec.SetLogRecordLimits(limits); + const uint8_t bytes_in[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + rec.SetAttribute("k", nostd::span(bytes_in, 5)); + + ASSERT_EQ(rec.log_record().attributes_size(), 1); + const auto &b = rec.log_record().attributes(0).value().bytes_value(); + ASSERT_EQ(b.size(), 3u); + EXPECT_EQ(static_cast(b[0]), 0x01); + EXPECT_EQ(static_cast(b[1]), 0x02); + EXPECT_EQ(static_cast(b[2]), 0x03); +} + +TEST(OtlpLogRecordable, DefaultRecordAppliesNoLimitUntilConfigured) +{ + // No SetLogRecordLimits() call: a bare recordable applies no cap. The + // spec-default count limit is injected by the LoggerProvider wiring, not by + // the recordable itself, so every attribute is kept. + OtlpLogRecordable rec; + for (int i = 0; i < 200; ++i) + { + rec.SetAttribute("attr_" + std::to_string(i), static_cast(i)); + } + EXPECT_EQ(rec.log_record().attributes_size(), 200); + EXPECT_EQ(rec.log_record().dropped_attributes_count(), 0u); +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/test/otlp_populate_attribute_utils_test.cc b/exporters/otlp/test/otlp_populate_attribute_utils_test.cc new file mode 100644 index 0000000000..ed1f5f5adb --- /dev/null +++ b/exporters/otlp/test/otlp_populate_attribute_utils_test.cc @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +// --------------------------------------------------------------------------- +// Utf8SafePrefixLength +// --------------------------------------------------------------------------- +// +// "h\xC3\xA9llo" is "héllo" in UTF-8 (6 bytes). 'é' occupies bytes 1-2 as a +// 2-byte sequence (0xC3 0xA9). These tests pin the behavior at the boundary. + +TEST(Utf8SafePrefixLength, ReturnsFullSizeWhenValueShorterThanMax) +{ + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("abc", 10), 3u); +} + +TEST(Utf8SafePrefixLength, ReturnsZeroForZeroBudget) +{ + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("anything", 0), 0u); +} + +TEST(Utf8SafePrefixLength, AsciiBoundaryEqualsRawByteCount) +{ + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("0123456789", 5), 5u); +} + +TEST(Utf8SafePrefixLength, DropsPartialMultiByteSequenceAtBoundary) +{ + // limit=2: 'h' fits at byte 0; the next codepoint 'é' would span bytes 1-2, + // ending past the limit, so the algorithm stops at byte 1. + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("h\xC3\xA9llo", 2), 1u); +} + +TEST(Utf8SafePrefixLength, KeepsCompletedMultiByteSequenceWithinBudget) +{ + // limit=3: 'h' (1) + 'é' (2) = 3 bytes exactly, still valid UTF-8. + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("h\xC3\xA9llo", 3), 3u); +} + +TEST(Utf8SafePrefixLength, MalformedContinuationFallsBackToByteCount) +{ + // 0xC3 lead byte announces a 2-byte sequence, but 0x28 ('(') is not a valid + // continuation byte (not in 0x80-0xBF). The lead is treated as a one-byte + // unit, then '(' is its own one-byte unit, yielding a 2-byte prefix. + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("\xC3(abc", 2), 2u); +} + +TEST(Utf8SafePrefixLength, InvalidLeadByteCountedAsOneByte) +{ + // 0xFF is not a valid UTF-8 lead byte (>= 0xF8). The algorithm treats it as + // a one-byte unit, identical to raw byte truncation in this case. + std::string value("\xFF\xFF\xFF\xFF\xFF", 5); + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength(value, 3), 3u); +} + +TEST(Utf8SafePrefixLength, TruncatedTailLeadByteFallsBack) +{ + // String ends mid-sequence (lone 0xC3 at the tail); the algorithm should + // treat it as one byte rather than reading past the buffer. + EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("ab\xC3", 10), 3u); +} + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/test/otlp_recordable_benchmark.cc b/exporters/otlp/test/otlp_recordable_benchmark.cc index 6c672c9217..2d5202d71d 100644 --- a/exporters/otlp/test/otlp_recordable_benchmark.cc +++ b/exporters/otlp/test/otlp_recordable_benchmark.cc @@ -3,38 +3,72 @@ // clang-format off // -// 2026-06-18T21:05:11+00:00 +// ~/build/exporters/otlp/otlp_recordable_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-01T15:57:22+00:00 // Running /home/devuser/build/exporters/otlp/otlp_recordable_benchmark -// Run on (32 X 5175.97 MHz CPU s) +// Run on (32 X 5700 MHz CPU s) // CPU Caches: // L1 Data 48 KiB (x16) // L1 Instruction 32 KiB (x16) // L2 Unified 2048 KiB (x16) // L3 Unified 36864 KiB (x1) -// Load Average: 0.61, 1.30, 1.33 -// ----------------------------------------------------------------------------------- -// Benchmark Time CPU Iterations -// ----------------------------------------------------------------------------------- -// OtlpSpanFixture/Minimal_mean 355 ns 357 ns 5 -// OtlpSpanFixture/Minimal_median 355 ns 358 ns 5 -// OtlpSpanFixture/Minimal_stddev 0.489 ns 0.790 ns 5 -// OtlpSpanFixture/Minimal_cv 0.14 % 0.22 % 5 -// OtlpSpanFixture/Nominal_mean 617 ns 619 ns 5 -// OtlpSpanFixture/Nominal_median 617 ns 620 ns 5 -// OtlpSpanFixture/Nominal_stddev 0.960 ns 1.25 ns 5 -// OtlpSpanFixture/Nominal_cv 0.16 % 0.20 % 5 -// OtlpSpanFixture/MaxAttributes_mean 8014 ns 8016 ns 5 items_per_second=16.0018M/s -// OtlpSpanFixture/MaxAttributes_median 7737 ns 7739 ns 5 items_per_second=16.5389M/s -// OtlpSpanFixture/MaxAttributes_stddev 419 ns 418 ns 5 items_per_second=819.876k/s -// OtlpSpanFixture/MaxAttributes_cv 5.23 % 5.22 % 5 items_per_second=5.12% -// BM_PopulateRequest/span_count:1_mean 0.398 us 0.398 us 5 items_per_second=2.51517M/s -// BM_PopulateRequest/span_count:1_median 0.397 us 0.397 us 5 items_per_second=2.52167M/s -// BM_PopulateRequest/span_count:1_stddev 0.002 us 0.002 us 5 items_per_second=12.1068k/s -// BM_PopulateRequest/span_count:1_cv 0.48 % 0.48 % 5 items_per_second=0.48% -// BM_PopulateRequest/span_count:512_mean 26.3 us 26.3 us 5 items_per_second=19.4714M/s -// BM_PopulateRequest/span_count:512_median 26.3 us 26.3 us 5 items_per_second=19.4746M/s -// BM_PopulateRequest/span_count:512_stddev 0.078 us 0.077 us 5 items_per_second=56.7323k/s -// BM_PopulateRequest/span_count:512_cv 0.30 % 0.29 % 5 items_per_second=0.29% +// Load Average: 2.05, 1.65, 1.67 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// -------------------------------------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// -------------------------------------------------------------------------------------------------------------------- +// OtlpRecordableFixture/RecordMinimalSpan_mean 362 ns 364 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_median 363 ns 365 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_stddev 1.77 ns 1.97 ns 5 +// OtlpRecordableFixture/RecordMinimalSpan_cv 0.49 % 0.54 % 5 +// OtlpRecordableFixture/RecordNominalSpan_mean 638 ns 639 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_median 636 ns 637 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_stddev 8.01 ns 8.04 ns 5 +// OtlpRecordableFixture/RecordNominalSpan_cv 1.26 % 1.26 % 5 +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_mean 414 ns 416 ns 5 items_per_second=2.40287M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_median 414 ns 416 ns 5 items_per_second=2.40294M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_stddev 1.94 ns 2.14 ns 5 items_per_second=12.3138k/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:1_cv 0.47 % 0.51 % 5 items_per_second=0.51% +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_mean 880 ns 881 ns 5 items_per_second=11.3516M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_median 883 ns 883 ns 5 items_per_second=11.3193M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_stddev 12.8 ns 13.0 ns 5 items_per_second=167.444k/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:10_cv 1.46 % 1.47 % 5 items_per_second=1.48% +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_mean 7293 ns 7297 ns 5 items_per_second=17.7189M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_median 6834 ns 6839 ns 5 items_per_second=18.7175M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_stddev 842 ns 841 ns 5 items_per_second=1.922M/s +// OtlpRecordableFixture/RecordSpanWithAttributes/attribute_count:128_cv 11.55 % 11.53 % 5 items_per_second=10.85% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_mean 502 ns 503 ns 5 items_per_second=1.98937M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_median 501 ns 503 ns 5 items_per_second=1.99001M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_stddev 1.04 ns 0.917 ns 5 items_per_second=3.63084k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:1_cv 0.21 % 0.18 % 5 items_per_second=0.18% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_mean 1705 ns 1707 ns 5 items_per_second=5.86103M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_median 1699 ns 1700 ns 5 items_per_second=5.88169M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_stddev 29.0 ns 29.0 ns 5 items_per_second=99.4244k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:10_cv 1.70 % 1.70 % 5 items_per_second=1.70% +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_mean 16750 ns 16750 ns 5 items_per_second=7.64354M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_median 16757 ns 16756 ns 5 items_per_second=7.63925M/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_stddev 287 ns 287 ns 5 items_per_second=130.651k/s +// OtlpRecordableFixture/RecordSpanWithEvents/event_count:128_cv 1.71 % 1.71 % 5 items_per_second=1.71% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_mean 511 ns 513 ns 5 items_per_second=1.94877M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_median 510 ns 512 ns 5 items_per_second=1.95458M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_stddev 5.76 ns 6.17 ns 5 items_per_second=23.304k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:1_cv 1.13 % 1.20 % 5 items_per_second=1.20% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_mean 1784 ns 1786 ns 5 items_per_second=5.60068M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_median 1785 ns 1787 ns 5 items_per_second=5.59477M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_stddev 6.08 ns 6.23 ns 5 items_per_second=19.5651k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:10_cv 0.34 % 0.35 % 5 items_per_second=0.35% +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_mean 19515 ns 19517 ns 5 items_per_second=6.62769M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_median 18663 ns 18666 ns 5 items_per_second=6.85722M/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_stddev 2403 ns 2403 ns 5 items_per_second=703.536k/s +// OtlpRecordableFixture/RecordSpanWithLinks/link_count:128_cv 12.31 % 12.31 % 5 items_per_second=10.62% +// BM_OtlpPopulateRequest/span_count:1_mean 0.387 us 0.387 us 5 items_per_second=2.58274M/s +// BM_OtlpPopulateRequest/span_count:1_median 0.387 us 0.387 us 5 items_per_second=2.58201M/s +// BM_OtlpPopulateRequest/span_count:1_stddev 0.004 us 0.004 us 5 items_per_second=23.6738k/s +// BM_OtlpPopulateRequest/span_count:1_cv 0.92 % 0.92 % 5 items_per_second=0.92% +// BM_OtlpPopulateRequest/span_count:512_mean 23.6 us 23.6 us 5 items_per_second=21.7343M/s +// BM_OtlpPopulateRequest/span_count:512_median 23.5 us 23.5 us 5 items_per_second=21.7479M/s +// BM_OtlpPopulateRequest/span_count:512_stddev 0.260 us 0.259 us 5 items_per_second=238.588k/s +// BM_OtlpPopulateRequest/span_count:512_cv 1.10 % 1.10 % 5 items_per_second=1.10% // // clang-format on @@ -47,7 +81,6 @@ #include #include -#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" #include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" @@ -55,21 +88,15 @@ #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/span.h" -#include "opentelemetry/nostd/string_view.h" -#include "opentelemetry/nostd/utility.h" #include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" -#include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/exporter.h" -#include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/test_common/sdk/trace/test_utils.h" #include "opentelemetry/trace/span.h" -#include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" -#include "opentelemetry/trace/trace_flags.h" -#include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/tracer.h" // clang-format off @@ -79,9 +106,10 @@ #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep // clang-format on -namespace otlp = opentelemetry::exporter::otlp; -namespace trace_sdk = opentelemetry::sdk::trace; -namespace trace_api = opentelemetry::trace; +namespace otlp = opentelemetry::exporter::otlp; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace trace_api = opentelemetry::trace; +namespace test_utils = opentelemetry::test_common; namespace { @@ -107,69 +135,22 @@ class NullSpanExporter final : public trace_sdk::SpanExporter bool Shutdown(std::chrono::microseconds) noexcept override { return true; } }; -// A span processor that queues completed recordables into an internal buffer -// without serialising or destroying them mirroring the batch processor. -class BufferingProcessor final : public trace_sdk::SpanProcessor -{ -public: - using Buffer = std::vector>; - - explicit BufferingProcessor(std::unique_ptr exporter) - : exporter_(std::move(exporter)) - {} - - std::unique_ptr MakeRecordable() noexcept override - { - return exporter_->MakeRecordable(); - } - - void OnStart(trace_sdk::Recordable &, const opentelemetry::trace::SpanContext &) noexcept override - {} - - void OnEnd(std::unique_ptr &&span) noexcept override - { - buffer_.push_back(std::move(span)); - } - - bool ForceFlush(std::chrono::microseconds) noexcept override - { - buffer_.clear(); - return true; - } - bool Shutdown(std::chrono::microseconds) noexcept override { return true; } - -private: - std::unique_ptr exporter_; - Buffer buffer_; -}; - -const opentelemetry::sdk::resource::Resource &TestResource() -{ - static const auto resource = opentelemetry::sdk::resource::Resource::Create( - {{"service.name", opentelemetry::nostd::string_view{"benchmark_service"}}, - {"service.version", opentelemetry::nostd::string_view{"1.0.0"}}}); - return resource; -} - -const opentelemetry::sdk::instrumentationscope::InstrumentationScope &TestScope() -{ - static auto scope = opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create( - "benchmark_scope", "1.0.0", "https://opentelemetry.io/schemas/1.24.0", - {{"scope.source", "benchmark"}}); - return *scope; -} - // Benchmark fixture: each benchmark function gets its own TracerProvider backed -// by a BufferingProcessor with a NullSpanExporter. -class OtlpSpanFixture : public benchmark::Fixture +// by a BufferingSpanProcessor with a NullSpanExporter. +class OtlpRecordableFixture : public benchmark::Fixture { public: + using benchmark::Fixture::SetUp; + void SetUp(benchmark::State &) override { - auto processor = std::make_unique(std::make_unique()); - provider_ = std::make_shared(std::move(processor)); - tracer_ = provider_->GetTracer(TestScope().GetName(), TestScope().GetVersion(), - TestScope().GetSchemaURL()); + auto processor = + std::make_unique(std::make_unique()); + provider_ = std::make_shared(std::move(processor)); + tracer_ = provider_->GetTracer(test_utils::TestScope().GetName(), + test_utils::TestScope().GetVersion(), + test_utils::TestScope().GetSchemaURL()); + test_utils::InitializeSpanTestData(); } protected: @@ -178,39 +159,13 @@ class OtlpSpanFixture : public benchmark::Fixture }; // Benchmark argument values. -constexpr int kMaxAttributes = 128; // default attribute count limit -constexpr int kBatchSmall = 1; // single span -constexpr int kBatchLarge = 512; // default batch size - -// Pre-built keys for the max-attributes benchmark (matches SDK default attribute limit). -const std::vector &MaxAttributeKeys() -{ - static const std::vector keys = []() { - std::vector result; - result.reserve(kMaxAttributes); - for (int key_idx = 0; key_idx < kMaxAttributes; ++key_idx) - { - result.push_back("attribute." + std::to_string(key_idx)); - } - return result; - }(); - return keys; -} +constexpr std::size_t kBatchSmall = 1; // single span +constexpr std::size_t kBatchLarge = 512; // default batch size // Build a batch of N fully-populated OtlpRecordable spans for PopulateRequest benchmarks. std::vector> MakeSpanBatch( std::size_t span_count) { - constexpr uint8_t kTraceIdBytes[opentelemetry::trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8}; - constexpr uint8_t kSpanIdBytes[opentelemetry::trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; - constexpr uint8_t kParentIdBytes[opentelemetry::trace::SpanId::kSize] = {8, 7, 6, 5, 4, 3, 2, 1}; - const opentelemetry::trace::TraceId trace_id{kTraceIdBytes}; - const opentelemetry::trace::SpanId span_id{kSpanIdBytes}; - const opentelemetry::trace::SpanId parent_id{kParentIdBytes}; - const opentelemetry::trace::SpanContext ctx{ - trace_id, span_id, - opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, true}; const auto now = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); std::vector> batch; @@ -218,13 +173,13 @@ std::vector> MakeSpanBatc for (std::size_t span_idx = 0; span_idx < span_count; ++span_idx) { auto recordable = std::make_unique(); - recordable->SetIdentity(ctx, parent_id); + recordable->SetIdentity(test_utils::TestSpanContext(), opentelemetry::trace::SpanId{}); recordable->SetName("benchmark_span"); recordable->SetSpanKind(trace_api::SpanKind::kServer); recordable->SetStartTime(now); recordable->SetDuration(std::chrono::milliseconds(5)); - recordable->SetResource(TestResource()); - recordable->SetInstrumentationScope(TestScope()); + recordable->SetResource(test_utils::TestResource()); + recordable->SetInstrumentationScope(test_utils::TestScope()); batch.push_back(std::move(recordable)); } return batch; @@ -240,80 +195,110 @@ std::unique_ptr CreateArena() } // namespace -// Baseline: start a span and end it immediately with no attributes. -BENCHMARK_DEFINE_F(OtlpSpanFixture, Minimal)(benchmark::State &state) +// Baseline: start a span and end it immediately with no attributes, links, or events. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordMinimalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartMinimalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordMinimalSpan)->Unit(benchmark::kNanosecond); + +// RecordNominalSpan span: starts a span with a representative mix of attribute types then ends the +// span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordNominalSpan)(benchmark::State &state) { for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span"); + auto span = test_utils::StartNominalSpan(*tracer_); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } } -BENCHMARK_REGISTER_F(OtlpSpanFixture, Minimal)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordNominalSpan)->Unit(benchmark::kNanosecond); -// Nominal span: representative mix of attribute types passed at creation. -BENCHMARK_DEFINE_F(OtlpSpanFixture, Nominal)(benchmark::State &state) +// RecordSpanWithAttributes: starts a span with N attributes (all string values) then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithAttributes)(benchmark::State &state) { - using SpanAttribute = - std::pair; - static const std::vector kAttributes = { - {"benchmark.string.short", opentelemetry::nostd::string_view{"abcdefgh"}}, - {"benchmark.string.medium", - opentelemetry::nostd::string_view{"abcdefghijklmnopqrstuvwxyz012345"}}, - {"benchmark.string.long", - opentelemetry::nostd::string_view{ - "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"}}, - {"benchmark.int", opentelemetry::common::AttributeValue{static_cast(1)}}, - {"benchmark.double", opentelemetry::common::AttributeValue{3.14159265358979}}, - {"benchmark.bool", opentelemetry::common::AttributeValue{true}}, - }; + const int64_t attribute_count = state.range(0); + const std::vector attributes = + test_utils::MakeAttributes(static_cast(attribute_count)); for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span", kAttributes); + auto span = test_utils::StartSpanWithAttributes(*tracer_, attributes); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } + state.SetItemsProcessed(state.iterations() * attribute_count); } -BENCHMARK_REGISTER_F(OtlpSpanFixture, Nominal)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithAttributes) + ->ArgName("attribute_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanAttributeLimit) + ->Unit(benchmark::kNanosecond); + +// RecordSpanWithEvents: starts a span via the tracer API with N events, then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithEvents)(benchmark::State &state) +{ + const int64_t event_count = state.range(0); -// Max-attributes span: fills the recordable to its attribute limit (128) with string values. -BENCHMARK_DEFINE_F(OtlpSpanFixture, MaxAttributes)(benchmark::State &state) + for (auto _ : state) + { + auto span = test_utils::StartSpanWithEvents(*tracer_, static_cast(event_count)); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * event_count); +} +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithEvents) + ->ArgName("event_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanEventLimit) + ->Unit(benchmark::kNanosecond); + +// RecordSpanWithLinks: starts a span via the tracer API with N links, then ends the span. +BENCHMARK_DEFINE_F(OtlpRecordableFixture, RecordSpanWithLinks)(benchmark::State &state) { - using SpanAttribute = - std::pair; - static const std::vector kAttributes = []() { - const auto &keys = MaxAttributeKeys(); - std::vector attributes; - attributes.reserve(kMaxAttributes); - for (const auto &key : keys) - { - attributes.emplace_back(key, opentelemetry::nostd::string_view{"value-string-attribute"}); - } - return attributes; - }(); + const int64_t link_count = state.range(0); + const std::vector link_entries = + test_utils::MakeLinkEntries(static_cast(link_count)); + for (auto _ : state) { - auto span = tracer_->StartSpan("benchmark_span", kAttributes); + auto span = test_utils::StartSpanWithLinks(*tracer_, link_entries); span->End(); state.PauseTiming(); provider_->ForceFlush(); state.ResumeTiming(); } - state.SetItemsProcessed(state.iterations() * kMaxAttributes); + state.SetItemsProcessed(state.iterations() * link_count); } -BENCHMARK_REGISTER_F(OtlpSpanFixture, MaxAttributes)->Unit(benchmark::kNanosecond); +BENCHMARK_REGISTER_F(OtlpRecordableFixture, RecordSpanWithLinks) + ->ArgName("link_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanLinkLimit) + ->Unit(benchmark::kNanosecond); // Benchmark the arena allocation + OtlpRecordableUtils::PopulateRequest for a // batch of N spans. This mirrors the path in OtlpGrpcExporter::Export. -static void BM_PopulateRequest(benchmark::State &state) +static void BM_OtlpPopulateRequest(benchmark::State &state) { - const std::size_t span_count = static_cast(state.range(0)); - auto batch = MakeSpanBatch(span_count); + const int64_t span_count = state.range(0); + auto batch = MakeSpanBatch(static_cast(span_count)); for (auto _ : state) { auto arena = CreateArena(); @@ -326,9 +311,9 @@ static void BM_PopulateRequest(benchmark::State &state) request); benchmark::DoNotOptimize(request); } - state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetItemsProcessed(state.iterations() * span_count); } -BENCHMARK(BM_PopulateRequest) +BENCHMARK(BM_OtlpPopulateRequest) ->ArgName("span_count") ->Arg(kBatchSmall) ->Arg(kBatchLarge) diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 56c4015fe2..2481383f85 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -24,6 +24,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -548,11 +549,14 @@ TEST(OtlpRecordable, PopulateRequestMissing) } } +namespace +{ template struct EmptyArrayAttributeTest : public testing::Test { using ElementType = T; }; +} // namespace using ArrayElementTypes = testing::Types; @@ -576,11 +580,14 @@ TYPED_TEST(EmptyArrayAttributeTest, SetEmptyArrayAttribute) * unsigned int, and uint64_t. To avoid writing test cases for each, we can * use a template approach to test all int types. */ +namespace +{ template struct IntAttributeTest : public testing::Test { using IntParamType = T; }; +} // namespace using IntTypes = testing::Types; TYPED_TEST_SUITE(IntAttributeTest, IntTypes); @@ -657,6 +664,7 @@ TEST(OtlpRecordable, SetUint64ArrayOverflowAsStringPerSpec) EXPECT_EQ(array_v.values(1).string_value(), std::to_string(overflow_val)); } +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. TEST(OtlpRecordableTest, TestCollectionLimits) { // Initialize recordable with strict limits: @@ -698,6 +706,7 @@ TEST(OtlpRecordableTest, TestCollectionLimits) EXPECT_EQ(event_list[0].dropped_attributes_count(), 1); } +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. TEST(OtlpRecordableTest, TestLinkLimits) { // Limits: Max Links: 2, Max Attributes Per Link: 1 @@ -779,6 +788,275 @@ TEST(OtlpRecordable, PopulateRequestSameScope) EXPECT_EQ(req.resource_spans(0).scope_spans(0).spans_size(), 2); EXPECT_EQ(req.resource_spans(0).scope_spans(0).scope().name(), "lib"); } + +TEST(OtlpRecordable, SpanLimits) +{ + OtlpRecordable data; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 1; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 1; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 1; + + constexpr const char *kKey1 = "one"; + constexpr const char *kKey2 = "two"; + constexpr const char *kValue1 = "1234"; + constexpr const char *kValue2 = "5678"; + + std::map attribute_collection{{kKey1, kValue1}, {kKey2, kValue2}}; + + const auto &proto_span = data.span(); + + data.SetSpanLimits(limits); + + // span attribute count and length limits + data.SetAttribute(kKey1, kValue1); + data.SetAttribute(kKey2, kValue2); + + EXPECT_EQ(proto_span.attributes_size(), 1); + EXPECT_EQ(proto_span.dropped_attributes_count(), 1); + EXPECT_EQ(proto_span.attributes(0).value().string_value(), "12"); + + // event count and per-event attribute count limits + data.AddEvent("event1", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + data.AddEvent("event2", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + + ASSERT_EQ(proto_span.events_size(), 1); + EXPECT_EQ(proto_span.dropped_events_count(), 1); + const auto &event = proto_span.events(0); + EXPECT_EQ(event.dropped_attributes_count(), 1); + EXPECT_EQ(event.name(), "event1"); + const auto &event_attributes = event.attributes(); + EXPECT_EQ(event_attributes.size(), 1); + EXPECT_EQ(event_attributes.at(0).value().string_value(), "12"); + + // link count and per-link attribute count limits + data.AddLink(trace_api::SpanContext(true, false), common::MakeAttributes(attribute_collection)); + data.AddLink(trace_api::SpanContext(true, false), common::MakeAttributes(attribute_collection)); + ASSERT_EQ(proto_span.links_size(), 1); + EXPECT_EQ(proto_span.dropped_links_count(), 1); + const auto &link = proto_span.links(0); + EXPECT_EQ(link.dropped_attributes_count(), 1); + const auto &link_attributes = link.attributes(); + EXPECT_EQ(link_attributes.size(), 1); + EXPECT_EQ(link_attributes.at(0).value().string_value(), "12"); +} + +TEST(OtlpRecordable, SpanLimitsNoLimitDefault) +{ + constexpr std::uint32_t kMaxCount = 500; + OtlpRecordable recordable; + std::map attributes; + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = std::to_string(i); + } + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + recordable.SetAttribute("attribute_" + std::to_string(i), i); + recordable.AddEvent("event_" + std::to_string(i), std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + recordable.AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + } + + EXPECT_EQ(recordable.span().attributes_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_attributes_count(), 0u); + EXPECT_EQ(recordable.span().events_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_events_count(), 0u); + EXPECT_EQ(recordable.span().links_size(), kMaxCount); + EXPECT_EQ(recordable.span().dropped_links_count(), 0u); + EXPECT_EQ(recordable.span().events(0).dropped_attributes_count(), 0u); + EXPECT_EQ(recordable.span().links(0).dropped_attributes_count(), 0u); +} + +// TODO: remove this test case once the deprecated otlp options span limit fields are removed. +TEST(OtlpRecordable, SpanLimitsFieldsMerged) +{ + constexpr std::uint32_t kOtlpAttributeCount = 2; + constexpr std::uint32_t kOtlpEventCount = 3; + constexpr std::uint32_t kOtlpLinkCount = 4; + constexpr std::uint32_t kOtlpEventAttributeCount = 5; + constexpr std::uint32_t kOtlpLinkAttributeCount = 6; + + trace_sdk::SpanLimits more_restrictive_limits; + more_restrictive_limits.attribute_count_limit = 1; + more_restrictive_limits.event_count_limit = 2; + more_restrictive_limits.link_count_limit = 3; + more_restrictive_limits.event_attribute_count_limit = 4; + more_restrictive_limits.link_attribute_count_limit = 5; + more_restrictive_limits.attribute_value_length_limit = 2; + + trace_sdk::SpanLimits less_restrictive_limits; + less_restrictive_limits.attribute_count_limit = 3; + less_restrictive_limits.event_count_limit = 4; + less_restrictive_limits.link_count_limit = 5; + less_restrictive_limits.event_attribute_count_limit = 6; + less_restrictive_limits.link_attribute_count_limit = 7; + less_restrictive_limits.attribute_value_length_limit = 6; + + auto make_recordable = + [&](const trace_sdk::SpanLimits &limits) -> std::unique_ptr { + auto recordable = + std::make_unique(kOtlpAttributeCount, kOtlpEventCount, kOtlpLinkCount, + kOtlpEventAttributeCount, kOtlpLinkAttributeCount); + recordable->SetSpanLimits(limits); + return recordable; + }; + + auto make_attributes = []() -> std::map { + constexpr std::uint32_t kMaxCount = 10; + std::map attributes; + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = static_cast(i); + } + return attributes; + }; + + const std::map empty_attributes; + const auto event_time = std::chrono::system_clock::time_point{std::chrono::seconds{1000000000}}; + + // attribute_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restrictive_limits); + const auto attributes = make_attributes(); + for (const auto &attr : attributes) + { + recordable->SetAttribute(attr.first, attr.second); + } + EXPECT_EQ(recordable->span().attributes_size(), + static_cast(more_restrictive_limits.attribute_count_limit)); + EXPECT_EQ(recordable->span().dropped_attributes_count(), + attributes.size() - more_restrictive_limits.attribute_count_limit); + } + // attribute_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restrictive_limits); + const auto attributes = make_attributes(); + for (const auto &attr : attributes) + { + recordable->SetAttribute(attr.first, attr.second); + } + EXPECT_EQ(recordable->span().attributes_size(), static_cast(kOtlpAttributeCount)); + EXPECT_EQ(recordable->span().dropped_attributes_count(), + attributes.size() - kOtlpAttributeCount); + } + + // attribute_value_length_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restrictive_limits); + recordable->SetAttribute("value", nostd::string_view("abcdefghij")); + EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), + more_restrictive_limits.attribute_value_length_limit); + } + // attribute_value_length_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restrictive_limits); + recordable->SetAttribute("value", nostd::string_view("abcdefghij")); + EXPECT_EQ(recordable->span().attributes(0).value().string_value().size(), + less_restrictive_limits.attribute_value_length_limit); + } + + // event_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restrictive_limits); + recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); + EXPECT_EQ(recordable->span().events_size(), + static_cast(more_restrictive_limits.event_count_limit)); + EXPECT_EQ(recordable->span().dropped_events_count(), 1u); + } + // event_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restrictive_limits); + recordable->AddEvent("event_one", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_two", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_three", event_time, common::MakeAttributes(empty_attributes)); + recordable->AddEvent("event_four", event_time, common::MakeAttributes(empty_attributes)); + EXPECT_EQ(recordable->span().events_size(), static_cast(kOtlpEventCount)); + EXPECT_EQ(recordable->span().dropped_events_count(), 1u); + } + + // link_count_limit: config limits are more restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(more_restrictive_limits); + for (std::uint32_t i = 0; i < kOtlpLinkCount; ++i) + { + recordable->AddLink(trace::SpanContext::GetInvalid(), + common::MakeAttributes(empty_attributes)); + } + EXPECT_EQ(recordable->span().links_size(), + static_cast(more_restrictive_limits.link_count_limit)); + EXPECT_EQ(recordable->span().dropped_links_count(), 1u); + } + // link_count_limit: config limits are less restrictive than the deprecated otlp options. + { + auto recordable = make_recordable(less_restrictive_limits); + for (std::uint32_t i = 0; i < less_restrictive_limits.link_count_limit; ++i) + { + recordable->AddLink(trace::SpanContext::GetInvalid(), + common::MakeAttributes(empty_attributes)); + } + EXPECT_EQ(recordable->span().links_size(), static_cast(kOtlpLinkCount)); + EXPECT_EQ(recordable->span().dropped_links_count(), 1u); + } + + // event_attribute_count_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restrictive_limits); + const auto attributes = make_attributes(); + recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().events(0).attributes_size(), + static_cast(more_restrictive_limits.event_attribute_count_limit)); + EXPECT_EQ(recordable->span().events(0).dropped_attributes_count(), + attributes.size() - more_restrictive_limits.event_attribute_count_limit); + } + // event_attribute_count_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restrictive_limits); + const auto attributes = make_attributes(); + recordable->AddEvent("test_event", event_time, common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().events(0).attributes_size(), + static_cast(kOtlpEventAttributeCount)); + EXPECT_EQ(recordable->span().events(0).dropped_attributes_count(), + attributes.size() - kOtlpEventAttributeCount); + } + + // link_attribute_count_limit: config limits are more restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(more_restrictive_limits); + const auto attributes = make_attributes(); + recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().links(0).attributes_size(), + static_cast(more_restrictive_limits.link_attribute_count_limit)); + EXPECT_EQ(recordable->span().links(0).dropped_attributes_count(), + attributes.size() - more_restrictive_limits.link_attribute_count_limit); + } + // link_attribute_count_limit: config limits are less restrictive than the deprecated otlp + // options. + { + auto recordable = make_recordable(less_restrictive_limits); + const auto attributes = make_attributes(); + recordable->AddLink(trace::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + EXPECT_EQ(recordable->span().links(0).attributes_size(), + static_cast(kOtlpLinkAttributeCount)); + EXPECT_EQ(recordable->span().links(0).dropped_attributes_count(), + attributes.size() - kOtlpLinkAttributeCount); + } +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/prometheus/BUILD b/exporters/prometheus/BUILD index 71d8b00031..f9165945b5 100644 --- a/exporters/prometheus/BUILD +++ b/exporters/prometheus/BUILD @@ -58,6 +58,7 @@ cc_library( deps = [ "//api", "//sdk:headers", + "//sdk/src/metrics", "@com_github_jupp0r_prometheus_cpp//core", "@com_github_jupp0r_prometheus_cpp//pull", ], diff --git a/exporters/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index 55105bace8..bcca6bb73c 100644 --- a/exporters/prometheus/src/exporter_utils.cc +++ b/exporters/prometheus/src/exporter_utils.cc @@ -143,8 +143,6 @@ std::vector PrometheusExporterUtils::TranslateT { for (const auto &metric_data : instrumentation_info.metric_data_) { - auto origin_name = metric_data.instrument_descriptor.name_; - auto unit = metric_data.instrument_descriptor.unit_; prometheus_client::MetricFamily metric_family; metric_family.help = metric_data.instrument_descriptor.description_; auto front = metric_data.point_data_attr_.front(); @@ -290,14 +288,6 @@ std::string PrometheusExporterUtils::SanitizeNames(std::string name) return name; } -#if OPENTELEMETRY_HAVE_WORKING_REGEX -const std::regex INVALID_CHARACTERS_PATTERN("[^a-zA-Z0-9]"); -const std::regex CHARACTERS_BETWEEN_BRACES_PATTERN("\\{(.*?)\\}"); -const std::regex SANITIZE_LEADING_UNDERSCORES("^_+"); -const std::regex SANITIZE_TRAILING_UNDERSCORES("_+$"); -const std::regex SANITIZE_CONSECUTIVE_UNDERSCORES("[_]{2,}"); -#endif - std::string PrometheusExporterUtils::GetEquivalentPrometheusUnit( const std::string &raw_metric_unit_name) { @@ -373,6 +363,7 @@ std::string PrometheusExporterUtils::GetPrometheusPerUnit(const std::string &per std::string PrometheusExporterUtils::RemoveUnitPortionInBraces(const std::string &unit) { #if OPENTELEMETRY_HAVE_WORKING_REGEX + static const std::regex CHARACTERS_BETWEEN_BRACES_PATTERN("\\{(.*?)\\}"); return std::regex_replace(unit, CHARACTERS_BETWEEN_BRACES_PATTERN, ""); #else bool in_braces = false; @@ -427,6 +418,10 @@ std::string PrometheusExporterUtils::ConvertRateExpressedToPrometheusUnit( std::string PrometheusExporterUtils::CleanUpString(const std::string &str) { #if OPENTELEMETRY_HAVE_WORKING_REGEX + static const std::regex INVALID_CHARACTERS_PATTERN("[^a-zA-Z0-9]"); + static const std::regex SANITIZE_LEADING_UNDERSCORES("^_+"); + static const std::regex SANITIZE_TRAILING_UNDERSCORES("_+$"); + static const std::regex SANITIZE_CONSECUTIVE_UNDERSCORES("[_]{2,}"); std::string cleaned_string = std::regex_replace(str, INVALID_CHARACTERS_PATTERN, "_"); cleaned_string = std::regex_replace(cleaned_string, SANITIZE_CONSECUTIVE_UNDERSCORES, "_"); cleaned_string = std::regex_replace(cleaned_string, SANITIZE_TRAILING_UNDERSCORES, ""); diff --git a/exporters/prometheus/test/collector_test.cc b/exporters/prometheus/test/collector_test.cc index 20c3070cec..e333c22ff9 100644 --- a/exporters/prometheus/test/collector_test.cc +++ b/exporters/prometheus/test/collector_test.cc @@ -20,6 +20,8 @@ using opentelemetry::exporter::metrics::PrometheusCollector; using opentelemetry::sdk::metrics::MetricProducer; using opentelemetry::sdk::metrics::ResourceMetrics; +namespace +{ class MockMetricProducer : public MetricProducer { TestDataPoints test_data_points_; @@ -43,7 +45,10 @@ class MockMetricProducer : public MetricProducer std::chrono::microseconds sleep_ms_; size_t data_sent_size_{0}; }; +} // namespace +namespace +{ class MockMetricReader : public opentelemetry::sdk::metrics::MetricReader { public: @@ -61,6 +66,7 @@ class MockMetricReader : public opentelemetry::sdk::metrics::MetricReader void OnInitialized() noexcept override {} }; +} // namespace // ==================== Test for addMetricsData() function ====================== diff --git a/exporters/prometheus/test/exporter_utils_test.cc b/exporters/prometheus/test/exporter_utils_test.cc index e43b930331..92ba79eba9 100644 --- a/exporters/prometheus/test/exporter_utils_test.cc +++ b/exporters/prometheus/test/exporter_utils_test.cc @@ -286,6 +286,8 @@ TEST(PrometheusExporterUtils, TranslateToPrometheusHistogramNormal) ASSERT_EQ(checked_label_num, 3); } +namespace +{ class SanitizeTest : public ::testing::Test { Resource resource_ = Resource::Create({}); @@ -308,6 +310,7 @@ class SanitizeTest : public ::testing::Test EXPECT_EQ(result.begin()->metric.begin()->label.begin()->name, sanitized); } }; +} // namespace TEST_F(SanitizeTest, Label) { @@ -321,6 +324,8 @@ TEST_F(SanitizeTest, Label) TEST_F(SanitizeTest, Name) {} +namespace +{ class AttributeCollisionTest : public ::testing::Test { Resource resource_ = Resource::Create(ResourceAttributes{}); @@ -356,6 +361,7 @@ class AttributeCollisionTest : public ::testing::Test } } }; +} // namespace TEST_F(AttributeCollisionTest, SeparatesDistinctKeys) { diff --git a/exporters/zipkin/src/recordable.cc b/exporters/zipkin/src/recordable.cc index 24325ffd24..75cf962c6f 100644 --- a/exporters/zipkin/src/recordable.cc +++ b/exporters/zipkin/src/recordable.cc @@ -3,10 +3,8 @@ #include #include -#include #include #include -#include #include "nlohmann/json.hpp" @@ -39,14 +37,6 @@ namespace trace_api = opentelemetry::trace; namespace common = opentelemetry::common; namespace sdk = opentelemetry::sdk; -// constexpr needs keys to be constexpr, const is next best to use. -static const std::map kSpanKindMap = { - {trace_api::SpanKind::kClient, "CLIENT"}, - {trace_api::SpanKind::kServer, "SERVER"}, - {trace_api::SpanKind::kConsumer, "CONSUMER"}, - {trace_api::SpanKind::kProducer, "PRODUCER"}, -}; - // // See `attribute_value.h` for details. // @@ -262,10 +252,22 @@ void Recordable::SetDuration(std::chrono::nanoseconds duration) noexcept void Recordable::SetSpanKind(trace_api::SpanKind span_kind) noexcept { - auto span_iter = kSpanKindMap.find(span_kind); - if (span_iter != kSpanKindMap.end()) + switch (span_kind) { - span_["kind"] = span_iter->second; + case trace_api::SpanKind::kClient: + span_["kind"] = "CLIENT"; + break; + case trace_api::SpanKind::kServer: + span_["kind"] = "SERVER"; + break; + case trace_api::SpanKind::kConsumer: + span_["kind"] = "CONSUMER"; + break; + case trace_api::SpanKind::kProducer: + span_["kind"] = "PRODUCER"; + break; + default: + break; } } diff --git a/exporters/zipkin/test/zipkin_exporter_test.cc b/exporters/zipkin/test/zipkin_exporter_test.cc index 086357c07f..a15871cc89 100644 --- a/exporters/zipkin/test/zipkin_exporter_test.cc +++ b/exporters/zipkin/test/zipkin_exporter_test.cc @@ -60,6 +60,8 @@ class ZipkinExporterTestPeer : public ::testing::Test } }; +namespace +{ class MockHttpClient : public opentelemetry::ext::http::client::HttpClientSync { public: @@ -80,7 +82,10 @@ class MockHttpClient : public opentelemetry::ext::http::client::HttpClientSync const ext::http::client::Compression &), (noexcept, override)); }; +} // namespace +namespace +{ class IsValidMessageMatcher { public: @@ -102,6 +107,7 @@ class IsValidMessageMatcher private: std::string trace_id_; }; +} // namespace static PolymorphicMatcher IsValidMessage(const std::string &trace_id) { diff --git a/exporters/zipkin/test/zipkin_recordable_test.cc b/exporters/zipkin/test/zipkin_recordable_test.cc index d4b3cd4b53..df448d9e01 100644 --- a/exporters/zipkin/test/zipkin_recordable_test.cc +++ b/exporters/zipkin/test/zipkin_recordable_test.cc @@ -268,11 +268,14 @@ TEST(ZipkinSpanRecordable, SetResource) * unsigned int, and uint64_t. To avoid writing test cases for each, we can * use a template approach to test all int types. */ +namespace +{ template struct ZipkinIntAttributeTest : public testing::Test { using IntParamType = T; }; +} // namespace using IntTypes = testing::Types; TYPED_TEST_SUITE(ZipkinIntAttributeTest, IntTypes); diff --git a/ext/include/opentelemetry/ext/http/client/curl/http_operation_curl.h b/ext/include/opentelemetry/ext/http/client/curl/http_operation_curl.h index c4e2226f99..541b74a85c 100644 --- a/ext/include/opentelemetry/ext/http/client/curl/http_operation_curl.h +++ b/ext/include/opentelemetry/ext/http/client/curl/http_operation_curl.h @@ -39,9 +39,9 @@ namespace client { namespace curl { -const std::chrono::milliseconds kDefaultHttpConnTimeout(5000); // ms -const std::string kHttpStatusRegexp = "HTTP\\/\\d\\.\\d (\\d+)\\ .*"; -const std::string kHttpHeaderRegexp = "(.*)\\: (.*)\\n*"; +constexpr std::chrono::milliseconds kDefaultHttpConnTimeout{5000}; // ms +constexpr const char *kHttpStatusRegexp = "HTTP\\/\\d\\.\\d (\\d+)\\ .*"; +constexpr const char *kHttpHeaderRegexp = "(.*)\\: (.*)\\n*"; /** * Default max HTTP Response size. diff --git a/ext/include/opentelemetry/ext/http/server/http_server.h b/ext/include/opentelemetry/ext/http/server/http_server.h index 941713f2b9..2129c2b643 100644 --- a/ext/include/opentelemetry/ext/http/server/http_server.h +++ b/ext/include/opentelemetry/ext/http/server/http_server.h @@ -3,7 +3,9 @@ #pragma once +#include #include +#include #include #include #include @@ -433,7 +435,21 @@ class HttpServer : private SocketTools::Reactor::SocketCallback auto const contentLength = conn.request.headers.find("Content-Length"); if (contentLength != conn.request.headers.end()) { - conn.contentLength = atoi(contentLength->second.c_str()); + char *lengthEnd = nullptr; + errno = 0; + auto const declared = std::strtol(contentLength->second.c_str(), &lengthEnd, 10); + // Reject a non-numeric, negative, or out-of-range Content-Length; tolerate trailing + // characters (e.g. optional whitespace) after a valid leading count. + if (errno != 0 || lengthEnd == contentLength->second.c_str() || declared < 0) + { + LOG_WARN("HttpServer: [%s] invalid Content-Length - %s", conn.request.client.c_str(), + contentLength->second.c_str()); + conn.response.code = 400; // Bad Request + conn.keepalive = false; + conn.state = Connection::Processing; + continue; + } + conn.contentLength = static_cast(declared); } else { diff --git a/ext/include/opentelemetry/ext/http/server/socket_tools.h b/ext/include/opentelemetry/ext/http/server/socket_tools.h index 5c8bd92a4e..b2e3217818 100644 --- a/ext/include/opentelemetry/ext/http/server/socket_tools.h +++ b/ext/include/opentelemetry/ext/http/server/socket_tools.h @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -196,7 +198,18 @@ struct SocketAddr char const *colon = strchr(addr, ':'); if (colon) { - inet4.sin_port = htons(atoi(colon + 1)); + char *portEnd = nullptr; + errno = 0; + auto const parsed = std::strtol(colon + 1, &portEnd, 10); + // Accept only a converted, in-range port value; fall back to 0 otherwise. + if (errno == 0 && portEnd != colon + 1 && parsed >= 0 && parsed <= 65535) + { + inet4.sin_port = htons(static_cast(parsed)); + } + else + { + inet4.sin_port = 0; + } char buf[16]; memcpy(buf, addr, (std::min)(15, colon - addr)); buf[15] = '\0'; diff --git a/ext/test/http/curl_http_test.cc b/ext/test/http/curl_http_test.cc index 0c1c6c68a1..0807a405dc 100644 --- a/ext/test/http/curl_http_test.cc +++ b/ext/test/http/curl_http_test.cc @@ -72,6 +72,7 @@ class CustomEventHandler : public http_client::EventHandler class GetEventHandler : public CustomEventHandler { +public: void OnResponse(http_client::Response &response) noexcept override { ASSERT_EQ(200, response.GetStatusCode()); @@ -83,6 +84,7 @@ class GetEventHandler : public CustomEventHandler class PostEventHandler : public CustomEventHandler { +public: void OnResponse(http_client::Response &response) noexcept override { ASSERT_EQ(200, response.GetStatusCode()); @@ -120,6 +122,7 @@ class FinishInCallbackHandler : public CustomEventHandler class RetryEventHandler : public CustomEventHandler { +public: void OnResponse(http_client::Response &response) noexcept override { ASSERT_EQ(429, response.GetStatusCode()); @@ -145,6 +148,7 @@ class BasicCurlHttpTests : public ::testing::Test, public HTTP_SERVER_NS::HttpRe public: BasicCurlHttpTests() : is_setup_(false), is_running_(false) {} +protected: void SetUp() override { if (is_setup_.exchange(true)) @@ -173,6 +177,7 @@ class BasicCurlHttpTests : public ::testing::Test, public HTTP_SERVER_NS::HttpRe is_running_ = false; } +public: int onHttpRequest(HTTP_SERVER_NS::HttpRequest const &request, HTTP_SERVER_NS::HttpResponse &response) override { diff --git a/ext/test/http/url_parser_test.cc b/ext/test/http/url_parser_test.cc index 7c23e7dd2c..b4cd095ce0 100644 --- a/ext/test/http/url_parser_test.cc +++ b/ext/test/http/url_parser_test.cc @@ -12,6 +12,9 @@ namespace http_common = opentelemetry::ext::http::common; +namespace +{ + struct ParsedUrl { std::string scheme; @@ -138,3 +141,5 @@ TEST_P(UrlDecoderTests, BasicTests) EXPECT_EQ(actual, expected); } + +} // namespace diff --git a/ext/test/w3c_tracecontext_http_test_server/main.cc b/ext/test/w3c_tracecontext_http_test_server/main.cc index 67beaea45e..9e3f009dd0 100644 --- a/ext/test/w3c_tracecontext_http_test_server/main.cc +++ b/ext/test/w3c_tracecontext_http_test_server/main.cc @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -186,7 +186,7 @@ int main(int argc, char *argv[]) // The port the validation service listens to can be specified via the command line. if (argc > 1) { - port = static_cast(atoi(argv[1])); + port = static_cast(std::strtol(argv[1], nullptr, 10)); } auto root_span = get_tracer()->StartSpan(__func__); diff --git a/functional/otlp/func_grpc_main.cc b/functional/otlp/func_grpc_main.cc index 808bb227bc..f53cde3f1b 100644 --- a/functional/otlp/func_grpc_main.cc +++ b/functional/otlp/func_grpc_main.cc @@ -51,7 +51,8 @@ static bool opt_list = false; static bool opt_debug = false; static bool opt_secure = false; // HTTPS by default -static std::string opt_endpoint = "https://127.0.0.1:4317"; +constexpr char kDefaultOptEndpoint[] = "https://127.0.0.1:4317"; +static std::string opt_endpoint; static std::string opt_cert_dir; static std::string opt_test_name; static TestMode opt_mode = TestMode::kNone; @@ -318,7 +319,7 @@ typedef int (*test_func_t)(); struct test_case { - std::string m_name; + nostd::string_view m_name; test_func_t m_func; }; @@ -392,6 +393,8 @@ int main(int argc, char *argv[]) argc--; argv++; + opt_endpoint = kDefaultOptEndpoint; + int rc = parse_args(argc, argv); if (rc != 0) diff --git a/functional/otlp/func_http_main.cc b/functional/otlp/func_http_main.cc index 525580b18a..ae7399f46d 100644 --- a/functional/otlp/func_http_main.cc +++ b/functional/otlp/func_http_main.cc @@ -50,7 +50,8 @@ static bool opt_list = false; static bool opt_debug = false; static bool opt_secure = false; // HTTPS by default -static std::string opt_endpoint = "https://localhost:4318/v1/traces"; +constexpr char kDefaultOptEndpoint[] = "https://localhost:4318/v1/traces"; +static std::string opt_endpoint; static std::string opt_cert_dir; static std::string opt_test_name; static test_mode opt_mode = MODE_NONE; @@ -335,7 +336,7 @@ typedef int (*test_func_t)(); struct test_case { - std::string m_name; + nostd::string_view m_name; test_func_t m_func; }; @@ -462,6 +463,8 @@ int main(int argc, char *argv[]) argc--; argv++; + opt_endpoint = kDefaultOptEndpoint; + int rc = parse_args(argc, argv); if (rc != 0) diff --git a/install/cmake/CMakeLists.txt b/install/cmake/CMakeLists.txt index 44c9f7bb82..427f28f998 100644 --- a/install/cmake/CMakeLists.txt +++ b/install/cmake/CMakeLists.txt @@ -223,7 +223,6 @@ if(grpc IN_LIST _THIRDPARTY_PACKAGE_LIST) GIT_TAG ${grpc_GIT_TAG} GIT_SHALLOW ON GIT_SUBMODULES "third_party/re2" "third_party/cares/cares" - "third_party/boringssl-with-bazel" PREFIX ${CMAKE_BINARY_DIR}/external/grpc INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CMAKE_ARGS "${CMAKE_OPTIONS}" @@ -238,6 +237,7 @@ if(grpc IN_LIST _THIRDPARTY_PACKAGE_LIST) "-DgRPC_BUILD_GRPC_RUBY_PLUGIN=OFF" "-DgRPC_BUILD_GRPCPP_OTEL_PLUGIN=OFF" "-DRE2_BUILD_TESTING=OFF" + "-DgRPC_SSL_PROVIDER=package" "-DgRPC_ZLIB_PROVIDER=package" "-DgRPC_PROTOBUF_PROVIDER=package" "-DgRPC_PROTOBUF_PACKAGE_TYPE=CONFIG" diff --git a/install/cmake/third_party_latest b/install/cmake/third_party_latest index febf7bae79..e3d30c74b6 100644 --- a/install/cmake/third_party_latest +++ b/install/cmake/third_party_latest @@ -7,8 +7,8 @@ abseil=20250512.1 zlib=v1.3.2 curl=curl-8_21_0 -protobuf=v33.6 -grpc=v1.81.1 +protobuf=v35.1 +grpc=v1.82.1 benchmark=v1.9.4 googletest=v1.17.0 ms-gsl=v4.2.1 diff --git a/install/test/cmake/api_only_test/CMakeLists.txt b/install/test/cmake/api_only_test/CMakeLists.txt new file mode 100644 index 0000000000..2ec3e04cc1 --- /dev/null +++ b/install/test/cmake/api_only_test/CMakeLists.txt @@ -0,0 +1,117 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# This test covers the WITH_API_ONLY build option of opentelemetry-cpp. It +# ensures that no third-party libraries are included in the build and that only +# the opentelemetry-cpp::api target is created. + +cmake_minimum_required(VERSION 3.16) + +project(opentelemetry-cpp-api-only-test LANGUAGES CXX) + +if(NOT OPENTELEMETRY_CPP_SRC_DIR) + message( + FATAL_ERROR + "OPENTELEMETRY_CPP_SRC_DIR must be defined when running cmake on this test project" + ) +endif() + +set(EXPECTED_OPTIONS_IN_CACHE + WITH_EXAMPLES + WITH_EXAMPLES_HTTP + BUILD_TESTING + WITH_FUNC_TESTS + WITH_BENCHMARK + WITH_OTLP_GRPC + WITH_OTLP_HTTP + WITH_OTLP_FILE + WITH_OTLP_HTTP_COMPRESSION + WITH_PROMETHEUS + WITH_ZIPKIN + WITH_ELASTICSEARCH + WITH_OPENTRACING + WITH_CONFIGURATION) + +function(check_options_in_cache) + foreach(component IN LISTS EXPECTED_OPTIONS_IN_CACHE) + if(NOT ${component}) + message( + FATAL_ERROR + "Expected component ${component} to be ON in the cache, but it is OFF." + ) + endif() + endforeach() +endfunction() + +# Verify that all options which may bring in a third-party dependency are ON in +# the cache. +check_options_in_cache() + +# Turn on the WITH_API_ONLY build option and disable GSL. This must override any +# other options and not import third party targets. +set(WITH_API_ONLY ON) +set(WITH_GSL OFF) + +# Add the opentelemetry-cpp source directory as a subdirectory to mimic +# vendoring the library. +add_subdirectory(${OPENTELEMETRY_CPP_SRC_DIR} + ${CMAKE_BINARY_DIR}/opentelemetry-cpp) + +# Verify that all options which may bring in a third-party dependency are still +# ON in the cache. +check_options_in_cache() + +set(UNEXPECTED_TARGETS + opentelemetry-cpp::sdk + Microsoft.GSL::GSL + Threads::Threads + ZLIB::ZLIB + CURL::libcurl + protobuf::libprotobuf + gRPC::grpc++ + nlohmann_json::nlohmann_json + OpenTracing::opentracing + prometheus-cpp::core + ryml::ryml + GTest::gtest + benchmark::benchmark) + +foreach(target IN LISTS UNEXPECTED_TARGETS) + if(TARGET ${target}) + message( + FATAL_ERROR + "Unexpected target ${target} was created in an WITH_API_ONLY build.") + endif() +endforeach() + +if(NOT TARGET opentelemetry-cpp::api) + message( + FATAL_ERROR + "opentelemetry-cpp::api target was not created in an WITH_API_ONLY build." + ) +endif() + +include(FetchContent) + +FetchContent_Declare( + googletest SOURCE_DIR ${OPENTELEMETRY_CPP_SRC_DIR}/third_party/googletest) + +FetchContent_MakeAvailable(googletest) + +message(STATUS "opentelemetry-cpp_SRC_DIR: ${OPENTELEMETRY_CPP_SRC_DIR}") + +set(BUILD_TESTING ON) + +add_executable(api_only_test + ${OPENTELEMETRY_CPP_SRC_DIR}/install/test/src/test_api.cc) + +target_link_libraries(api_only_test PRIVATE opentelemetry-cpp::api GTest::gtest + GTest::gtest_main) + +include(CTest) +include(GoogleTest) + +gtest_add_tests( + TARGET api_only_test + TEST_PREFIX api_only. + TEST_LIST api_only_test) diff --git a/install/test/src/test_api.cc b/install/test/src/test_api.cc index 6fc88663f5..ed2036e819 100644 --- a/install/test/src/test_api.cc +++ b/install/test/src/test_api.cc @@ -3,6 +3,9 @@ #include +#include +#include + #include #include @@ -20,6 +23,35 @@ #include #include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + TEST(ApiInstallTest, VersionCheck) { EXPECT_GE(OPENTELEMETRY_VERSION_MAJOR, 0); @@ -88,4 +120,98 @@ TEST(ApiInstallTest, MetricsApiCheck) auto async_updown_counter = meter->CreateInt64ObservableUpDownCounter("test-async-updown-counter"); ASSERT_TRUE(async_updown_counter != nullptr); -} \ No newline at end of file +} + +TEST(ApiInstallTest, BaggageApiCheck) +{ + auto baggage = opentelemetry::baggage::Baggage::GetDefault(); + ASSERT_TRUE(baggage != nullptr); + + auto baggage_with_entry = baggage->Set("key", "value"); + ASSERT_TRUE(baggage_with_entry != nullptr); + + std::string retrieved_value; + EXPECT_TRUE(baggage_with_entry->GetValue("key", retrieved_value)); + EXPECT_EQ(retrieved_value, "value"); + + auto context = opentelemetry::context::RuntimeContext::GetCurrent(); + auto ctx_with_baggage = opentelemetry::baggage::SetBaggage(context, baggage_with_entry); + auto retrieved_baggage = opentelemetry::baggage::GetBaggage(ctx_with_baggage); + ASSERT_TRUE(retrieved_baggage != nullptr); + + retrieved_value.clear(); + EXPECT_TRUE(retrieved_baggage->GetValue("key", retrieved_value)); + EXPECT_EQ(retrieved_value, "value"); +} + +TEST(ApiInstallTest, ContextPropagationApiCheck) +{ + struct MapCarrier : public opentelemetry::context::propagation::TextMapCarrier + { + std::unordered_map headers; + + opentelemetry::nostd::string_view Get( + opentelemetry::nostd::string_view key) const noexcept override + { + auto it = headers.find(std::string(key)); + if (it != headers.end()) + { + return it->second; + } + return ""; + } + void Set(opentelemetry::nostd::string_view key, + opentelemetry::nostd::string_view value) noexcept override + { + headers[std::string(key)] = std::string(value); + } + }; + + auto propagator = + opentelemetry::context::propagation::GlobalTextMapPropagator::GetGlobalPropagator(); + ASSERT_TRUE(propagator != nullptr); + + MapCarrier carrier; + auto context = + opentelemetry::context::RuntimeContext::GetCurrent().SetValue("test-key", "test-value"); + propagator->Inject(carrier, context); + auto extracted = propagator->Extract(carrier, context); + EXPECT_EQ(extracted, context); + EXPECT_TRUE(extracted.HasKey("test-key")); +} + +TEST(SemconvInstallTest, SchemaUrl) +{ + EXPECT_NE(opentelemetry::semconv::kSchemaUrl, nullptr); + EXPECT_STRNE(opentelemetry::semconv::kSchemaUrl, ""); +} + +TEST(SemconvInstallTest, HttpMetrics) +{ + auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); + ASSERT_TRUE(provider != nullptr); + + auto meter = provider->GetMeter("test-semconv-meter"); + ASSERT_TRUE(meter != nullptr); + + auto http_client_duration = + opentelemetry::semconv::http::CreateSyncDoubleMetricHttpClientRequestDuration(meter.get()); + ASSERT_TRUE(http_client_duration != nullptr); + + auto http_server_duration = + opentelemetry::semconv::http::CreateSyncInt64MetricHttpServerRequestDuration(meter.get()); + ASSERT_TRUE(http_server_duration != nullptr); +} + +TEST(SemconvInstallTest, DbMetrics) +{ + auto provider = opentelemetry::metrics::Provider::GetMeterProvider(); + ASSERT_TRUE(provider != nullptr); + + auto meter = provider->GetMeter("test-semconv-meter"); + ASSERT_TRUE(meter != nullptr); + + auto db_client_duration = + opentelemetry::semconv::db::CreateSyncDoubleMetricDbClientOperationDuration(meter.get()); + ASSERT_TRUE(db_client_duration != nullptr); +} diff --git a/opentracing-shim/src/tracer_shim.cc b/opentracing-shim/src/tracer_shim.cc index bfc6055971..6cd5342a99 100644 --- a/opentracing-shim/src/tracer_shim.cc +++ b/opentracing-shim/src/tracer_shim.cc @@ -183,7 +183,7 @@ opentracing::expected> TracerShim::ext CarrierReaderShim carrier{reader}; auto current_context = opentelemetry::context::RuntimeContext::GetCurrent(); auto context = propagator->Extract(carrier, current_context); - auto span_context = opentelemetry::trace::GetSpan(context)->GetContext(); + auto span_context = opentelemetry::trace::GetSpanContext(context); auto baggage = opentelemetry::baggage::GetBaggage(context); // The operation MUST return a `SpanContext` Shim instance with the extracted values if any of diff --git a/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index 3010c6b9bd..8b83b363b0 100644 --- a/sdk/include/opentelemetry/sdk/common/attribute_utils.h +++ b/sdk/include/opentelemetry/sdk/common/attribute_utils.h @@ -3,8 +3,11 @@ #pragma once +#include +#include #include #include +#include #include #include #include @@ -18,6 +21,10 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_HAVE_EXCEPTIONS +# include +#endif + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -68,11 +75,94 @@ enum OwnedAttributeType : std::uint8_t kTypeSpanByte }; +/** + * Byte length of the longest prefix of `data[0, size)` that fits within + * `max_bytes` without splitting a well-formed UTF-8 multi-byte sequence. A lead + * byte's declared length is only honored when its continuation bytes are + * present and in range (0x80-0xBF); otherwise the lead is treated as a one-byte + * unit, so malformed input degrades to plain byte truncation. Callers that need + * the result to stay valid UTF-8 (string attributes, whose protobuf/JSON wire + * form requires it) truncate at this boundary instead of cutting through a + * multi-byte sequence. + */ +// UTF-8 lead-byte ranges used below: +// 0x00-0x7F 0xxxxxxx ASCII, 1 byte +// 0x80-0xBF 10xxxxxx continuation byte (never a valid lead) +// 0xC0-0xDF 110xxxxx lead of a 2-byte sequence +// 0xE0-0xEF 1110xxxx lead of a 3-byte sequence +// 0xF0-0xF7 11110xxx lead of a 4-byte sequence +// 0xF8-0xFF not a valid lead byte +inline std::size_t Utf8SafePrefixLength(const char *data, + std::size_t size, + std::size_t max_bytes) noexcept +{ + // When the whole value fits within the budget (including the common + // unbounded case where max_bytes is SIZE_MAX), no truncation is possible, + // so skip the per-byte scan. + if (max_bytes >= size) + { + return size; + } + std::size_t i = 0; + while (i < size) + { + const auto lead = static_cast(data[i]); + std::size_t seq = (lead < 0x80) ? 1 + : (lead < 0xC0) ? 1 + : (lead < 0xE0) ? 2 + : (lead < 0xF0) ? 3 + : (lead < 0xF8) ? 4 + : 1; + if (seq > 1) + { + if (i + seq > size) + { + seq = 1; + } + else + { + for (std::size_t k = 1; k < seq; ++k) + { + const auto continuation = static_cast(data[i + k]); + if (continuation < 0x80 || continuation > 0xBF) + { + seq = 1; + break; + } + } + } + } + if (i + seq > max_bytes) + { + break; + } + i += seq; + } + return i; +} + /** * Creates an owned copy (OwnedAttributeValue) of a non-owning AttributeValue. + * + * The default-constructed converter does not truncate. To apply a byte-length + * cap during conversion, construct with the `(std::size_t max_length)` + * overload. Only the string, string-array, and bytes alternatives are capped; + * other alternatives are unaffected. String alternatives (std::string, + * string_view, const char*, and arrays of them) are truncated at a UTF-8 + * code-point boundary via Utf8SafePrefixLength so the kept value stays valid + * UTF-8 when the input was, matching the OTel convention that std::string holds + * UTF-8 text. The bytes alternative (span) carries raw binary + * data and is cut at the exact byte boundary with no encoding semantics. */ struct AttributeConverter { + AttributeConverter() = default; + + /// Constructs a converter that truncates string and bytes alternatives to + /// at most `max_length` bytes during conversion. Other alternatives are + /// unaffected. Used by recordables enforcing attribute_value_length_limit. + explicit AttributeConverter(std::size_t max_length) noexcept : max_length_(max_length) {} + OwnedAttributeValue operator()(bool v) { return OwnedAttributeValue(v); } OwnedAttributeValue operator()(int32_t v) { return OwnedAttributeValue(v); } OwnedAttributeValue operator()(uint32_t v) { return OwnedAttributeValue(v); } @@ -81,11 +171,31 @@ struct AttributeConverter OwnedAttributeValue operator()(double v) { return OwnedAttributeValue(v); } OwnedAttributeValue operator()(nostd::string_view v) { - return OwnedAttributeValue(std::string(v)); + const std::size_t kept = + v.size() > max_length_ ? Utf8SafePrefixLength(v.data(), v.size(), max_length_) : v.size(); + return OwnedAttributeValue(std::string(v.data(), kept)); + } + OwnedAttributeValue operator()(std::string v) + { + if (v.size() > max_length_) + { + v.resize(Utf8SafePrefixLength(v.data(), v.size(), max_length_)); + } + return OwnedAttributeValue(std::move(v)); + } + OwnedAttributeValue operator()(const char *v) + { + // Delegate to the string_view overload so an oversized C string is not + // copied in full before being truncated, and so it shares the UTF-8-safe + // boundary handling. + return (*this)(nostd::string_view(v)); + } + OwnedAttributeValue operator()(nostd::span v) + { + // Raw bytes carry no encoding, so cut at the exact byte boundary. + const std::size_t kept = (std::min)(v.size(), max_length_); + return OwnedAttributeValue(std::vector(v.data(), v.data() + kept)); } - OwnedAttributeValue operator()(std::string v) { return OwnedAttributeValue(std::move(v)); } - OwnedAttributeValue operator()(const char *v) { return OwnedAttributeValue(std::string(v)); } - OwnedAttributeValue operator()(nostd::span v) { return convertSpan(v); } OwnedAttributeValue operator()(nostd::span v) { return convertSpan(v); } OwnedAttributeValue operator()(nostd::span v) { return convertSpan(v); } OwnedAttributeValue operator()(nostd::span v) { return convertSpan(v); } @@ -94,7 +204,16 @@ struct AttributeConverter OwnedAttributeValue operator()(nostd::span v) { return convertSpan(v); } OwnedAttributeValue operator()(nostd::span v) { - return convertSpan(v); + std::vector result; + result.reserve(v.size()); + for (const auto &sv : v) + { + const std::size_t kept = sv.size() > max_length_ + ? Utf8SafePrefixLength(sv.data(), sv.size(), max_length_) + : sv.size(); + result.emplace_back(sv.data(), kept); + } + return OwnedAttributeValue(std::move(result)); } template @@ -102,6 +221,9 @@ struct AttributeConverter { return OwnedAttributeValue(std::vector(vals.begin(), vals.end())); } + +private: + std::size_t max_length_ = (std::numeric_limits::max)(); }; /** @@ -158,6 +280,62 @@ struct AttributeEqualToVisitor } }; +/** + * Insert or assign a key-value pair into a map using map.insert_or_assign if available, or + * map.emplace otherwise. + * @param map The map to insert or assign the key-value pair into. + * @param key The key to insert or assign. + * @param value The value to insert or assign. + * @return A pair of an iterator to the element and a bool (true if the insertion took place). + */ +template +inline std::pair +AttributeInsertOrAssign(Map &map, opentelemetry::nostd::string_view key, Value &&value) noexcept +{ +#if __cplusplus >= 201703L + // Use insert_or_assign if C++17 is available + return map.insert_or_assign(std::string(key), std::forward(value)); +#else + auto result = map.emplace(std::string(key), typename Map::mapped_type{}); + result.first->second = std::forward(value); + return result; +#endif +} + +/** + * VisitVariant + * + * Invokes nostd::visit(visitor, value) with exception-safe handling. + * Returns pair: second=true on success, false if an exception was caught + * On exception the first element is default-constructed. + */ +template +inline auto VisitVariant(Visitor &&visitor, const Variant &value) noexcept + -> std::pair(visitor), value)), bool> +{ +#if OPENTELEMETRY_HAVE_EXCEPTIONS + using ReturnType = decltype(opentelemetry::nostd::visit(std::forward(visitor), value)); + try + { +#endif + return {opentelemetry::nostd::visit(std::forward(visitor), value), true}; +#if OPENTELEMETRY_HAVE_EXCEPTIONS + } +# if defined(OPENTELEMETRY_HAVE_STD_VARIANT) + catch (const std::bad_variant_access &) +# else + catch (const opentelemetry::nostd::bad_variant_access &) +# endif + { + return {ReturnType{}, false}; + } + catch (const std::bad_alloc &) + { + return {ReturnType{}, false}; + } +#endif +} + /** * Class for storing attributes. */ @@ -210,10 +388,18 @@ class AttributeMap : public std::unordered_map } // Convert non-owning key-value to owning std::string(key) and OwnedAttributeValue(value) - void SetAttribute(nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept + bool SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value, + std::size_t max_length = (std::numeric_limits::max)()) noexcept { - (*this)[std::string(key)] = nostd::visit(AttributeConverter(), value); + std::pair result = + VisitVariant(AttributeConverter(max_length), value); + if (result.second) + { + AttributeInsertOrAssign(*this, key, std::move(result.first)); + return true; + } + return false; } // Compare the attributes of this map with another KeyValueIterable @@ -285,9 +471,15 @@ class OrderedAttributeMap : public std::map // Convert non-owning key-value to owning std::string(key) and OwnedAttributeValue(value) void SetAttribute(nostd::string_view key, - const opentelemetry::common::AttributeValue &value) noexcept + const opentelemetry::common::AttributeValue &value, + std::size_t max_length = (std::numeric_limits::max)()) noexcept { - (*this)[std::string(key)] = nostd::visit(AttributeConverter(), value); + std::pair result = + VisitVariant(AttributeConverter(max_length), value); + if (result.second) + { + AttributeInsertOrAssign(*this, key, std::move(result.first)); + } } }; diff --git a/sdk/include/opentelemetry/sdk/configuration/attribute_limits_configuration.h b/sdk/include/opentelemetry/sdk/configuration/attribute_limits_configuration.h index 69f311575c..105856ede0 100644 --- a/sdk/include/opentelemetry/sdk/configuration/attribute_limits_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/attribute_limits_configuration.h @@ -18,8 +18,12 @@ namespace configuration class AttributeLimitsConfiguration { public: - std::size_t attribute_value_length_limit; - std::size_t attribute_count_limit; + // TODO: spec default is no limit, using 4096 to preserve original behavior + static constexpr std::size_t kDefaultAttributeValueLengthLimit = 4096; + static constexpr std::size_t kDefaultAttributeCountLimit = 128; + + std::size_t attribute_value_length_limit{kDefaultAttributeValueLengthLimit}; + std::size_t attribute_count_limit{kDefaultAttributeCountLimit}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h b/sdk/include/opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h index 86d87f8393..e684eadae7 100644 --- a/sdk/include/opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "opentelemetry/sdk/configuration/aggregation_configuration.h" #include "opentelemetry/sdk/configuration/aggregation_configuration_visitor.h" #include "opentelemetry/version.h" @@ -18,14 +20,21 @@ namespace configuration class Base2ExponentialBucketHistogramAggregationConfiguration : public AggregationConfiguration { public: + // TODO: max_scale range is [-10, 20]. Negative values require GetSignedInteger in the parser and + // changing the type of max_scale to an int32_t to match the + // Base2ExponentialHistogramAggregationConfig. + static constexpr std::size_t kDefaultMaxScale = 20; // schema: minimum -10, maximum 20 + static constexpr std::size_t kDefaultMaxSize = 160; + static constexpr bool kDefaultRecordMinMax = true; + void Accept(AggregationConfigurationVisitor *visitor) const override { visitor->VisitBase2ExponentialBucketHistogram(this); } - std::size_t max_scale{0}; - std::size_t max_size{0}; - bool record_min_max{false}; + std::size_t max_scale{kDefaultMaxScale}; + std::size_t max_size{kDefaultMaxSize}; + bool record_min_max{kDefaultRecordMinMax}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h b/sdk/include/opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h index 17570f0552..a011a907a8 100644 --- a/sdk/include/opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" @@ -21,15 +22,21 @@ namespace configuration class BatchLogRecordProcessorConfiguration : public LogRecordProcessorConfiguration { public: + // TODO: spec default is 1000ms for schedule_delay, using 5000ms to preserve original behavior + static constexpr std::size_t kDefaultScheduleDelayMs = 5000; + static constexpr std::size_t kDefaultExportTimeoutMs = 30000; + static constexpr std::size_t kDefaultMaxQueueSize = 2048; + static constexpr std::size_t kDefaultMaxExportBatchSize = 512; + void Accept(LogRecordProcessorConfigurationVisitor *visitor) const override { visitor->VisitBatch(this); } - std::size_t schedule_delay{0}; - std::size_t export_timeout{0}; - std::size_t max_queue_size{0}; - std::size_t max_export_batch_size{0}; + std::size_t schedule_delay{kDefaultScheduleDelayMs}; + std::size_t export_timeout{kDefaultExportTimeoutMs}; + std::size_t max_queue_size{kDefaultMaxQueueSize}; + std::size_t max_export_batch_size{kDefaultMaxExportBatchSize}; std::unique_ptr exporter; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/batch_span_processor_configuration.h b/sdk/include/opentelemetry/sdk/configuration/batch_span_processor_configuration.h index 1230f616a2..40446aac20 100644 --- a/sdk/include/opentelemetry/sdk/configuration/batch_span_processor_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/batch_span_processor_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "opentelemetry/sdk/configuration/span_exporter_configuration.h" @@ -21,15 +22,20 @@ namespace configuration class BatchSpanProcessorConfiguration : public SpanProcessorConfiguration { public: + static constexpr std::size_t kDefaultScheduleDelayMs = 5000; + static constexpr std::size_t kDefaultExportTimeoutMs = 30000; + static constexpr std::size_t kDefaultMaxQueueSize = 2048; + static constexpr std::size_t kDefaultMaxExportBatchSize = 512; + void Accept(SpanProcessorConfigurationVisitor *visitor) const override { visitor->VisitBatch(this); } - std::size_t schedule_delay{0}; - std::size_t export_timeout{0}; - std::size_t max_queue_size{0}; - std::size_t max_export_batch_size{0}; + std::size_t schedule_delay{kDefaultScheduleDelayMs}; + std::size_t export_timeout{kDefaultExportTimeoutMs}; + std::size_t max_queue_size{kDefaultMaxQueueSize}; + std::size_t max_export_batch_size{kDefaultMaxExportBatchSize}; std::unique_ptr exporter; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/boolean_attribute_value_configuration.h b/sdk/include/opentelemetry/sdk/configuration/boolean_attribute_value_configuration.h index 2e2700f6eb..3fb81a3465 100644 --- a/sdk/include/opentelemetry/sdk/configuration/boolean_attribute_value_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/boolean_attribute_value_configuration.h @@ -23,7 +23,7 @@ class BooleanAttributeValueConfiguration : public AttributeValueConfiguration visitor->VisitBoolean(this); } - bool value; + bool value{false}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/cardinality_limits_configuration.h b/sdk/include/opentelemetry/sdk/configuration/cardinality_limits_configuration.h index 808663cf4c..a65b90abe7 100644 --- a/sdk/include/opentelemetry/sdk/configuration/cardinality_limits_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/cardinality_limits_configuration.h @@ -18,15 +18,19 @@ namespace configuration class CardinalityLimitsConfiguration { public: - std::size_t default_limit; - // For all limits, 0 means unset, use default_limit - std::size_t counter; - std::size_t gauge; - std::size_t histogram; - std::size_t observable_counter; - std::size_t observable_gauge; - std::size_t observable_up_down_counter; - std::size_t up_down_counter; + static constexpr std::size_t kDefaultLimit = 2000; + // 0 is a sentinel: inherit from default_limit (schema exclusiveMinimum: 0) + static constexpr std::size_t kInheritDefault = 0; + + std::size_t default_limit{kDefaultLimit}; + // For all limits, kInheritDefault means unset, use default_limit + std::size_t counter{kInheritDefault}; + std::size_t gauge{kInheritDefault}; + std::size_t histogram{kInheritDefault}; + std::size_t observable_counter{kInheritDefault}; + std::size_t observable_gauge{kInheritDefault}; + std::size_t observable_up_down_counter{kInheritDefault}; + std::size_t up_down_counter{kInheritDefault}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/composable_probability_sampler_configuration.h b/sdk/include/opentelemetry/sdk/configuration/composable_probability_sampler_configuration.h index b5d63a40a3..a270a4c17f 100644 --- a/sdk/include/opentelemetry/sdk/configuration/composable_probability_sampler_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/composable_probability_sampler_configuration.h @@ -15,8 +15,10 @@ namespace configuration class ComposableProbabilitySamplerConfiguration : public ComposableSamplerConfiguration { public: + static constexpr double kDefaultRatio = 1.0; + ComposableProbabilitySamplerConfiguration() = default; - double ratio{1.0}; + double ratio{kDefaultRatio}; void Accept(SamplerConfigurationVisitor *visitor) const override; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration.h b/sdk/include/opentelemetry/sdk/configuration/configuration.h index 05dfffcb80..a7fbac4845 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/configuration.h @@ -58,7 +58,8 @@ namespace configuration class Configuration { public: - Configuration(std::unique_ptr doc) : doc_(std::move(doc)) {} + Configuration() = default; + explicit Configuration(std::unique_ptr doc) : doc_(std::move(doc)) {} Configuration(Configuration &&) = delete; Configuration(const Configuration &) = delete; Configuration &operator=(Configuration &&) = delete; diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h index 0566425946..84f5811874 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h +++ b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h @@ -474,8 +474,8 @@ class ConfigurationParser private: std::string version_; - int version_major_; - int version_minor_; + int version_major_{}; + int version_minor_{}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/console_push_metric_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/console_push_metric_exporter_configuration.h index 8755b31e9a..bff56b3375 100644 --- a/sdk/include/opentelemetry/sdk/configuration/console_push_metric_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/console_push_metric_exporter_configuration.h @@ -21,14 +21,18 @@ namespace configuration class ConsolePushMetricExporterConfiguration : public PushMetricExporterConfiguration { public: + static constexpr TemporalityPreference kDefaultTemporalityPreference = + TemporalityPreference::cumulative; + static constexpr DefaultHistogramAggregation kDefaultHistogramAggregation = + DefaultHistogramAggregation::explicit_bucket_histogram; + void Accept(PushMetricExporterConfigurationVisitor *visitor) const override { visitor->VisitConsole(this); } - TemporalityPreference temporality_preference{TemporalityPreference::cumulative}; - DefaultHistogramAggregation default_histogram_aggregation{ - DefaultHistogramAggregation::explicit_bucket_histogram}; + TemporalityPreference temporality_preference{kDefaultTemporalityPreference}; + DefaultHistogramAggregation default_histogram_aggregation{kDefaultHistogramAggregation}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/document_node.h b/sdk/include/opentelemetry/sdk/configuration/document_node.h index 646a15de17..7a725a3116 100644 --- a/sdk/include/opentelemetry/sdk/configuration/document_node.h +++ b/sdk/include/opentelemetry/sdk/configuration/document_node.h @@ -178,9 +178,9 @@ class PropertiesNodeConstIterator class DocumentNodeLocation { public: - size_t offset; - size_t line; - size_t col; + std::size_t offset{}; + std::size_t line{}; + std::size_t col{}; std::string filename; std::string ToString() const; diff --git a/sdk/include/opentelemetry/sdk/configuration/double_attribute_value_configuration.h b/sdk/include/opentelemetry/sdk/configuration/double_attribute_value_configuration.h index 2408d81bc0..b300e8bc29 100644 --- a/sdk/include/opentelemetry/sdk/configuration/double_attribute_value_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/double_attribute_value_configuration.h @@ -23,7 +23,7 @@ class DoubleAttributeValueConfiguration : public AttributeValueConfiguration visitor->VisitDouble(this); } - double value; + double value{0.0}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h b/sdk/include/opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h index 122e84d034..53101a4032 100644 --- a/sdk/include/opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h @@ -20,13 +20,16 @@ namespace configuration class ExplicitBucketHistogramAggregationConfiguration : public AggregationConfiguration { public: + // empty boundaries means use the SDK spec default ([0, 5, 10, ...]) + static constexpr bool kDefaultRecordMinMax = true; + void Accept(AggregationConfigurationVisitor *visitor) const override { visitor->VisitExplicitBucketHistogram(this); } std::vector boundaries; - bool record_min_max{false}; + bool record_min_max{kDefaultRecordMinMax}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/grpc_tls_configuration.h b/sdk/include/opentelemetry/sdk/configuration/grpc_tls_configuration.h index 9a10b8e6c8..b1120eda03 100644 --- a/sdk/include/opentelemetry/sdk/configuration/grpc_tls_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/grpc_tls_configuration.h @@ -19,10 +19,12 @@ namespace configuration class GrpcTlsConfiguration { public: + static constexpr bool kDefaultInsecure = false; + std::string ca_file; std::string key_file; std::string cert_file; - bool insecure{false}; + bool insecure{kDefaultInsecure}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/integer_attribute_value_configuration.h b/sdk/include/opentelemetry/sdk/configuration/integer_attribute_value_configuration.h index 60878d728b..28bc09c0be 100644 --- a/sdk/include/opentelemetry/sdk/configuration/integer_attribute_value_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/integer_attribute_value_configuration.h @@ -25,7 +25,7 @@ class IntegerAttributeValueConfiguration : public AttributeValueConfiguration visitor->VisitInteger(this); } - int64_t value; + std::int64_t value{0}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/jaeger_remote_sampler_configuration.h b/sdk/include/opentelemetry/sdk/configuration/jaeger_remote_sampler_configuration.h index 19af801b61..cc882eb62c 100644 --- a/sdk/include/opentelemetry/sdk/configuration/jaeger_remote_sampler_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/jaeger_remote_sampler_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -21,13 +22,15 @@ namespace configuration class JaegerRemoteSamplerConfiguration : public SamplerConfiguration { public: + static constexpr std::size_t kDefaultIntervalMs = 60000; + void Accept(SamplerConfigurationVisitor *visitor) const override { visitor->VisitJaegerRemote(this); } std::string endpoint; - std::size_t interval{0}; + std::size_t interval{kDefaultIntervalMs}; std::unique_ptr initial_sampler; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/log_record_limits_configuration.h b/sdk/include/opentelemetry/sdk/configuration/log_record_limits_configuration.h index 81ab1383be..26a66cc661 100644 --- a/sdk/include/opentelemetry/sdk/configuration/log_record_limits_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/log_record_limits_configuration.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -16,8 +18,12 @@ namespace configuration class LogRecordLimitsConfiguration { public: - std::size_t attribute_value_length_limit; - std::size_t attribute_count_limit; + // TODO: spec default is no limit, using 4096 to preserve original behavior + static constexpr std::size_t kDefaultAttributeValueLengthLimit = 4096; + static constexpr std::size_t kDefaultAttributeCountLimit = 128; + + std::size_t attribute_value_length_limit{kDefaultAttributeValueLengthLimit}; + std::size_t attribute_count_limit{kDefaultAttributeCountLimit}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/logger_config_configuration.h b/sdk/include/opentelemetry/sdk/configuration/logger_config_configuration.h index 736699754f..5f79709105 100644 --- a/sdk/include/opentelemetry/sdk/configuration/logger_config_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/logger_config_configuration.h @@ -17,9 +17,13 @@ namespace configuration class LoggerConfigConfiguration { public: - bool enabled{true}; - SeverityNumber minimum_severity{SeverityNumber::trace}; - bool trace_based{false}; + static constexpr bool kDefaultEnabled = true; + static constexpr SeverityNumber kDefaultMinimumSeverity = SeverityNumber::trace; + static constexpr bool kDefaultTraceBased = false; + + bool enabled{kDefaultEnabled}; + SeverityNumber minimum_severity{kDefaultMinimumSeverity}; + bool trace_based{kDefaultTraceBased}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/meter_provider_configuration.h b/sdk/include/opentelemetry/sdk/configuration/meter_provider_configuration.h index a48d522e50..844516a5b6 100644 --- a/sdk/include/opentelemetry/sdk/configuration/meter_provider_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/meter_provider_configuration.h @@ -25,7 +25,7 @@ class MeterProviderConfiguration public: std::vector> readers; std::vector> views; - ExemplarFilter exemplar_filter = ExemplarFilter::trace_based; + ExemplarFilter exemplar_filter{ExemplarFilter::trace_based}; std::unique_ptr meter_configurator; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_file_push_metric_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_file_push_metric_exporter_configuration.h index 03b75ef593..6744a5810b 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_file_push_metric_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_file_push_metric_exporter_configuration.h @@ -23,15 +23,19 @@ namespace configuration class OtlpFilePushMetricExporterConfiguration : public PushMetricExporterConfiguration { public: + static constexpr TemporalityPreference kDefaultTemporalityPreference = + TemporalityPreference::cumulative; + static constexpr DefaultHistogramAggregation kDefaultHistogramAggregation = + DefaultHistogramAggregation::explicit_bucket_histogram; + void Accept(PushMetricExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpFile(this); } std::string output_stream; - TemporalityPreference temporality_preference{TemporalityPreference::cumulative}; - DefaultHistogramAggregation default_histogram_aggregation{ - DefaultHistogramAggregation::explicit_bucket_histogram}; + TemporalityPreference temporality_preference{kDefaultTemporalityPreference}; + DefaultHistogramAggregation default_histogram_aggregation{kDefaultHistogramAggregation}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_log_record_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_log_record_exporter_configuration.h index 1d0f4a40d5..e1f036eb4d 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_log_record_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_log_record_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -23,6 +24,8 @@ namespace configuration class OtlpGrpcLogRecordExporterConfiguration : public LogRecordExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + void Accept(LogRecordExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpGrpc(this); @@ -33,7 +36,7 @@ class OtlpGrpcLogRecordExporterConfiguration : public LogRecordExporterConfigura std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; + std::size_t timeout{kDefaultTimeoutMs}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_push_metric_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_push_metric_exporter_configuration.h index fe63a5e5b9..3078042ca0 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_push_metric_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_push_metric_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -25,6 +26,12 @@ namespace configuration class OtlpGrpcPushMetricExporterConfiguration : public PushMetricExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + static constexpr TemporalityPreference kDefaultTemporalityPreference = + TemporalityPreference::cumulative; + static constexpr DefaultHistogramAggregation kDefaultHistogramAggregation = + DefaultHistogramAggregation::explicit_bucket_histogram; + void Accept(PushMetricExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpGrpc(this); @@ -35,10 +42,9 @@ class OtlpGrpcPushMetricExporterConfiguration : public PushMetricExporterConfigu std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; - TemporalityPreference temporality_preference{TemporalityPreference::cumulative}; - DefaultHistogramAggregation default_histogram_aggregation{ - DefaultHistogramAggregation::explicit_bucket_histogram}; + std::size_t timeout{kDefaultTimeoutMs}; + TemporalityPreference temporality_preference{kDefaultTemporalityPreference}; + DefaultHistogramAggregation default_histogram_aggregation{kDefaultHistogramAggregation}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_span_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_span_exporter_configuration.h index 2c2d116907..d61af2f9b1 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_span_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_grpc_span_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -23,6 +24,8 @@ namespace configuration class OtlpGrpcSpanExporterConfiguration : public SpanExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + void Accept(SpanExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpGrpc(this); @@ -33,7 +36,7 @@ class OtlpGrpcSpanExporterConfiguration : public SpanExporterConfiguration std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; + std::size_t timeout{kDefaultTimeoutMs}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_http_log_record_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_http_log_record_exporter_configuration.h index 7a27dfc77d..ac008febb5 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_http_log_record_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_http_log_record_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -24,6 +25,8 @@ namespace configuration class OtlpHttpLogRecordExporterConfiguration : public LogRecordExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + void Accept(LogRecordExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpHttp(this); @@ -34,7 +37,7 @@ class OtlpHttpLogRecordExporterConfiguration : public LogRecordExporterConfigura std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; + std::size_t timeout{kDefaultTimeoutMs}; OtlpHttpEncoding encoding{OtlpHttpEncoding::protobuf}; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_http_push_metric_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_http_push_metric_exporter_configuration.h index 4c71d45b46..a5da072812 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_http_push_metric_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_http_push_metric_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -26,6 +27,12 @@ namespace configuration class OtlpHttpPushMetricExporterConfiguration : public PushMetricExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + static constexpr TemporalityPreference kDefaultTemporalityPreference = + TemporalityPreference::cumulative; + static constexpr DefaultHistogramAggregation kDefaultHistogramAggregation = + DefaultHistogramAggregation::explicit_bucket_histogram; + void Accept(PushMetricExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpHttp(this); @@ -36,11 +43,10 @@ class OtlpHttpPushMetricExporterConfiguration : public PushMetricExporterConfigu std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; + std::size_t timeout{kDefaultTimeoutMs}; OtlpHttpEncoding encoding{OtlpHttpEncoding::protobuf}; - TemporalityPreference temporality_preference{TemporalityPreference::cumulative}; - DefaultHistogramAggregation default_histogram_aggregation{ - DefaultHistogramAggregation::explicit_bucket_histogram}; + TemporalityPreference temporality_preference{kDefaultTemporalityPreference}; + DefaultHistogramAggregation default_histogram_aggregation{kDefaultHistogramAggregation}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/otlp_http_span_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/otlp_http_span_exporter_configuration.h index 39353fa371..5867db0e24 100644 --- a/sdk/include/opentelemetry/sdk/configuration/otlp_http_span_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/otlp_http_span_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -24,6 +25,8 @@ namespace configuration class OtlpHttpSpanExporterConfiguration : public SpanExporterConfiguration { public: + static constexpr std::size_t kDefaultTimeoutMs = 10000; + void Accept(SpanExporterConfigurationVisitor *visitor) const override { visitor->VisitOtlpHttp(this); @@ -34,7 +37,7 @@ class OtlpHttpSpanExporterConfiguration : public SpanExporterConfiguration std::unique_ptr headers; std::string headers_list; std::string compression; - std::size_t timeout{0}; + std::size_t timeout{kDefaultTimeoutMs}; OtlpHttpEncoding encoding{OtlpHttpEncoding::protobuf}; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h b/sdk/include/opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h index 21264ee88a..6aed2b4e25 100644 --- a/sdk/include/opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -24,13 +25,17 @@ namespace configuration class PeriodicMetricReaderConfiguration : public MetricReaderConfiguration { public: + // TODO: spec default is 60000ms for interval, using 5000ms to preserve original behavior + static constexpr std::size_t kDefaultIntervalMs = 5000; + static constexpr std::size_t kDefaultTimeoutMs = 30000; + void Accept(MetricReaderConfigurationVisitor *visitor) const override { visitor->VisitPeriodic(this); } - std::size_t interval{0}; - std::size_t timeout{0}; + std::size_t interval{kDefaultIntervalMs}; + std::size_t timeout{kDefaultTimeoutMs}; std::unique_ptr exporter; std::vector> producers; std::unique_ptr cardinality_limits; diff --git a/sdk/include/opentelemetry/sdk/configuration/prometheus_pull_metric_exporter_configuration.h b/sdk/include/opentelemetry/sdk/configuration/prometheus_pull_metric_exporter_configuration.h index c3aa03fe65..141db9250b 100644 --- a/sdk/include/opentelemetry/sdk/configuration/prometheus_pull_metric_exporter_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/prometheus_pull_metric_exporter_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "opentelemetry/sdk/configuration/headers_configuration.h" @@ -23,17 +24,24 @@ namespace configuration class PrometheusPullMetricExporterConfiguration : public PullMetricExporterConfiguration { public: + static constexpr const char *kDefaultHost = "localhost"; + static constexpr std::size_t kDefaultPort = 9464; + static constexpr bool kDefaultWithoutScopeInfo = false; // schema: scope_info_enabled=true + static constexpr bool kDefaultWithoutTargetInfo = false; // schema: target_info_enabled=true + static constexpr TranslationStrategy kDefaultTranslationStrategy = + TranslationStrategy::UnderscoreEscapingWithSuffixes; + void Accept(PullMetricExporterConfigurationVisitor *visitor) const override { visitor->VisitPrometheus(this); } - std::string host; - std::size_t port{0}; - bool without_scope_info{false}; - bool without_target_info{false}; + std::string host{kDefaultHost}; + std::size_t port{kDefaultPort}; + bool without_scope_info{kDefaultWithoutScopeInfo}; + bool without_target_info{kDefaultWithoutTargetInfo}; std::unique_ptr with_resource_constant_labels; - TranslationStrategy translation_strategy; + TranslationStrategy translation_strategy{kDefaultTranslationStrategy}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h index 82cdb8e683..23e559977a 100644 --- a/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h +++ b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h @@ -70,7 +70,7 @@ class RymlDocumentNode : public DocumentNode const RymlDocument *doc_; ryml::ConstNodeRef node_; - std::size_t depth_; + std::size_t depth_{}; }; class RymlDocumentNodeConstIteratorImpl : public DocumentNodeConstIteratorImpl diff --git a/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h b/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h index a4624bb1a5..4c0556308c 100644 --- a/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -16,12 +18,20 @@ namespace configuration class SpanLimitsConfiguration { public: - std::size_t attribute_value_length_limit; - std::size_t attribute_count_limit; - std::size_t event_count_limit; - std::size_t link_count_limit; - std::size_t event_attribute_count_limit; - std::size_t link_attribute_count_limit; + // TODO: spec default is no limit, using 4096 to preserve original behavior + static constexpr std::size_t kDefaultAttributeValueLengthLimit = 4096; + static constexpr std::uint32_t kDefaultAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultEventCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkCountLimit = 128; + static constexpr std::uint32_t kDefaultEventAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkAttributeCountLimit = 128; + + std::size_t attribute_value_length_limit{kDefaultAttributeValueLengthLimit}; + std::uint32_t attribute_count_limit{kDefaultAttributeCountLimit}; + std::uint32_t event_count_limit{kDefaultEventCountLimit}; + std::uint32_t link_count_limit{kDefaultLinkCountLimit}; + std::uint32_t event_attribute_count_limit{kDefaultEventAttributeCountLimit}; + std::uint32_t link_attribute_count_limit{kDefaultLinkAttributeCountLimit}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h b/sdk/include/opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h index bc19cad509..b884142a77 100644 --- a/sdk/include/opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h @@ -18,12 +18,15 @@ namespace configuration class TraceIdRatioBasedSamplerConfiguration : public SamplerConfiguration { public: + // TODO: spec default is 1.0, using 0.0 to preserve original behavior + static constexpr double kDefaultRatio = 0.0; + void Accept(SamplerConfigurationVisitor *visitor) const override { visitor->VisitTraceIdRatioBased(this); } - double ratio{0.0}; + double ratio{kDefaultRatio}; }; } // namespace configuration diff --git a/sdk/include/opentelemetry/sdk/configuration/view_stream_configuration.h b/sdk/include/opentelemetry/sdk/configuration/view_stream_configuration.h index 006c83d460..1f10d837c2 100644 --- a/sdk/include/opentelemetry/sdk/configuration/view_stream_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/view_stream_configuration.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -22,10 +23,13 @@ namespace configuration class ViewStreamConfiguration { public: + // 0 is a sentinel: inherit cardinality limit from the metric reader (schema exclusiveMinimum: 0) + static constexpr std::size_t kInheritFromReader = 0; + std::string name; std::string description; std::unique_ptr aggregation; - std::size_t aggregation_cardinality_limit; + std::size_t aggregation_cardinality_limit{kInheritFromReader}; std::unique_ptr attribute_keys; }; diff --git a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h index 1ec8504bb7..4ce2370798 100644 --- a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h @@ -109,6 +109,11 @@ class BatchLogRecordProcessor : public LogRecordProcessor bool HasEnabledFilter() const noexcept override { return false; } + bool RecordableEnforcesLogRecordLimits() const noexcept override + { + return exporter_ != nullptr && exporter_->RecordableEnforcesLogRecordLimits(); + } + /** * Class destructor which invokes the Shutdown() method. */ diff --git a/sdk/include/opentelemetry/sdk/logs/exporter.h b/sdk/include/opentelemetry/sdk/logs/exporter.h index 42d90b3305..1fae5cb85c 100644 --- a/sdk/include/opentelemetry/sdk/logs/exporter.h +++ b/sdk/include/opentelemetry/sdk/logs/exporter.h @@ -68,6 +68,15 @@ class OPENTELEMETRY_EXPORT LogRecordExporter */ virtual bool Shutdown( std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept = 0; + + /** + * Returns true when the recordable produced by MakeRecordable() enforces + * LogRecord attribute limits (count and value length). The default returns + * false so the SDK can skip the per-record SetLogRecordLimits() call for + * recordables that ignore limits. Exporters whose recordable applies the + * limits (OTLP, ostream) override this to return true. + */ + virtual bool RecordableEnforcesLogRecordLimits() const noexcept { return false; } }; } // namespace logs } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/logs/log_record_limits.h b/sdk/include/opentelemetry/sdk/logs/log_record_limits.h new file mode 100644 index 0000000000..0d16bf3ccd --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/log_record_limits.h @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +/** + * LogRecordLimits carries the SDK-level attribute limits applied to every + * LogRecord emitted through a LoggerProvider. The defaults match the + * specification: at most 128 attributes per record, attribute value length + * unlimited (no truncation). + * + * See https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits. + */ +struct LogRecordLimits +{ + static constexpr std::size_t kDefaultAttributeCountLimit = 128; + static constexpr std::size_t kDefaultAttributeValueLengthLimit = + (std::numeric_limits::max)(); + + std::size_t attribute_count_limit = kDefaultAttributeCountLimit; + std::size_t attribute_value_length_limit = kDefaultAttributeValueLengthLimit; + + /** + * Limits that impose no cap. A recordable uses these until a LoggerProvider + * injects the configured limits, so a recordable created outside a provider + * does not enforce any limit on its own. + */ + static constexpr LogRecordLimits NoLimits() noexcept + { + return LogRecordLimits{(std::numeric_limits::max)(), + (std::numeric_limits::max)()}; + } +}; + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h index 8be05720a1..44ed4ec1ee 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger.h +++ b/sdk/include/opentelemetry/sdk/logs/logger.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -72,7 +73,7 @@ class Logger final : public opentelemetry::logs::Logger return GetInstrumentationScope(); } -private: +protected: bool EnabledImplementation(opentelemetry::logs::Severity severity, const opentelemetry::logs::EventId &event_id) const noexcept override; @@ -89,6 +90,19 @@ class Logger final : public opentelemetry::logs::Logger opentelemetry::logs::Severity severity, const opentelemetry::logs::EventId &event_id) const noexcept override; #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + +private: + // LoggerProvider needs access to UpdateLoggerConfig to propagate configuration updates to + // existing loggers. + friend class LoggerProvider; + + /** + * Update this logger's LoggerConfig. Called only by + * LoggerProvider::UpdateLoggerConfigurator when the provider-level + * LoggerConfigurator is replaced at runtime. + */ + void UpdateLoggerConfig(LoggerConfig config) noexcept; + // The name of this logger std::string logger_name_; @@ -96,7 +110,10 @@ class Logger final : public opentelemetry::logs::Logger // logger-context. std::unique_ptr instrumentation_scope_; std::shared_ptr context_; - LoggerConfig logger_config_; + // LoggerConfig state is stored in atomic variables to avoid locking in the hot path of Enabled() + // and EmitLogRecord(). + std::atomic logger_enabled_{true}; + std::atomic trace_based_{false}; static opentelemetry::logs::NoopLogger kNoopLogger; }; diff --git a/sdk/include/opentelemetry/sdk/logs/logger_context.h b/sdk/include/opentelemetry/sdk/logs/logger_context.h index 038da2d948..bf19fa879c 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger_context.h +++ b/sdk/include/opentelemetry/sdk/logs/logger_context.h @@ -9,6 +9,7 @@ #include "logger_config.h" #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/version.h" @@ -43,7 +44,8 @@ class LoggerContext std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( LoggerConfig::Default()) - .Build())) noexcept; + .Build()), + LogRecordLimits log_record_limits = LogRecordLimits()) noexcept; /** * Attaches a log processor to list of configured processors to this logger context. @@ -76,6 +78,28 @@ class LoggerContext const instrumentationscope::ScopeConfigurator &GetLoggerConfigurator() const noexcept; + /** + * Obtain the LogRecord limits applied by this context. + * @return The LogRecordLimits for this logger context. + */ + const LogRecordLimits &GetLogRecordLimits() const noexcept; + + /** + * Whether any configured processor produces a recordable that enforces the + * LogRecord attribute limits. Computed once at construction (and refreshed by + * AddProcessor) so the Logger hot path can skip the per-record + * SetLogRecordLimits() virtual call when no processor needs it. + * @return true if at least one processor enforces limits. + */ + bool RecordableEnforcesLogRecordLimits() const noexcept; + + /** + * Replace the ScopeConfigurator for this logger context. + * @param logger_configurator The new configurator. + */ + void SetLoggerConfigurator(std::unique_ptr> + logger_configurator) noexcept; + /** * Force all active LogProcessors to flush any buffered logs * within the given timeout. @@ -93,6 +117,13 @@ class LoggerContext std::unique_ptr processor_; std::unique_ptr> logger_configurator_; + + LogRecordLimits log_record_limits_; + + // Cached capability flag: true when at least one configured processor's + // recordable enforces the LogRecord attribute limits. Refreshed by + // AddProcessor so the Logger hot path reads a plain bool. + bool recordable_enforces_limits_{false}; }; } // namespace logs } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/logs/logger_provider.h b/sdk/include/opentelemetry/sdk/logs/logger_provider.h index a1cb054772..06ef66cc6a 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger_provider.h +++ b/sdk/include/opentelemetry/sdk/logs/logger_provider.h @@ -13,7 +13,9 @@ #include "opentelemetry/logs/logger_provider.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/logs/logger.h" +#include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/resource/resource.h" @@ -107,6 +109,20 @@ class OPENTELEMETRY_EXPORT LoggerProvider final : public opentelemetry::logs::Lo */ void AddProcessor(std::unique_ptr processor) noexcept; + /** + * Update the LoggerConfigurator for this provider. Updates the LoggerConfig for all existing + * loggers. + * + * @param logger_configurator The new configurator. + * + * @note Calling LoggerProvider::GetLogger from within the + * ScopeConfigurator::ComputeConfig function (as a scope_matcher callback set with + * ScopeConfigurator::AddCondition) is not supported and will result in a deadlock. + */ + void UpdateLoggerConfigurator( + std::unique_ptr> + logger_configurator) noexcept; + /** * Obtain the resource associated with this logger provider. * @return The resource for this logger provider. diff --git a/sdk/include/opentelemetry/sdk/logs/logger_provider_factory.h b/sdk/include/opentelemetry/sdk/logs/logger_provider_factory.h index 6745b39cee..6edc2c2983 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger_provider_factory.h +++ b/sdk/include/opentelemetry/sdk/logs/logger_provider_factory.h @@ -6,6 +6,7 @@ #include #include +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/logger_provider.h" #include "opentelemetry/sdk/logs/processor.h" @@ -66,6 +67,15 @@ class OPENTELEMETRY_EXPORT LoggerProviderFactory const opentelemetry::sdk::resource::Resource &resource, std::unique_ptr> logger_configurator); + /** + * Create a LoggerProvider with explicit LogRecord limits. + */ + static std::unique_ptr Create( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr> logger_configurator, + LogRecordLimits log_record_limits); + /** * Create a LoggerProvider. */ diff --git a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h index 8158a2dbf6..b7f73ec0c1 100644 --- a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h @@ -64,6 +64,8 @@ class MultiLogRecordProcessor : public LogRecordProcessor bool HasEnabledFilter() const noexcept override; + bool RecordableEnforcesLogRecordLimits() const noexcept override; + protected: /** * Exports all log records that have not yet been exported to the configured Exporter. diff --git a/sdk/include/opentelemetry/sdk/logs/multi_recordable.h b/sdk/include/opentelemetry/sdk/logs/multi_recordable.h index fe7a8b571f..d1c2fe5451 100644 --- a/sdk/include/opentelemetry/sdk/logs/multi_recordable.h +++ b/sdk/include/opentelemetry/sdk/logs/multi_recordable.h @@ -13,6 +13,7 @@ #include "opentelemetry/common/timestamp.h" #include "opentelemetry/logs/log_record.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/resource/resource.h" @@ -107,6 +108,12 @@ class MultiRecordable final : public Recordable void SetInstrumentationScope(const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept override; + /** + * Propagate attribute limits to every wrapped recordable. Must be called + * before any SetAttribute call. + */ + void SetLogRecordLimits(const LogRecordLimits &limits) noexcept override; + private: std::unordered_map> recordables_; }; diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h index 557db92250..41e47d1a27 100644 --- a/sdk/include/opentelemetry/sdk/logs/processor.h +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -111,6 +111,16 @@ class LogRecordProcessor { return true; } + +public: + /** + * Returns true when records produced through this processor enforce LogRecord + * attribute limits (count and value length). The default returns false. + * Processors backed by an exporter delegate to the exporter; composite + * processors return true if any child does. The SDK Logger consults this once + * per context to decide whether to push limits onto each recordable. + */ + virtual bool RecordableEnforcesLogRecordLimits() const noexcept { return false; } }; } // namespace logs } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h index 044285540f..ad0c6086c0 100644 --- a/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h +++ b/sdk/include/opentelemetry/sdk/logs/read_write_log_record.h @@ -14,6 +14,7 @@ #include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/readable_log_record.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_flags.h" @@ -153,6 +154,13 @@ class ReadWriteLogRecord final : public ReadableLogRecord void SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept override; + /** + * Apply attribute count and value length limits. Must be called before any + * SetAttribute call to take effect. The limits are copied into this + * recordable. + */ + void SetLogRecordLimits(const LogRecordLimits &limits) noexcept override; + /** * Get attributes of this log. * @return the body field of this log @@ -160,6 +168,12 @@ class ReadWriteLogRecord final : public ReadableLogRecord const std::unordered_map & GetAttributes() const noexcept override; + /** + * Get the number of attributes dropped because the attribute count limit + * was reached. Truncated string values are not counted as dropped. + */ + uint32_t GetDroppedAttributesCount() const noexcept override; + /** * Get resource of this log * @return the resource of this log @@ -200,6 +214,13 @@ class ReadWriteLogRecord final : public ReadableLogRecord int64_t event_id_{0}; std::string event_name_; + // Stored by value so the recordable does not depend on the limits object + // outliving the LoggerContext that supplied it. Defaults to no limits; the + // LoggerProvider wiring injects the configured limits via SetLogRecordLimits, + // so a recordable used outside a provider does not cap attributes on its own. + LogRecordLimits limits_ = LogRecordLimits::NoLimits(); + uint32_t dropped_attributes_count_{0}; + // We do not pay for trace state when not necessary struct TraceState { diff --git a/sdk/include/opentelemetry/sdk/logs/readable_log_record.h b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h index 3245717ce2..b612bcc51d 100644 --- a/sdk/include/opentelemetry/sdk/logs/readable_log_record.h +++ b/sdk/include/opentelemetry/sdk/logs/readable_log_record.h @@ -114,6 +114,14 @@ class ReadableLogRecord : public Recordable virtual const std::unordered_map & GetAttributes() const noexcept = 0; + /** + * Get the number of attributes dropped because the attribute count limit + * was reached. The default implementation reports zero so existing + * recordables that do not enforce limits compile without changes. + * @return the number of dropped attributes + */ + virtual uint32_t GetDroppedAttributesCount() const noexcept { return 0; } + /** * Get resource of this log * @return the resource of this log diff --git a/sdk/include/opentelemetry/sdk/logs/recordable.h b/sdk/include/opentelemetry/sdk/logs/recordable.h index ea203fb8a4..d7ddb3b34a 100644 --- a/sdk/include/opentelemetry/sdk/logs/recordable.h +++ b/sdk/include/opentelemetry/sdk/logs/recordable.h @@ -21,6 +21,9 @@ class InstrumentationScope; namespace logs { + +struct LogRecordLimits; + /** * Maintains a representation of a log in a format that can be processed by a recorder. * @@ -43,6 +46,15 @@ class Recordable : public opentelemetry::logs::LogRecord virtual void SetInstrumentationScope( const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept = 0; + + /** + * Apply attribute count and value length limits to this log. The default + * implementation is a no-op; concrete recordables that wish to enforce + * limits override this and copy the supplied limits before any + * SetAttribute call is observed, so the caller does not need to keep the + * supplied object alive. + */ + virtual void SetLogRecordLimits(const LogRecordLimits & /* limits */) noexcept {} }; } // namespace logs diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h index 94b0bb7bdd..42f1b1b7e4 100644 --- a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h @@ -51,6 +51,11 @@ class SimpleLogRecordProcessor : public LogRecordProcessor bool HasEnabledFilter() const noexcept override { return false; } + bool RecordableEnforcesLogRecordLimits() const noexcept override + { + return exporter_ != nullptr && exporter_->RecordableEnforcesLogRecordLimits(); + } + bool IsShutdown() const noexcept; private: diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h index 82985b2da6..0ae31b4be3 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h @@ -75,11 +75,13 @@ class AlignedHistogramBucketExemplarReservoir : public FixedSizeExemplarReservoi return static_cast(max_size); } - private: + public: void reset() override { // Do nothing } + + private: std::vector boundaries_; }; }; diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/filtered_exemplar_reservoir.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/filtered_exemplar_reservoir.h new file mode 100644 index 0000000000..179cf7d533 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/filtered_exemplar_reservoir.h @@ -0,0 +1,111 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW + +# include +# include +# include +# include + +# include "opentelemetry/common/timestamp.h" +# include "opentelemetry/context/context.h" +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/sdk/metrics/data/exemplar_data.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" +# include "opentelemetry/sdk/metrics/exemplar/reservoir.h" +# include "opentelemetry/trace/context.h" +# include "opentelemetry/trace/span_context.h" +# include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace metrics +{ + +/** + * A reservoir that pre-filters measurements according to an ExemplarFilterType before + * delegating eligible ones to a wrapped reservoir. + */ +class FilteredExemplarReservoir final : public ExemplarReservoir +{ +public: + FilteredExemplarReservoir(ExemplarFilterType filter_type, + nostd::shared_ptr reservoir) + : should_sample_(SelectFilter(filter_type)), reservoir_(std::move(reservoir)) + {} + + void OfferMeasurement(int64_t value, + const MetricAttributes &attributes, + const opentelemetry::context::Context &context, + const opentelemetry::common::SystemTimestamp ×tamp) noexcept override + { + if (should_sample_(context)) + { + reservoir_->OfferMeasurement(value, attributes, context, timestamp); + } + } + + void OfferMeasurement(double value, + const MetricAttributes &attributes, + const opentelemetry::context::Context &context, + const opentelemetry::common::SystemTimestamp ×tamp) noexcept override + { + if (should_sample_(context)) + { + reservoir_->OfferMeasurement(value, attributes, context, timestamp); + } + } + + std::vector> CollectAndReset( + const MetricAttributes &pointAttributes) noexcept override + { + return reservoir_->CollectAndReset(pointAttributes); + } + +private: + using ShouldSampleFn = bool (*)(const opentelemetry::context::Context &context); + + static bool AlwaysOn(const opentelemetry::context::Context & /* context */) noexcept + { + return true; + } + + static bool AlwaysOff(const opentelemetry::context::Context & /* context */) noexcept + { + return false; + } + + static bool TraceBased(const opentelemetry::context::Context &context) noexcept + { + const opentelemetry::trace::SpanContext span_context = + opentelemetry::trace::GetSpanContext(context); + return span_context.IsValid() && span_context.IsSampled(); + } + + static ShouldSampleFn SelectFilter(ExemplarFilterType filter_type) noexcept + { + switch (filter_type) + { + case ExemplarFilterType::kAlwaysOn: + return &AlwaysOn; + case ExemplarFilterType::kAlwaysOff: + return &AlwaysOff; + case ExemplarFilterType::kTraceBased: + return &TraceBased; + } + return &TraceBased; // unreachable; all enumerators handled above + } + + ShouldSampleFn should_sample_; + nostd::shared_ptr reservoir_; +}; + +} // namespace metrics +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE + +#endif // ENABLE_METRICS_EXEMPLAR_PREVIEW diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir.h index efbd20abc4..19ad5551e6 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir.h @@ -75,7 +75,7 @@ class ExemplarReservoir static nostd::shared_ptr GetSimpleFilteredExemplarReservoir( ExemplarFilterType filter_type, - std::shared_ptr reservoir); + nostd::shared_ptr reservoir); static nostd::shared_ptr GetSimpleFixedSizeExemplarReservoir( size_t size, diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_cell.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_cell.h index 91e66264df..be22ba9e61 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_cell.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_cell.h @@ -18,7 +18,6 @@ # include "opentelemetry/sdk/metrics/data/metric_data.h" # include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/trace/context.h" -# include "opentelemetry/trace/span.h" # include "opentelemetry/trace/span_context.h" # include "opentelemetry/version.h" @@ -135,14 +134,10 @@ class ReservoirCell { attributes_ = attributes; record_time_ = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); - auto span = opentelemetry::trace::GetSpan(context); - if (span) + const auto current_ctx = opentelemetry::trace::GetSpanContext(context); + if (current_ctx.IsValid()) { - auto current_ctx = span->GetContext(); - if (current_ctx.IsValid()) - { - context_.reset(new opentelemetry::trace::SpanContext{current_ctx}); - } + context_.reset(new opentelemetry::trace::SpanContext{current_ctx}); } } diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h index 6ca88411af..e8e2970e89 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h @@ -10,6 +10,8 @@ # include "opentelemetry/common/macros.h" # include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" # include "opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" +# include "opentelemetry/sdk/metrics/exemplar/reservoir.h" # include "opentelemetry/sdk/metrics/exemplar/simple_fixed_size_exemplar_reservoir.h" # include "opentelemetry/version.h" @@ -49,7 +51,8 @@ static inline size_t GetSimpleFixedReservoirDefaultSize(const AggregationType ag static inline nostd::shared_ptr GetExemplarReservoir( const AggregationType agg_type, const AggregationConfig *agg_config, - const InstrumentDescriptor &instrument_descriptor) + const InstrumentDescriptor &instrument_descriptor, + ExemplarFilterType filter_type) { if (agg_type == AggregationType::kHistogram) { @@ -62,18 +65,21 @@ static inline nostd::shared_ptr GetExemplarReservoir( // if (histogram_agg_config != nullptr && histogram_agg_config->boundaries_.size() > 1) { - return nostd::shared_ptr(new AlignedHistogramBucketExemplarReservoir( - histogram_agg_config->boundaries_.size(), - AlignedHistogramBucketExemplarReservoir::GetHistogramCellSelector( - histogram_agg_config->boundaries_), - GetMapAndResetCellMethod(instrument_descriptor))); + return ExemplarReservoir::GetSimpleFilteredExemplarReservoir( + filter_type, + nostd::shared_ptr(new AlignedHistogramBucketExemplarReservoir( + histogram_agg_config->boundaries_.size(), + AlignedHistogramBucketExemplarReservoir::GetHistogramCellSelector( + histogram_agg_config->boundaries_), + GetMapAndResetCellMethod(instrument_descriptor)))); } } - return nostd::shared_ptr(new SimpleFixedSizeExemplarReservoir( - GetSimpleFixedReservoirDefaultSize(agg_type, agg_config), - SimpleFixedSizeExemplarReservoir::GetSimpleFixedSizeCellSelector(), - GetMapAndResetCellMethod(instrument_descriptor))); + return ExemplarReservoir::GetSimpleFilteredExemplarReservoir( + filter_type, nostd::shared_ptr(new SimpleFixedSizeExemplarReservoir( + GetSimpleFixedReservoirDefaultSize(agg_type, agg_config), + SimpleFixedSizeExemplarReservoir::GetSimpleFixedSizeCellSelector(), + GetMapAndResetCellMethod(instrument_descriptor)))); } } // namespace metrics } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/metrics/state/async_metric_storage.h b/sdk/include/opentelemetry/sdk/metrics/state/async_metric_storage.h index a3f56ef64d..017d802578 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/async_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/async_metric_storage.h @@ -13,7 +13,6 @@ #include "opentelemetry/sdk/metrics/aggregation/default_aggregation.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -37,7 +36,6 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora AsyncMetricStorage(const InstrumentDescriptor &instrument_descriptor, const AggregationType aggregation_type, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType exempler_filter_type, nostd::shared_ptr &&exemplar_reservoir, #endif const AggregationConfig *aggregation_config) @@ -49,7 +47,6 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora delta_hash_map_( std::make_unique(aggregation_config_->cardinality_limit_)), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - exemplar_filter_type_(exempler_filter_type), exemplar_reservoir_(std::move(exemplar_reservoir)), #endif temporal_metric_storage_(instrument_descriptor, aggregation_type, aggregation_config) @@ -66,11 +63,8 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora for (auto &measurement : measurements) { #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - if (exemplar_filter_type_ == ExemplarFilterType::kAlwaysOn) - { - exemplar_reservoir_->OfferMeasurement(measurement.second, {}, {}, - std::chrono::system_clock::now()); - } + exemplar_reservoir_->OfferMeasurement(measurement.second, {}, {}, + std::chrono::system_clock::now()); #endif auto aggr = DefaultAggregation::CreateAggregation(aggregation_type_, instrument_descriptor_); @@ -146,7 +140,6 @@ class AsyncMetricStorage : public MetricStorage, public AsyncWritableMetricStora std::unique_ptr delta_hash_map_; opentelemetry::common::SpinLockMutex hashmap_lock_; #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType exemplar_filter_type_; nostd::shared_ptr exemplar_reservoir_; #endif TemporalMetricStorage temporal_metric_storage_; diff --git a/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h b/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h index 19e10343ba..38f62005e1 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h @@ -26,11 +26,17 @@ namespace metrics using opentelemetry::sdk::common::OrderedAttributeMap; constexpr size_t kAggregationCardinalityLimit = 2000; -const std::string kAttributesLimitOverflowKey = "otel.metric.overflow"; const bool kAttributesLimitOverflowValue = true; -const MetricAttributes kOverflowAttributes = { - {kAttributesLimitOverflowKey, - kAttributesLimitOverflowValue}}; // precalculated for optimization + +constexpr char kAttributesLimitOverflowKey[] = "otel.metric.overflow"; + +inline const MetricAttributes &GetOverflowAttributes() +{ + static const MetricAttributes value = { + {kAttributesLimitOverflowKey, + kAttributesLimitOverflowValue}}; // precalculated for optimization + return value; +} class AttributeHashGenerator { @@ -127,7 +133,7 @@ class AttributesHashMapWithCustomHash } else if (IsOverflowAttributes(attributes)) { - hash_map_[kOverflowAttributes] = std::move(aggr); + hash_map_[GetOverflowAttributes()] = std::move(aggr); } else { @@ -144,7 +150,7 @@ class AttributesHashMapWithCustomHash } else if (IsOverflowAttributes(attributes)) { - hash_map_[kOverflowAttributes] = std::move(aggr); + hash_map_[GetOverflowAttributes()] = std::move(aggr); } else { @@ -191,13 +197,13 @@ class AttributesHashMapWithCustomHash Aggregation *GetOrSetOveflowAttributes(std::unique_ptr agg) { - auto it = hash_map_.find(kOverflowAttributes); + auto it = hash_map_.find(GetOverflowAttributes()); if (it != hash_map_.end()) { return it->second.get(); } - auto result = hash_map_.emplace(kOverflowAttributes, std::move(agg)); + auto result = hash_map_.emplace(GetOverflowAttributes(), std::move(agg)); return result.first->second.get(); } @@ -205,22 +211,15 @@ class AttributesHashMapWithCustomHash { // If the incoming attributes are exactly the overflow sentinel, route // directly to the overflow entry. - if (attributes == kOverflowAttributes) + if (attributes == GetOverflowAttributes()) { return true; } - // Determine if overflow entry already exists. - bool has_overflow = (hash_map_.find(kOverflowAttributes) != hash_map_.end()); - // If overflow already present, total size already includes it; trigger overflow - // when current size (including overflow) is >= limit. - if (has_overflow) - { - return hash_map_.size() >= attributes_limit_; - } - // If overflow not present yet, simulate adding a new distinct key. If that - // would exceed the limit, we redirect to overflow instead of creating a - // new real attribute entry. - return (hash_map_.size() + 1) >= attributes_limit_; + // The configured limit applies to distinct non-overflow attribute sets. + // The overflow point is an additional reserved entry. + const bool has_overflow = (hash_map_.find(GetOverflowAttributes()) != hash_map_.end()); + const size_t non_overflow_size = hash_map_.size() - (has_overflow ? 1 : 0); + return non_overflow_size >= attributes_limit_; } }; diff --git a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h index 0c77f74c63..a3aa7b8d13 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h @@ -36,7 +36,6 @@ #endif #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -48,25 +47,11 @@ namespace metrics class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage { -#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - - static inline bool EnableExamplarFilter(ExemplarFilterType filter_type, - const opentelemetry::context::Context &context) - { - return filter_type == ExemplarFilterType::kAlwaysOn || - (filter_type == ExemplarFilterType::kTraceBased && - opentelemetry::trace::GetSpan(context)->GetContext().IsValid() && - opentelemetry::trace::GetSpan(context)->GetContext().IsSampled()); - } - -#endif // ENABLE_METRICS_EXEMPLAR_PREVIEW - public: SyncMetricStorage(const InstrumentDescriptor &instrument_descriptor, const AggregationType aggregation_type, std::shared_ptr attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType exempler_filter_type, nostd::shared_ptr &&exemplar_reservoir, #endif const AggregationConfig *aggregation_config) @@ -76,7 +61,6 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage std::make_unique(aggregation_config_->cardinality_limit_)), attributes_processor_(std::move(attributes_processor)), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - exemplar_filter_type_(exempler_filter_type), exemplar_reservoir_(std::move(exemplar_reservoir)), #endif temporal_metric_storage_(instrument_descriptor, aggregation_type, aggregation_config) @@ -97,10 +81,7 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage return; } #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - if (EnableExamplarFilter(exemplar_filter_type_, context)) - { - exemplar_reservoir_->OfferMeasurement(value, {}, context, std::chrono::system_clock::now()); - } + exemplar_reservoir_->OfferMeasurement(value, {}, context, std::chrono::system_clock::now()); #endif static MetricAttributes attr = MetricAttributes{}; std::lock_guard guard(attribute_hashmap_lock_); @@ -123,11 +104,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage return; } #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - if (EnableExamplarFilter(exemplar_filter_type_, context)) - { - exemplar_reservoir_->OfferMeasurement(value, attributes, context, - std::chrono::system_clock::now()); - } + exemplar_reservoir_->OfferMeasurement(value, attributes, context, + std::chrono::system_clock::now()); #endif MetricAttributes attr{attributes, attributes_processor_.get()}; @@ -155,10 +133,7 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage return; } #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - if (EnableExamplarFilter(exemplar_filter_type_, context)) - { - exemplar_reservoir_->OfferMeasurement(value, {}, context, std::chrono::system_clock::now()); - } + exemplar_reservoir_->OfferMeasurement(value, {}, context, std::chrono::system_clock::now()); #endif static MetricAttributes attr = MetricAttributes{}; std::lock_guard guard(attribute_hashmap_lock_); @@ -181,11 +156,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage return; } #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - if (EnableExamplarFilter(exemplar_filter_type_, context)) - { - exemplar_reservoir_->OfferMeasurement(value, attributes, context, - std::chrono::system_clock::now()); - } + exemplar_reservoir_->OfferMeasurement(value, attributes, context, + std::chrono::system_clock::now()); #endif MetricAttributes attr{attributes, attributes_processor_.get()}; std::lock_guard guard(attribute_hashmap_lock_); @@ -264,27 +236,25 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage // Must be called with attribute_hashmap_lock_ held. MetricAttributes ResolveCardinality(const MetricAttributes &filtered) noexcept { - if (filtered == kOverflowAttributes) + if (filtered == GetOverflowAttributes()) { - active_keys_.insert(kOverflowAttributes); - return kOverflowAttributes; + active_keys_.insert(GetOverflowAttributes()); + return GetOverflowAttributes(); } if (active_keys_.find(filtered) != active_keys_.end()) { return filtered; } const size_t limit = aggregation_config_->cardinality_limit_; - const bool has_overflow = active_keys_.find(kOverflowAttributes) != active_keys_.end(); - // Mirror AttributesHashMap::IsOverflowAttributes() exactly. When the - // overflow slot is already counted in active_keys_, only route to - // overflow when total active keys reach the limit. Otherwise simulate - // adding the overflow slot too, matching the (size + 1) >= limit branch. - const bool would_overflow = - has_overflow ? (active_keys_.size() >= limit) : (active_keys_.size() + 1 >= limit); + const bool has_overflow = active_keys_.find(GetOverflowAttributes()) != active_keys_.end(); + // Mirror AttributesHashMap::IsOverflowAttributes() exactly. The configured + // limit applies to non-overflow attribute sets, while overflow is reserved. + const size_t non_overflow_size = active_keys_.size() - (has_overflow ? 1 : 0); + const bool would_overflow = non_overflow_size >= limit; if (would_overflow) { - active_keys_.insert(kOverflowAttributes); - return kOverflowAttributes; + active_keys_.insert(GetOverflowAttributes()); + return GetOverflowAttributes(); } active_keys_.insert(filtered); return filtered; @@ -298,7 +268,6 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage std::function()> create_default_aggregation_; std::shared_ptr attributes_processor_; #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType exemplar_filter_type_; nostd::shared_ptr exemplar_reservoir_; #endif TemporalMetricStorage temporal_metric_storage_; diff --git a/sdk/include/opentelemetry/sdk/metrics/view/predicate.h b/sdk/include/opentelemetry/sdk/metrics/view/predicate.h index b4170e9698..85cdef0ba4 100644 --- a/sdk/include/opentelemetry/sdk/metrics/view/predicate.h +++ b/sdk/include/opentelemetry/sdk/metrics/view/predicate.h @@ -75,11 +75,13 @@ class ExactPredicate : public Predicate class MatchEverythingPattern : public Predicate { +public: bool Match(opentelemetry::nostd::string_view /* str */) const noexcept override { return true; } }; class MatchNothingPattern : public Predicate { +public: bool Match(opentelemetry::nostd::string_view /* str */) const noexcept override { return false; } }; } // namespace metrics diff --git a/sdk/include/opentelemetry/sdk/trace/multi_recordable.h b/sdk/include/opentelemetry/sdk/trace/multi_recordable.h index 1b1798a807..84c4e6be81 100644 --- a/sdk/include/opentelemetry/sdk/trace/multi_recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/multi_recordable.h @@ -58,6 +58,8 @@ class MultiRecordable : public Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + void SetSpanLimits(const SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const InstrumentationScope &instrumentation_scope) noexcept override; private: diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index 4f6a895170..e12d069f80 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -10,6 +10,7 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/empty_attributes.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -171,6 +172,12 @@ class Recordable */ virtual void SetDuration(std::chrono::nanoseconds duration) noexcept = 0; + /** + * Set the span limits for the span. + * This method must be called before any SetAttribute / AddEvent / AddLink call. + */ + virtual void SetSpanLimits(const SpanLimits & /* limits */) noexcept {} + /** * Get the SpanData object for this Recordable. * diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/composable_trace_id_ratio.h b/sdk/include/opentelemetry/sdk/trace/samplers/composable_probability.h similarity index 85% rename from sdk/include/opentelemetry/sdk/trace/samplers/composable_trace_id_ratio.h rename to sdk/include/opentelemetry/sdk/trace/samplers/composable_probability.h index fbec155def..9274670fd4 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/composable_trace_id_ratio.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/composable_probability.h @@ -16,7 +16,7 @@ namespace trace { /** - * ComposableTraceIdRatioBasedSampler samples a configurable fraction of traces + * ComposableProbabilitySampler samples a configurable fraction of traces * using consistent probability sampling. It corresponds to the specification's * ComposableProbability sampler and the ComposableProbabilitySamplerConfiguration * config model. @@ -24,10 +24,10 @@ namespace trace * The ratio maps to a 56-bit rejection threshold. A ratio of 0 drops every span * (an unreliable, non-probabilistic intent, equivalent to ComposableAlwaysOff). */ -class ComposableTraceIdRatioBasedSampler : public ComposableSampler +class ComposableProbabilitySampler : public ComposableSampler { public: - explicit ComposableTraceIdRatioBasedSampler(double ratio); + explicit ComposableProbabilitySampler(double ratio); SamplingIntent GetSamplingIntent( const opentelemetry::trace::SpanContext &parent_context, diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h index 6a7d570afb..e5869d8fc6 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_data.h +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -38,9 +40,12 @@ namespace trace class SpanDataEvent { public: - SpanDataEvent(std::string name, - opentelemetry::common::SystemTimestamp timestamp, - const opentelemetry::common::KeyValueIterable &attributes); + SpanDataEvent( + std::string name, + opentelemetry::common::SystemTimestamp timestamp, + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit = (std::numeric_limits::max)(), + std::size_t attribute_value_length_limit = (std::numeric_limits::max)()); /** * Get the name for this event @@ -61,10 +66,17 @@ class SpanDataEvent const std::unordered_map & GetAttributes() const noexcept; + /** + * Get the number of attributes dropped due to the per-event attribute count limit. + * @return the number of attributes dropped + */ + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + private: std::string name_; opentelemetry::common::SystemTimestamp timestamp_; opentelemetry::sdk::common::AttributeMap attribute_map_; + std::uint32_t dropped_attributes_count_{0}; }; /** @@ -73,8 +85,11 @@ class SpanDataEvent class SpanDataLink { public: - SpanDataLink(opentelemetry::trace::SpanContext span_context, - const opentelemetry::common::KeyValueIterable &attributes); + SpanDataLink( + opentelemetry::trace::SpanContext span_context, + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit = (std::numeric_limits::max)(), + std::size_t attribute_value_length_limit = (std::numeric_limits::max)()); /** * Get the attributes for this link @@ -89,9 +104,17 @@ class SpanDataLink */ const opentelemetry::trace::SpanContext &GetSpanContext() const noexcept { return span_context_; } + /** + * Get the number of attributes dropped due to the per-link + * attribute count limit. + * @return the number of attributes dropped + */ + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + private: opentelemetry::trace::SpanContext span_context_; opentelemetry::sdk::common::AttributeMap attribute_map_; + std::uint32_t dropped_attributes_count_{0}; }; /** @@ -235,8 +258,16 @@ class SpanData final : public Recordable void SetDuration(std::chrono::nanoseconds duration) noexcept override; + void SetSpanLimits(const SpanLimits &limits) noexcept override; + void SetInstrumentationScope(const InstrumentationScope &instrumentation_scope) noexcept override; + std::uint32_t GetDroppedAttributesCount() const noexcept { return dropped_attributes_count_; } + + std::uint32_t GetDroppedEventsCount() const noexcept { return dropped_events_count_; } + + std::uint32_t GetDroppedLinksCount() const noexcept { return dropped_links_count_; } + private: opentelemetry::trace::SpanContext span_context_{false, false}; opentelemetry::trace::SpanId parent_span_id_; @@ -252,6 +283,10 @@ class SpanData final : public Recordable opentelemetry::trace::SpanKind span_kind_{opentelemetry::trace::SpanKind::kInternal}; const opentelemetry::sdk::resource::Resource *resource_{nullptr}; const InstrumentationScope *instrumentation_scope_{nullptr}; + SpanLimits limits_{SpanLimits::NoLimits()}; + std::uint32_t dropped_attributes_count_{0}; + std::uint32_t dropped_events_count_{0}; + std::uint32_t dropped_links_count_{0}; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/span_limits.h b/sdk/include/opentelemetry/sdk/trace/span_limits.h new file mode 100644 index 0000000000..3f2db6e3cc --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/span_limits.h @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +/** + * Span limits set on the TracerProvider and used by supporting recordables. + * See https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-limits. + */ +struct SpanLimits +{ + static constexpr std::uint32_t kDefaultAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultEventCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkCountLimit = 128; + static constexpr std::uint32_t kDefaultEventAttributeCountLimit = 128; + static constexpr std::uint32_t kDefaultLinkAttributeCountLimit = 128; + static constexpr std::size_t kDefaultAttributeValueLengthLimit = + (std::numeric_limits::max)(); + + /// Maximum number of attributes per span. + std::uint32_t attribute_count_limit{kDefaultAttributeCountLimit}; + + /// Maximum allowed attribute value length (applies to string values and byte arrays) + std::size_t attribute_value_length_limit{kDefaultAttributeValueLengthLimit}; + + /// Maximum number of events per span. + std::uint32_t event_count_limit{kDefaultEventCountLimit}; + + /// Maximum number of links per span. + std::uint32_t link_count_limit{kDefaultLinkCountLimit}; + + /// Maximum number of attributes per span event. + std::uint32_t event_attribute_count_limit{kDefaultEventAttributeCountLimit}; + + /// Maximum number of attributes per span link. + std::uint32_t link_attribute_count_limit{kDefaultLinkAttributeCountLimit}; + + /** + * Limit values that impose no cap on any field + */ + static constexpr SpanLimits NoLimits() noexcept + { + SpanLimits limits; + limits.attribute_count_limit = (std::numeric_limits::max)(); + limits.attribute_value_length_limit = (std::numeric_limits::max)(); + limits.event_count_limit = (std::numeric_limits::max)(); + limits.link_count_limit = (std::numeric_limits::max)(); + limits.event_attribute_count_limit = (std::numeric_limits::max)(); + limits.link_attribute_count_limit = (std::numeric_limits::max)(); + return limits; + } +}; + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 4c9969a89f..601cfc1b7c 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -15,6 +15,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/noop.h" @@ -83,6 +84,9 @@ class Tracer final : public opentelemetry::trace::Tracer, /** Returns the configured span processor. */ SpanProcessor &GetProcessor() noexcept { return context_->GetProcessor(); } + /** Returns the configured span limits. */ + const SpanLimits &GetSpanLimits() const noexcept { return context_->GetSpanLimits(); } + /** Returns the configured Id generator */ IdGenerator &GetIdGenerator() const noexcept { return context_->GetIdGenerator(); } @@ -137,7 +141,6 @@ class Tracer final : public opentelemetry::trace::Tracer, #if OPENTELEMETRY_ABI_VERSION_NO < 2 std::atomic is_enabled_{false}; #endif - static const std::shared_ptr kNoopTracer; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_context.h b/sdk/include/opentelemetry/sdk/trace/tracer_context.h index 518c1cbdf6..f94972b8fe 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_context.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_context.h @@ -14,6 +14,7 @@ #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/version.h" @@ -50,7 +51,8 @@ class TracerContext std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; TracerContext(const TracerContext &) = delete; TracerContext(TracerContext &&) = delete; @@ -102,6 +104,12 @@ class TracerContext */ opentelemetry::sdk::trace::IdGenerator &GetIdGenerator() const noexcept; + /** + * Obtain the SpanLimits associated with this tracer context. + * @return The SpanLimits for this tracer context. + */ + const SpanLimits &GetSpanLimits() const noexcept; + /** * Replace the TracerConfigurator for this context. * @@ -129,6 +137,7 @@ class TracerContext std::unique_ptr id_generator_; std::unique_ptr processor_; std::unique_ptr> tracer_configurator_; + SpanLimits span_limits_; }; } // namespace trace diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h index b58f46dc20..5b9986a4c3 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h @@ -15,6 +15,7 @@ #include "opentelemetry/sdk/trace/random_id_generator.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/trace/tracer.h" @@ -41,6 +42,7 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T * not be a nullptr * @param tracer_configurator Provides access to a function that computes the TracerConfig for * Tracers provided by this TracerProvider. + * @param span_limits The span limits for this tracer provider. */ explicit TracerProvider( std::unique_ptr processor, @@ -53,7 +55,8 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; explicit TracerProvider( std::vector> &&processors, @@ -66,7 +69,8 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T std::make_unique>( instrumentationscope::ScopeConfigurator::Builder( TracerConfig::Default()) - .Build())) noexcept; + .Build()), + SpanLimits span_limits = SpanLimits::NoLimits()) noexcept; /** * Initialize a new tracer provider with a specified context @@ -130,6 +134,12 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T */ const opentelemetry::sdk::resource::Resource &GetResource() const noexcept; + /** + * Obtain the span limits configured for this tracer provider. + * @return The SpanLimits for this tracer provider. + */ + const opentelemetry::sdk::trace::SpanLimits &GetSpanLimits() const noexcept; + /** * Shutdown the span processor associated with this tracer provider. */ diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h index 85177f6565..81e0e36f01 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider_factory.h @@ -10,6 +10,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/trace/tracer_provider.h" @@ -55,6 +56,14 @@ class OPENTELEMETRY_EXPORT TracerProviderFactory std::unique_ptr id_generator, std::unique_ptr> tracer_configurator); + static std::unique_ptr Create( + std::unique_ptr processor, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits); + /* Series of creator methods with a vector of processors. */ static std::unique_ptr Create( @@ -82,6 +91,14 @@ class OPENTELEMETRY_EXPORT TracerProviderFactory std::unique_ptr id_generator, std::unique_ptr> tracer_configurator); + static std::unique_ptr Create( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits); + /* Create with a tracer context. */ static std::unique_ptr Create( diff --git a/sdk/include/opentelemetry/sdk/version/version.h b/sdk/include/opentelemetry/sdk/version/version.h index 487797e603..78074a50f5 100644 --- a/sdk/include/opentelemetry/sdk/version/version.h +++ b/sdk/include/opentelemetry/sdk/version/version.h @@ -3,7 +3,7 @@ #pragma once -#define OPENTELEMETRY_SDK_VERSION "1.28.0-dev" +#define OPENTELEMETRY_SDK_VERSION "1.29.0-dev" #include "opentelemetry/version.h" diff --git a/sdk/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index 7173ce75be..cc83201a97 100644 --- a/sdk/src/configuration/configuration_parser.cc +++ b/sdk/src/configuration/configuration_parser.cc @@ -1,11 +1,11 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include -#include #include #include +#include #include +#include #include #include #include @@ -356,10 +356,13 @@ std::unique_ptr ConfigurationParser::ParseAttributeLimitsConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = AttributeLimitsConfiguration; + auto model = std::make_unique(); - model->attribute_value_length_limit = node->GetInteger("attribute_value_length_limit", 4096); - model->attribute_count_limit = node->GetInteger("attribute_count_limit", 128); + model->attribute_value_length_limit = + node->GetInteger("attribute_value_length_limit", Config::kDefaultAttributeValueLengthLimit); + model->attribute_count_limit = + node->GetInteger("attribute_count_limit", Config::kDefaultAttributeCountLimit); return model; } @@ -379,12 +382,13 @@ std::unique_ptr ConfigurationParser::ParseHttpTlsConfigura std::unique_ptr ConfigurationParser::ParseGrpcTlsConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = GrpcTlsConfiguration; + auto model = std::make_unique(); model->ca_file = node->GetString("ca_file", ""); model->key_file = node->GetString("key_file", ""); model->cert_file = node->GetString("cert_file", ""); - model->insecure = node->GetBoolean("insecure", false); + model->insecure = node->GetBoolean("insecure", Config::kDefaultInsecure); return model; } @@ -393,7 +397,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpHttpLogRecordExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpHttpLogRecordExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -412,7 +417,7 @@ ConfigurationParser::ParseOtlpHttpLogRecordExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); const std::string encoding = node->GetString("encoding", "protobuf"); model->encoding = ParseOtlpHttpEncoding(node, encoding); @@ -424,7 +429,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpGrpcLogRecordExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpGrpcLogRecordExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -443,7 +449,7 @@ ConfigurationParser::ParseOtlpGrpcLogRecordExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); return model; } @@ -534,13 +540,15 @@ std::unique_ptr ConfigurationParser::ParseBatchLogRecordProcessorConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = BatchLogRecordProcessorConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->schedule_delay = node->GetInteger("schedule_delay", 5000); - model->export_timeout = node->GetInteger("export_timeout", 30000); - model->max_queue_size = node->GetInteger("max_queue_size", 2048); - model->max_export_batch_size = node->GetInteger("max_export_batch_size", 512); + model->schedule_delay = node->GetInteger("schedule_delay", Config::kDefaultScheduleDelayMs); + model->export_timeout = node->GetInteger("export_timeout", Config::kDefaultExportTimeoutMs); + model->max_queue_size = node->GetInteger("max_queue_size", Config::kDefaultMaxQueueSize); + model->max_export_batch_size = + node->GetInteger("max_export_batch_size", Config::kDefaultMaxExportBatchSize); child = node->GetRequiredChildNode("exporter"); model->exporter = ParseLogRecordExporterConfiguration(child); @@ -618,10 +626,13 @@ std::unique_ptr ConfigurationParser::ParseLogRecordLimitsConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = LogRecordLimitsConfiguration; + auto model = std::make_unique(); - model->attribute_value_length_limit = node->GetInteger("attribute_value_length_limit", 4096); - model->attribute_count_limit = node->GetInteger("attribute_count_limit", 128); + model->attribute_value_length_limit = + node->GetInteger("attribute_value_length_limit", Config::kDefaultAttributeValueLengthLimit); + model->attribute_count_limit = + node->GetInteger("attribute_count_limit", Config::kDefaultAttributeCountLimit); return model; } @@ -629,13 +640,14 @@ ConfigurationParser::ParseLogRecordLimitsConfiguration( LoggerConfigConfiguration ConfigurationParser::ParseLoggerConfigConfiguration( const std::unique_ptr &node) const { - LoggerConfigConfiguration model; - model.enabled = node->GetBoolean("enabled", true); + using Config = LoggerConfigConfiguration; + Config model; + model.enabled = node->GetBoolean("enabled", Config::kDefaultEnabled); const std::string minimum_severity_str = node->GetString("minimum_severity", "trace"); model.minimum_severity = ParseSeverityNumber(node, minimum_severity_str); - model.trace_based = node->GetBoolean("trace_based", false); + model.trace_based = node->GetBoolean("trace_based", Config::kDefaultTraceBased); return model; } @@ -756,7 +768,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpHttpPushMetricExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpHttpPushMetricExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -775,7 +788,7 @@ ConfigurationParser::ParseOtlpHttpPushMetricExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); const std::string temporality_preference = node->GetString("temporality_preference", "cumulative"); @@ -796,7 +809,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpGrpcPushMetricExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpGrpcPushMetricExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -815,7 +829,7 @@ ConfigurationParser::ParseOtlpGrpcPushMetricExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); const std::string temporality_preference = node->GetString("temporality_preference", "cumulative"); @@ -901,13 +915,16 @@ std::unique_ptr ConfigurationParser::ParsePrometheusPullMetricExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = PrometheusPullMetricExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->host = node->GetString("host", "localhost"); - model->port = node->GetInteger("port", 9464); - model->without_scope_info = node->GetBoolean("without_scope_info", false); - model->without_target_info = node->GetBoolean("without_target_info", false); + model->host = node->GetString("host", Config::kDefaultHost); + model->port = node->GetInteger("port", Config::kDefaultPort); + model->without_scope_info = + node->GetBoolean("without_scope_info", Config::kDefaultWithoutScopeInfo); + model->without_target_info = + node->GetBoolean("without_target_info", Config::kDefaultWithoutTargetInfo); child = node->GetChildNode("with_resource_constant_labels"); if (child) @@ -1093,16 +1110,18 @@ std::unique_ptr ConfigurationParser::ParseCardinalityLimitsConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = CardinalityLimitsConfiguration; + auto model = std::make_unique(); - model->default_limit = node->GetInteger("default", 2000); - model->counter = node->GetInteger("counter", 0); - model->gauge = node->GetInteger("gauge", 0); - model->histogram = node->GetInteger("histogram", 0); - model->observable_counter = node->GetInteger("observable_counter", 0); - model->observable_gauge = node->GetInteger("observable_gauge", 0); - model->observable_up_down_counter = node->GetInteger("observable_up_down_counter", 0); - model->up_down_counter = node->GetInteger("up_down_counter", 0); + model->default_limit = node->GetInteger("default", Config::kDefaultLimit); + model->counter = node->GetInteger("counter", Config::kInheritDefault); + model->gauge = node->GetInteger("gauge", Config::kInheritDefault); + model->histogram = node->GetInteger("histogram", Config::kInheritDefault); + model->observable_counter = node->GetInteger("observable_counter", Config::kInheritDefault); + model->observable_gauge = node->GetInteger("observable_gauge", Config::kInheritDefault); + model->observable_up_down_counter = + node->GetInteger("observable_up_down_counter", Config::kInheritDefault); + model->up_down_counter = node->GetInteger("up_down_counter", Config::kInheritDefault); return model; } @@ -1111,11 +1130,12 @@ std::unique_ptr ConfigurationParser::ParsePeriodicMetricReaderConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = PeriodicMetricReaderConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->interval = node->GetInteger("interval", 5000); - model->timeout = node->GetInteger("timeout", 30000); + model->interval = node->GetInteger("interval", Config::kDefaultIntervalMs); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); child = node->GetRequiredChildNode("exporter"); model->exporter = ParsePushMetricExporterConfiguration(child); @@ -1320,7 +1340,8 @@ std::unique_ptr ConfigurationParser::ParseExplicitBucketHistogramAggregationConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = ExplicitBucketHistogramAggregationConfiguration; + auto model = std::make_unique(); std::unique_ptr child; child = node->GetChildNode("boundaries"); @@ -1337,7 +1358,7 @@ ConfigurationParser::ParseExplicitBucketHistogramAggregationConfiguration( } } - model->record_min_max = node->GetBoolean("record_min_max", true); + model->record_min_max = node->GetBoolean("record_min_max", Config::kDefaultRecordMinMax); return model; } @@ -1346,11 +1367,12 @@ std::unique_ptr ConfigurationParser::ParseBase2ExponentialBucketHistogramAggregationConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = Base2ExponentialBucketHistogramAggregationConfiguration; + auto model = std::make_unique(); - model->max_scale = node->GetInteger("max_scale", 20); - model->max_size = node->GetInteger("max_size", 160); - model->record_min_max = node->GetBoolean("record_min_max", true); + model->max_scale = node->GetInteger("max_scale", Config::kDefaultMaxScale); + model->max_size = node->GetInteger("max_size", Config::kDefaultMaxSize); + model->record_min_max = node->GetBoolean("record_min_max", Config::kDefaultRecordMinMax); return model; } @@ -1427,12 +1449,14 @@ std::unique_ptr ConfigurationParser::ParseAggregationC std::unique_ptr ConfigurationParser::ParseViewStreamConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = ViewStreamConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->name = node->GetString("name", ""); - model->description = node->GetString("description", ""); - model->aggregation_cardinality_limit = node->GetInteger("aggregation_cardinality_limit", 0); + model->name = node->GetString("name", ""); + model->description = node->GetString("description", ""); + model->aggregation_cardinality_limit = + node->GetInteger("aggregation_cardinality_limit", Config::kInheritFromReader); child = node->GetChildNode("aggregation"); if (child) @@ -1594,14 +1618,29 @@ std::unique_ptr ConfigurationParser::ParsePropagatorCon std::unique_ptr ConfigurationParser::ParseSpanLimitsConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = SpanLimitsConfiguration; + auto model = std::make_unique(); + + const auto get_valid_uint32 = [&node](const std::string &name, std::size_t default_value) { + std::size_t value = node->GetInteger(name, default_value); + if (value > std::numeric_limits::max()) + { + std::string message = "Invalid value for " + name + ": " + std::to_string(value); + throw InvalidSchemaException(node->Location(), message); + } + return static_cast(value); + }; - model->attribute_value_length_limit = node->GetInteger("attribute_value_length_limit", 4096); - model->attribute_count_limit = node->GetInteger("attribute_count_limit", 128); - model->event_count_limit = node->GetInteger("event_count_limit", 128); - model->link_count_limit = node->GetInteger("link_count_limit", 128); - model->event_attribute_count_limit = node->GetInteger("event_attribute_count_limit", 128); - model->link_attribute_count_limit = node->GetInteger("link_attribute_count_limit", 128); + model->attribute_value_length_limit = + node->GetInteger("attribute_value_length_limit", Config::kDefaultAttributeValueLengthLimit); + model->attribute_count_limit = + get_valid_uint32("attribute_count_limit", Config::kDefaultAttributeCountLimit); + model->event_count_limit = get_valid_uint32("event_count_limit", Config::kDefaultEventCountLimit); + model->link_count_limit = get_valid_uint32("link_count_limit", Config::kDefaultLinkCountLimit); + model->event_attribute_count_limit = + get_valid_uint32("event_attribute_count_limit", Config::kDefaultEventAttributeCountLimit); + model->link_attribute_count_limit = + get_valid_uint32("link_attribute_count_limit", Config::kDefaultLinkAttributeCountLimit); return model; } @@ -1632,6 +1671,8 @@ ConfigurationParser::ParseJaegerRemoteSamplerConfiguration( const std::unique_ptr &node, size_t depth) const { + using Config = JaegerRemoteSamplerConfiguration; + auto model = std::make_unique(); std::unique_ptr child; @@ -1640,7 +1681,7 @@ ConfigurationParser::ParseJaegerRemoteSamplerConfiguration( OTEL_INTERNAL_LOG_ERROR("JaegerRemoteSamplerConfiguration: FIXME"); model->endpoint = node->GetString("endpoint", "FIXME"); - model->interval = node->GetInteger("interval", 0); + model->interval = node->GetInteger("interval", Config::kDefaultIntervalMs); child = node->GetChildNode("initial_sampler"); if (child) @@ -1703,10 +1744,11 @@ ConfigurationParser::ParseTraceIdRatioBasedSamplerConfiguration( const std::unique_ptr &node, size_t /* depth */) const { - auto model = std::make_unique(); + using Config = TraceIdRatioBasedSamplerConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->ratio = node->GetDouble("ratio", 0); + model->ratio = node->GetDouble("ratio", Config::kDefaultRatio); return model; } @@ -1732,8 +1774,9 @@ ConfigurationParser::ParseComposableProbabilitySamplerConfiguration( const std::unique_ptr &node, size_t /* depth */) const { + using Config = ComposableProbabilitySamplerConfiguration; auto model = std::make_unique(); - model->ratio = node->GetDouble("ratio", 1.0); + model->ratio = node->GetDouble("ratio", Config::kDefaultRatio); return model; } @@ -2009,7 +2052,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpHttpSpanExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpHttpSpanExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -2028,7 +2072,7 @@ ConfigurationParser::ParseOtlpHttpSpanExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); const std::string encoding = node->GetString("encoding", "protobuf"); model->encoding = ParseOtlpHttpEncoding(node, encoding); @@ -2040,7 +2084,8 @@ std::unique_ptr ConfigurationParser::ParseOtlpGrpcSpanExporterConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = OtlpGrpcSpanExporterConfiguration; + auto model = std::make_unique(); std::unique_ptr child; model->endpoint = node->GetRequiredString("endpoint"); @@ -2059,7 +2104,7 @@ ConfigurationParser::ParseOtlpGrpcSpanExporterConfiguration( model->headers_list = node->GetString("headers_list", ""); model->compression = node->GetString("compression", ""); - model->timeout = node->GetInteger("timeout", 10000); + model->timeout = node->GetInteger("timeout", Config::kDefaultTimeoutMs); return model; } @@ -2149,13 +2194,15 @@ std::unique_ptr ConfigurationParser::ParseBatchSpanProcessorConfiguration( const std::unique_ptr &node) const { - auto model = std::make_unique(); + using Config = BatchSpanProcessorConfiguration; + auto model = std::make_unique(); std::unique_ptr child; - model->schedule_delay = node->GetInteger("schedule_delay", 5000); - model->export_timeout = node->GetInteger("export_timeout", 30000); - model->max_queue_size = node->GetInteger("max_queue_size", 2048); - model->max_export_batch_size = node->GetInteger("max_export_batch_size", 512); + model->schedule_delay = node->GetInteger("schedule_delay", Config::kDefaultScheduleDelayMs); + model->export_timeout = node->GetInteger("export_timeout", Config::kDefaultExportTimeoutMs); + model->max_queue_size = node->GetInteger("max_queue_size", Config::kDefaultMaxQueueSize); + model->max_export_batch_size = + node->GetInteger("max_export_batch_size", Config::kDefaultMaxExportBatchSize); child = node->GetRequiredChildNode("exporter"); model->exporter = ParseSpanExporterConfiguration(child); @@ -2700,6 +2747,7 @@ std::unique_ptr ConfigurationParser::Parse(std::unique_ptrfile_format.c_str(), "%d.%d", &major, &minor); if (count != 2) { diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index f60362fa9e..897413643d 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -1,9 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include -#include #include +#include +#include #include #include #include @@ -16,6 +16,7 @@ #include "opentelemetry/common/kv_properties.h" #include "opentelemetry/context/propagation/composite_propagator.h" #include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/global_log_handler.h" @@ -64,6 +65,7 @@ #include "opentelemetry/sdk/configuration/jaeger_remote_sampler_configuration.h" #include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" #include "opentelemetry/sdk/configuration/log_record_exporter_configuration_visitor.h" +#include "opentelemetry/sdk/configuration/log_record_limits_configuration.h" #include "opentelemetry/sdk/configuration/log_record_processor_configuration.h" #include "opentelemetry/sdk/configuration/log_record_processor_configuration_visitor.h" #include "opentelemetry/sdk/configuration/logger_config_configuration.h" @@ -114,6 +116,7 @@ #include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" #include "opentelemetry/sdk/configuration/span_exporter_configuration.h" #include "opentelemetry/sdk/configuration/span_exporter_configuration_visitor.h" +#include "opentelemetry/sdk/configuration/span_limits_configuration.h" #include "opentelemetry/sdk/configuration/span_processor_configuration.h" #include "opentelemetry/sdk/configuration/span_processor_configuration_visitor.h" #include "opentelemetry/sdk/configuration/string_array_attribute_value_configuration.h" @@ -133,6 +136,7 @@ #include "opentelemetry/sdk/logs/batch_log_record_processor_factory.h" #include "opentelemetry/sdk/logs/batch_log_record_processor_options.h" #include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_provider.h" #include "opentelemetry/sdk/logs/logger_provider_factory.h" @@ -170,6 +174,7 @@ #include "opentelemetry/sdk/trace/samplers/parent_factory.h" #include "opentelemetry/sdk/trace/samplers/trace_id_ratio_factory.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_provider.h" #include "opentelemetry/sdk/trace/tracer_provider_factory.h" @@ -191,6 +196,68 @@ using common::WildcardMatch; namespace { +static opentelemetry::logs::Severity ToLogSeverity( + opentelemetry::sdk::configuration::SeverityNumber severity_number) +{ + // Convert configuration::SeverityNumber to opentelemetry::logs::Severity + // The configuration::SeverityNumber enum values do not match the opentelemetry::logs::Severity + // enum values use a switch statement for conversion. + switch (severity_number) + { + case SeverityNumber::trace: + return opentelemetry::logs::Severity::kTrace; + case SeverityNumber::trace2: + return opentelemetry::logs::Severity::kTrace2; + case SeverityNumber::trace3: + return opentelemetry::logs::Severity::kTrace3; + case SeverityNumber::trace4: + return opentelemetry::logs::Severity::kTrace4; + case SeverityNumber::debug: + return opentelemetry::logs::Severity::kDebug; + case SeverityNumber::debug2: + return opentelemetry::logs::Severity::kDebug2; + case SeverityNumber::debug3: + return opentelemetry::logs::Severity::kDebug3; + case SeverityNumber::debug4: + return opentelemetry::logs::Severity::kDebug4; + case SeverityNumber::info: + return opentelemetry::logs::Severity::kInfo; + case SeverityNumber::info2: + return opentelemetry::logs::Severity::kInfo2; + case SeverityNumber::info3: + return opentelemetry::logs::Severity::kInfo3; + case SeverityNumber::info4: + return opentelemetry::logs::Severity::kInfo4; + case SeverityNumber::warn: + return opentelemetry::logs::Severity::kWarn; + case SeverityNumber::warn2: + return opentelemetry::logs::Severity::kWarn2; + case SeverityNumber::warn3: + return opentelemetry::logs::Severity::kWarn3; + case SeverityNumber::warn4: + return opentelemetry::logs::Severity::kWarn4; + case SeverityNumber::error: + return opentelemetry::logs::Severity::kError; + case SeverityNumber::error2: + return opentelemetry::logs::Severity::kError2; + case SeverityNumber::error3: + return opentelemetry::logs::Severity::kError3; + case SeverityNumber::error4: + return opentelemetry::logs::Severity::kError4; + case SeverityNumber::fatal: + return opentelemetry::logs::Severity::kFatal; + case SeverityNumber::fatal2: + return opentelemetry::logs::Severity::kFatal2; + case SeverityNumber::fatal3: + return opentelemetry::logs::Severity::kFatal3; + case SeverityNumber::fatal4: + return opentelemetry::logs::Severity::kFatal4; + default: + break; + } + return opentelemetry::logs::Severity::kInvalid; +} + class ResourceAttributeValueSetter : public opentelemetry::sdk::configuration::AttributeValueConfigurationVisitor { @@ -1102,20 +1169,38 @@ std::unique_ptr SdkBuilder::CreateTra sdk_processors.push_back(CreateSpanProcessor(processor_model)); } - // FIXME-SDK: https://github.com/open-telemetry/opentelemetry-cpp/issues/3303 - // FIXME-SDK: use limits, id_generator, ... + opentelemetry::sdk::trace::SpanLimits span_limits; + if (model->limits) + { + span_limits.attribute_value_length_limit = model->limits->attribute_value_length_limit; + span_limits.attribute_count_limit = model->limits->attribute_count_limit; + span_limits.event_count_limit = model->limits->event_count_limit; + span_limits.link_count_limit = model->limits->link_count_limit; + span_limits.event_attribute_count_limit = model->limits->event_attribute_count_limit; + span_limits.link_attribute_count_limit = model->limits->link_attribute_count_limit; + } + if (model->tracer_configurator) { auto tracer_configurator = CreateTracerConfigurator(model->tracer_configurator); auto id_generator = opentelemetry::sdk::trace::RandomIdGeneratorFactory::Create(); sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create( std::move(sdk_processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator)); + std::move(tracer_configurator), span_limits); } else { - sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sdk_processors), - resource, std::move(sampler)); + auto id_generator = opentelemetry::sdk::trace::RandomIdGeneratorFactory::Create(); + auto tracer_configurator = + std::make_unique>( + opentelemetry::sdk::instrumentationscope:: + ScopeConfigurator::Builder( + opentelemetry::sdk::trace::TracerConfig::Default()) + .Build()); + sdk = opentelemetry::sdk::trace::TracerProviderFactory::Create( + std::move(sdk_processors), resource, std::move(sampler), std::move(id_generator), + std::move(tracer_configurator), span_limits); } return sdk; @@ -1874,15 +1959,17 @@ SdkBuilder::CreateLoggerConfigurator( using opentelemetry::sdk::instrumentationscope::ScopeConfigurator; using opentelemetry::sdk::logs::LoggerConfig; - LoggerConfig default_config = - model->default_config.enabled ? LoggerConfig::Enabled() : LoggerConfig::Disabled(); + LoggerConfig default_config = LoggerConfig::Create( + model->default_config.enabled, ToLogSeverity(model->default_config.minimum_severity), + model->default_config.trace_based); auto builder = ScopeConfigurator::Builder(default_config); for (const auto &entry : model->loggers) { LoggerConfig entry_config = - entry.config.enabled ? LoggerConfig::Enabled() : LoggerConfig::Disabled(); + LoggerConfig::Create(entry.config.enabled, ToLogSeverity(entry.config.minimum_severity), + entry.config.trace_based); std::string pattern = entry.name; builder.AddCondition( [pattern](const InstrumentationScope &scope) { @@ -1907,20 +1994,34 @@ std::unique_ptr SdkBuilder::CreateLogg sdk_processors.push_back(CreateLogRecordProcessor(processor_model)); } - // FIXME-SDK: https://github.com/open-telemetry/opentelemetry-cpp/issues/3303 - // FIXME-SDK: use limits + opentelemetry::sdk::logs::LogRecordLimits log_record_limits; + if (model->limits) + { + log_record_limits.attribute_value_length_limit = model->limits->attribute_value_length_limit; + log_record_limits.attribute_count_limit = model->limits->attribute_count_limit; + } + + std::unique_ptr> + logger_configurator; if (model->logger_configurator) { - auto logger_configurator = CreateLoggerConfigurator(model->logger_configurator); - sdk = opentelemetry::sdk::logs::LoggerProviderFactory::Create( - std::move(sdk_processors), resource, std::move(logger_configurator)); + logger_configurator = CreateLoggerConfigurator(model->logger_configurator); } else { - sdk = opentelemetry::sdk::logs::LoggerProviderFactory::Create(std::move(sdk_processors), - resource); + logger_configurator = + std::make_unique>( + opentelemetry::sdk::instrumentationscope:: + ScopeConfigurator::Builder( + opentelemetry::sdk::logs::LoggerConfig::Default()) + .Build()); } + sdk = opentelemetry::sdk::logs::LoggerProviderFactory::Create( + std::move(sdk_processors), resource, std::move(logger_configurator), log_record_limits); + return sdk; } diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 5c643da7b6..20bc6e4562 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -2,19 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include #include "opentelemetry/common/timestamp.h" #include "opentelemetry/context/context.h" -#include "opentelemetry/context/context_value.h" #include "opentelemetry/context/runtime_context.h" #include "opentelemetry/logs/event_id.h" #include "opentelemetry/logs/log_record.h" #include "opentelemetry/logs/noop.h" #include "opentelemetry/logs/severity.h" -#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/nostd/variant.h" @@ -25,10 +24,9 @@ #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/recordable.h" -#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/context.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" -#include "opentelemetry/trace/span_metadata.h" #include "opentelemetry/trace/trace_flags.h" #include "opentelemetry/version.h" @@ -49,33 +47,6 @@ nostd::string_view GetEventName(const opentelemetry::logs::EventId &event_id) no : nostd::string_view{}; } -trace_api::SpanContext ExtractSpanContextFromContext(const context::Context &context) noexcept -{ - if (!context.HasKey(trace_api::kSpanKey)) - { - return trace_api::SpanContext::GetInvalid(); - } - - const context::ContextValue context_value = context.GetValue(trace_api::kSpanKey); - - // Get the span metadata from the active span in the context - if (const nostd::shared_ptr *maybe_span = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span = *maybe_span; - return span->GetContext(); - } - // Get the span metadata directly from a SpanContext in the context. - // TODO: This path is unused and may be removed in the future. - if (const nostd::shared_ptr *maybe_span_context = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span_context = *maybe_span_context; - return *span_context; - } - return trace_api::SpanContext::GetInvalid(); -} - trace_api::SpanContext ExtractSpanContext( const nostd::variant &context_or_span) noexcept { @@ -85,16 +56,16 @@ trace_api::SpanContext ExtractSpanContext( } if (const context::Context *ctx = nostd::get_if(&context_or_span)) { - return ExtractSpanContextFromContext(*ctx); + return trace_api::GetSpanContext(*ctx); } return trace_api::SpanContext::GetInvalid(); } bool IsAllowedByTraceBasedFiltering( const nostd::variant &context_or_span, - const LoggerConfig &logger_config) noexcept + bool trace_based) noexcept { - if (!logger_config.IsTraceBased()) + if (!trace_based) { return true; } @@ -131,22 +102,28 @@ Logger::Logger( std::unique_ptr instrumentation_scope) noexcept : logger_name_(std::string(name)), instrumentation_scope_(std::move(instrumentation_scope)), - context_(std::move(context)), - logger_config_(context_->GetLoggerConfigurator().ComputeConfig(*instrumentation_scope_)) + context_(std::move(context)) +{ + LoggerConfig config = context_->GetLoggerConfigurator().ComputeConfig(*instrumentation_scope_); + UpdateLoggerConfig(config); +} + +void Logger::UpdateLoggerConfig(LoggerConfig config) noexcept { - SetMinimumSeverity(logger_config_.IsEnabled() - ? static_cast(logger_config_.GetMinimumSeverity()) - : opentelemetry::logs::kMaxSeverity); + logger_enabled_.store(config.IsEnabled(), std::memory_order_relaxed); + trace_based_.store(config.IsTraceBased(), std::memory_order_relaxed); + + SetMinimumSeverity(config.IsEnabled() ? static_cast(config.GetMinimumSeverity()) + : opentelemetry::logs::kMaxSeverity); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - SetExtendedEnabledRequired(logger_config_.IsTraceBased() || - context_->GetProcessor().HasEnabledFilter()); + SetExtendedEnabledRequired(config.IsTraceBased() || context_->GetProcessor().HasEnabledFilter()); #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 } const opentelemetry::nostd::string_view Logger::GetName() noexcept { - if (!logger_config_.IsEnabled()) + if (!logger_enabled_.load(std::memory_order_relaxed)) { return kNoopLogger.GetName(); } @@ -155,13 +132,17 @@ const opentelemetry::nostd::string_view Logger::GetName() noexcept opentelemetry::nostd::unique_ptr Logger::CreateLogRecord() noexcept { - if (!logger_config_.IsEnabled()) + if (!logger_enabled_.load(std::memory_order_relaxed)) { return kNoopLogger.CreateLogRecord(); } auto recordable = context_->GetProcessor().MakeRecordable(); + if (context_->RecordableEnforcesLogRecordLimits()) + { + recordable->SetLogRecordLimits(context_->GetLogRecordLimits()); + } recordable->SetObservedTimestamp(std::chrono::system_clock::now()); StampSpanContextFromVariant( @@ -177,13 +158,17 @@ opentelemetry::nostd::unique_ptr Logger::CreateL const nostd::variant &context_or_span) noexcept { - if (!logger_config_.IsEnabled()) + if (!logger_enabled_.load(std::memory_order_relaxed)) { return kNoopLogger.CreateLogRecord(); } auto recordable = context_->GetProcessor().MakeRecordable(); + if (context_->RecordableEnforcesLogRecordLimits()) + { + recordable->SetLogRecordLimits(context_->GetLogRecordLimits()); + } recordable->SetObservedTimestamp(std::chrono::system_clock::now()); StampSpanContextFromVariant(context_or_span, *recordable); @@ -195,7 +180,7 @@ opentelemetry::nostd::unique_ptr Logger::CreateL void Logger::EmitLogRecord( opentelemetry::nostd::unique_ptr &&log_record) noexcept { - if (!logger_config_.IsEnabled()) + if (!logger_enabled_.load(std::memory_order_relaxed)) { return kNoopLogger.EmitLogRecord(std::move(log_record)); } @@ -221,7 +206,7 @@ bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, { const nostd::variant current{ context::RuntimeContext::GetCurrent()}; - if (!IsAllowedByTraceBasedFiltering(current, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(current, trace_based_.load(std::memory_order_relaxed))) { return false; } @@ -235,7 +220,7 @@ bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, { const nostd::variant current{ context::RuntimeContext::GetCurrent()}; - if (!IsAllowedByTraceBasedFiltering(current, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(current, trace_based_.load(std::memory_order_relaxed))) { return false; } @@ -248,7 +233,8 @@ bool Logger::EnabledImplementation( const nostd::variant &context_or_span, opentelemetry::logs::Severity severity) const noexcept { - if (!IsAllowedByTraceBasedFiltering(context_or_span, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(context_or_span, + trace_based_.load(std::memory_order_relaxed))) { return false; } @@ -261,7 +247,8 @@ bool Logger::EnabledImplementation( opentelemetry::logs::Severity severity, const opentelemetry::logs::EventId &event_id) const noexcept { - if (!IsAllowedByTraceBasedFiltering(context_or_span, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(context_or_span, + trace_based_.load(std::memory_order_relaxed))) { return false; } diff --git a/sdk/src/logs/logger_context.cc b/sdk/src/logs/logger_context.cc index f7ad9db0c6..7f6d5315ec 100644 --- a/sdk/src/logs/logger_context.cc +++ b/sdk/src/logs/logger_context.cc @@ -6,7 +6,10 @@ #include #include +#include "opentelemetry/sdk/common/global_log_handler.h" + #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/multi_log_record_processor.h" @@ -20,20 +23,24 @@ namespace sdk namespace logs { -LoggerContext::LoggerContext(std::vector> &&processors, - const opentelemetry::sdk::resource::Resource &resource, - std::unique_ptr> - logger_configurator) noexcept +LoggerContext::LoggerContext( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr> logger_configurator, + LogRecordLimits log_record_limits) noexcept : resource_(resource), processor_( std::unique_ptr(new MultiLogRecordProcessor(std::move(processors)))), - logger_configurator_(std::move(logger_configurator)) + logger_configurator_(std::move(logger_configurator)), + log_record_limits_(log_record_limits), + recordable_enforces_limits_(processor_->RecordableEnforcesLogRecordLimits()) {} void LoggerContext::AddProcessor(std::unique_ptr processor) noexcept { auto multi_processor = static_cast(processor_.get()); multi_processor->AddProcessor(std::move(processor)); + recordable_enforces_limits_ = processor_->RecordableEnforcesLogRecordLimits(); } LogRecordProcessor &LoggerContext::GetProcessor() const noexcept @@ -52,6 +59,29 @@ const instrumentationscope::ScopeConfigurator &LoggerContext::GetL return *logger_configurator_; } +const LogRecordLimits &LoggerContext::GetLogRecordLimits() const noexcept +{ + return log_record_limits_; +} + +bool LoggerContext::RecordableEnforcesLogRecordLimits() const noexcept +{ + return recordable_enforces_limits_; +} + +void LoggerContext::SetLoggerConfigurator( + std::unique_ptr> + logger_configurator) noexcept +{ + if (!logger_configurator) + { + OTEL_INTERNAL_LOG_ERROR( + "[LoggerContext::SetLoggerConfigurator] logger_configurator must not be null, ignoring."); + return; + } + logger_configurator_ = std::move(logger_configurator); +} + bool LoggerContext::ForceFlush(std::chrono::microseconds timeout) noexcept { return processor_->ForceFlush(timeout); diff --git a/sdk/src/logs/logger_provider.cc b/sdk/src/logs/logger_provider.cc index ef345224f9..235c3bb4f8 100644 --- a/sdk/src/logs/logger_provider.cc +++ b/sdk/src/logs/logger_provider.cc @@ -107,6 +107,36 @@ void LoggerProvider::AddProcessor(std::unique_ptr processor) context_->AddProcessor(std::move(processor)); } +void LoggerProvider::UpdateLoggerConfigurator( + std::unique_ptr> + logger_configurator) noexcept +{ + if (!logger_configurator) + { + OTEL_INTERNAL_LOG_ERROR( + "[LoggerProvider::UpdateLoggerConfigurator] logger_configurator is null, " + "ignoring."); + return; + } + + // Lock the provider mutex to ensure that calls to GetLogger are exclusive with respect to the + // LoggerConfigurator update and corresponding LoggerConfig updates. This ensures that a Logger + // will never be returned from GetLogger with a LoggerConfig that is out of date with respect to + // the provider-level LoggerConfigurator. + const std::lock_guard guard(lock_); + context_->SetLoggerConfigurator(std::move(logger_configurator)); + + // The only way to set the LoggerConfig of a logger is on Logger construction in + // LoggerProvider::GetLogger or through Logger::UpdateLoggerConfig (which is private and only + // accessed by LoggerProvider). + for (auto &logger : loggers_) + { + LoggerConfig new_config = + context_->GetLoggerConfigurator().ComputeConfig(logger->GetInstrumentationScope()); + logger->UpdateLoggerConfig(new_config); + } +} + const opentelemetry::sdk::resource::Resource &LoggerProvider::GetResource() const noexcept { return context_->GetResource(); diff --git a/sdk/src/logs/logger_provider_factory.cc b/sdk/src/logs/logger_provider_factory.cc index f80dd43b5d..e8056f94d9 100644 --- a/sdk/src/logs/logger_provider_factory.cc +++ b/sdk/src/logs/logger_provider_factory.cc @@ -6,6 +6,7 @@ #include #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/logger_provider.h" @@ -76,6 +77,19 @@ std::unique_ptr LoggerProviderFactory: return provider; } +std::unique_ptr LoggerProviderFactory::Create( + std::vector> &&processors, + const resource::Resource &resource, + std::unique_ptr> logger_configurator, + LogRecordLimits log_record_limits) +{ + auto context = std::make_unique(std::move(processors), resource, + std::move(logger_configurator), log_record_limits); + std::unique_ptr provider( + new LoggerProvider(std::move(context))); + return provider; +} + std::unique_ptr LoggerProviderFactory::Create( std::unique_ptr context) { diff --git a/sdk/src/logs/multi_log_record_processor.cc b/sdk/src/logs/multi_log_record_processor.cc index 17826bcf43..8ebb004213 100644 --- a/sdk/src/logs/multi_log_record_processor.cc +++ b/sdk/src/logs/multi_log_record_processor.cc @@ -110,6 +110,18 @@ bool MultiLogRecordProcessor::HasEnabledFilter() const noexcept return false; } +bool MultiLogRecordProcessor::RecordableEnforcesLogRecordLimits() const noexcept +{ + for (const auto &processor : processors_) + { + if (processor != nullptr && processor->RecordableEnforcesLogRecordLimits()) + { + return true; + } + } + return false; +} + bool MultiLogRecordProcessor::ForceFlush(std::chrono::microseconds timeout) noexcept { return InternalForceFlush(timeout); diff --git a/sdk/src/logs/multi_recordable.cc b/sdk/src/logs/multi_recordable.cc index 1f4f1b8311..f63ea6ce41 100644 --- a/sdk/src/logs/multi_recordable.cc +++ b/sdk/src/logs/multi_recordable.cc @@ -184,6 +184,17 @@ void MultiRecordable::SetInstrumentationScope( } } +void MultiRecordable::SetLogRecordLimits(const LogRecordLimits &limits) noexcept +{ + for (auto &recordable : recordables_) + { + if (recordable.second) + { + recordable.second->SetLogRecordLimits(limits); + } + } +} + } // namespace logs } // namespace sdk diff --git a/sdk/src/logs/read_write_log_record.cc b/sdk/src/logs/read_write_log_record.cc index 160fa59848..ebf46a0778 100644 --- a/sdk/src/logs/read_write_log_record.cc +++ b/sdk/src/logs/read_write_log_record.cc @@ -6,12 +6,14 @@ #include #include #include +#include #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" #include "opentelemetry/sdk/logs/read_write_log_record.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_flags.h" @@ -155,8 +157,32 @@ void ReadWriteLogRecord::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { std::string safe_key(key); - opentelemetry::sdk::common::AttributeConverter converter; - attributes_map_[safe_key] = nostd::visit(converter, value); + + auto it = attributes_map_.find(safe_key); + if (it == attributes_map_.end()) + { + if (attributes_map_.size() >= limits_.attribute_count_limit) + { + ++dropped_attributes_count_; + return; + } + opentelemetry::sdk::common::AttributeConverter converter(limits_.attribute_value_length_limit); + attributes_map_.emplace(std::move(safe_key), nostd::visit(converter, value)); + return; + } + + opentelemetry::sdk::common::AttributeConverter converter(limits_.attribute_value_length_limit); + it->second = nostd::visit(converter, value); +} + +void ReadWriteLogRecord::SetLogRecordLimits(const LogRecordLimits &limits) noexcept +{ + limits_ = limits; +} + +uint32_t ReadWriteLogRecord::GetDroppedAttributesCount() const noexcept +{ + return dropped_attributes_count_; } const std::unordered_map & diff --git a/sdk/src/metrics/exemplar/reservoir.cc b/sdk/src/metrics/exemplar/reservoir.cc index f5fe4064b6..71e46ba298 100644 --- a/sdk/src/metrics/exemplar/reservoir.cc +++ b/sdk/src/metrics/exemplar/reservoir.cc @@ -4,9 +4,12 @@ #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW # include +# include # include "opentelemetry/nostd/shared_ptr.h" # include "opentelemetry/sdk/metrics/exemplar/aligned_histogram_bucket_exemplar_reservoir.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" +# include "opentelemetry/sdk/metrics/exemplar/filtered_exemplar_reservoir.h" # include "opentelemetry/sdk/metrics/exemplar/no_exemplar_reservoir.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir_cell.h" @@ -20,6 +23,14 @@ namespace sdk namespace metrics { +nostd::shared_ptr ExemplarReservoir::GetSimpleFilteredExemplarReservoir( + ExemplarFilterType filter_type, + nostd::shared_ptr reservoir) +{ + return nostd::shared_ptr{ + new FilteredExemplarReservoir{filter_type, std::move(reservoir)}}; +} + nostd::shared_ptr ExemplarReservoir::GetSimpleFixedSizeExemplarReservoir( size_t size, const std::shared_ptr &reservoir_cell_selector, diff --git a/sdk/src/metrics/instrument_metadata_validator.cc b/sdk/src/metrics/instrument_metadata_validator.cc index 482330a682..1fe193b0df 100644 --- a/sdk/src/metrics/instrument_metadata_validator.cc +++ b/sdk/src/metrics/instrument_metadata_validator.cc @@ -1,10 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include - -#include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/metrics/instrument_metadata_validator.h" +#include "opentelemetry/nostd/string_view.h" #include "opentelemetry/version.h" #if OPENTELEMETRY_HAVE_WORKING_REGEX @@ -20,9 +18,9 @@ namespace metrics { #if OPENTELEMETRY_HAVE_WORKING_REGEX // instrument-name = ALPHA 0*254 ("_" / "." / "-" / "/" / ALPHA / DIGIT) -const std::string kInstrumentNamePattern = "[a-zA-Z][-_./a-zA-Z0-9]{0,254}"; +constexpr const char *kInstrumentNamePattern = "[a-zA-Z][-_./a-zA-Z0-9]{0,254}"; // -const std::string kInstrumentUnitPattern = "[\x01-\x7F]{0,63}"; +constexpr const char *kInstrumentUnitPattern = "[\x01-\x7F]{0,63}"; // instrument-unit = It can have a maximum length of 63 ASCII chars #endif diff --git a/sdk/src/metrics/meter.cc b/sdk/src/metrics/meter.cc index 4a4c2f6b68..8acab9f3dd 100644 --- a/sdk/src/metrics/meter.cc +++ b/sdk/src/metrics/meter.cc @@ -545,9 +545,8 @@ std::unique_ptr Meter::RegisterSyncMetricStorage( sync_storage = std::shared_ptr(new SyncMetricStorage( view_instr_desc, view.GetAggregationType(), view.GetAttributesProcessor(), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - exemplar_filter_type, GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(), - view_instr_desc), + view_instr_desc, exemplar_filter_type), #endif view.GetAggregationConfig())); storage_registry_.insert({view_instr_desc, sync_storage}); @@ -618,9 +617,8 @@ std::unique_ptr Meter::RegisterAsyncMetricStorage( async_storage = std::shared_ptr(new AsyncMetricStorage( view_instr_desc, view.GetAggregationType(), #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - exemplar_filter_type, GetExemplarReservoir(view.GetAggregationType(), view.GetAggregationConfig(), - view_instr_desc), + view_instr_desc, exemplar_filter_type), #endif view.GetAggregationConfig())); storage_registry_.insert({view_instr_desc, async_storage}); diff --git a/sdk/src/metrics/state/sync_metric_storage.cc b/sdk/src/metrics/state/sync_metric_storage.cc index f4accbe124..80ec279d10 100644 --- a/sdk/src/metrics/state/sync_metric_storage.cc +++ b/sdk/src/metrics/state/sync_metric_storage.cc @@ -204,7 +204,7 @@ std::shared_ptr SyncMetricStorage::Bind( MetricAttributes key = ResolveCardinality(filtered); // If we ended up at overflow, dedupe against an existing overflow entry. - if (key == kOverflowAttributes) + if (key == GetOverflowAttributes()) { auto ov_it = bound_entries_.find(key); if (ov_it != bound_entries_.end()) diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index cc9d2fabfd..0d3e69d96b 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -25,7 +25,7 @@ add_library( samplers/ot_trace_state.cc samplers/composite_sampler.cc samplers/composite_sampler_factory.cc - samplers/composable_trace_id_ratio.cc + samplers/composable_probability.cc samplers/composable_parent_threshold.cc samplers/composable_rule_based.cc multi_recordable.cc diff --git a/sdk/src/trace/multi_recordable.cc b/sdk/src/trace/multi_recordable.cc index 5d74fd2403..37959a8e1f 100644 --- a/sdk/src/trace/multi_recordable.cc +++ b/sdk/src/trace/multi_recordable.cc @@ -69,6 +69,14 @@ void MultiRecordable::SetIdentity(const opentelemetry::trace::SpanContext &span_ } } +void MultiRecordable::SetSpanLimits(const SpanLimits &limits) noexcept +{ + for (auto &recordable : recordables_) + { + recordable.second->SetSpanLimits(limits); + } +} + void MultiRecordable::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { diff --git a/sdk/src/trace/samplers/composable_trace_id_ratio.cc b/sdk/src/trace/samplers/composable_probability.cc similarity index 77% rename from sdk/src/trace/samplers/composable_trace_id_ratio.cc rename to sdk/src/trace/samplers/composable_probability.cc index 3e86aedd1a..be341242c4 100644 --- a/sdk/src/trace/samplers/composable_trace_id_ratio.cc +++ b/sdk/src/trace/samplers/composable_probability.cc @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/sdk/trace/samplers/composable_trace_id_ratio.h" +#include "opentelemetry/sdk/trace/samplers/composable_probability.h" #include #include @@ -18,7 +18,7 @@ namespace sdk namespace trace { -ComposableTraceIdRatioBasedSampler::ComposableTraceIdRatioBasedSampler(double ratio) +ComposableProbabilitySampler::ComposableProbabilitySampler(double ratio) { if (ratio > 1.0) { @@ -33,10 +33,10 @@ ComposableTraceIdRatioBasedSampler::ComposableTraceIdRatioBasedSampler(double ra // this to behave like ComposableAlwaysOff: a non-probabilistic drop. has_threshold_ = threshold < kMaxThreshold; threshold_ = has_threshold_ ? threshold : 0; - description_ = "ComposableTraceIdRatioBasedSampler{" + std::to_string(ratio) + "}"; + description_ = "ComposableProbabilitySampler{" + std::to_string(ratio) + "}"; } -SamplingIntent ComposableTraceIdRatioBasedSampler::GetSamplingIntent( +SamplingIntent ComposableProbabilitySampler::GetSamplingIntent( const opentelemetry::trace::SpanContext & /* parent_context */, opentelemetry::trace::TraceId /* trace_id */, nostd::string_view /* name */, @@ -51,7 +51,7 @@ SamplingIntent ComposableTraceIdRatioBasedSampler::GetSamplingIntent( return intent; } -nostd::string_view ComposableTraceIdRatioBasedSampler::GetDescription() const noexcept +nostd::string_view ComposableProbabilitySampler::GetDescription() const noexcept { return description_; } diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index d764540875..1233e3361d 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -66,6 +66,7 @@ Span::Span(std::shared_ptr &&tracer, { return; } + recordable_->SetSpanLimits(tracer_->GetSpanLimits()); recordable_->SetName(name); recordable_->SetInstrumentationScope(tracer_->GetInstrumentationScope()); recordable_->SetIdentity(*span_context_, parent_span_context.IsValid() diff --git a/sdk/src/trace/span_data.cc b/sdk/src/trace/span_data.cc index 9969b44c30..e4d42fae4f 100644 --- a/sdk/src/trace/span_data.cc +++ b/sdk/src/trace/span_data.cc @@ -1,7 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +#include #include +#include +#include #include #include #include @@ -11,11 +14,14 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/common/timestamp.h" +#include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_flags.h" @@ -27,11 +33,74 @@ namespace sdk namespace trace { +namespace +{ + +bool SetAttributeImpl(opentelemetry::sdk::common::AttributeMap &attribute_map, + nostd::string_view key, + const opentelemetry::common::AttributeValue &value, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit) noexcept +{ + if (attribute_map.size() >= attribute_count_limit) + { + // The map is at the limit. Can only update existing keys. + auto it = attribute_map.find(std::string(key)); + if (it == attribute_map.end()) + { + return false; // The key is not in the map. Cannot add new attributes. + } + // try to convert the value and assign it to overwrite the existing value + opentelemetry::sdk::common::AttributeConverter converter(attribute_value_length_limit); + std::pair convert_result = + opentelemetry::sdk::common::VisitVariant(converter, value); + if (!convert_result.second) + { + return false; // There was an exception converting the value. Skip this attribute. + } + it->second = std::move(convert_result.first); + return true; + } + // The map is under the limit. Set the attribute (insert or assign). + return attribute_map.SetAttribute(key, value, attribute_value_length_limit); +} + +// Create the attribute map from KeyValueIterable attributes enforcing count and value-length +// limits. +opentelemetry::sdk::common::AttributeMap CreateAttributeMap( + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit, + std::uint32_t &dropped_count) +{ + opentelemetry::sdk::common::AttributeMap map; + map.reserve((std::min)(static_cast(attribute_count_limit), attributes.size())); + dropped_count = 0; + // Insert attributes until the count limit of unique keys is reached. + attributes.ForEachKeyValue( + [attribute_count_limit, attribute_value_length_limit, &map, &dropped_count]( + nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { + if (!SetAttributeImpl(map, key, value, attribute_count_limit, attribute_value_length_limit)) + { + ++dropped_count; + } + return true; + }); + return map; +} + +} // namespace + SpanDataEvent::SpanDataEvent(std::string name, opentelemetry::common::SystemTimestamp timestamp, - const opentelemetry::common::KeyValueIterable &attributes) - : name_(std::move(name)), timestamp_(timestamp), attribute_map_(attributes) -{} + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit) + : name_(std::move(name)), timestamp_(timestamp) +{ + attribute_map_ = CreateAttributeMap(attributes, attribute_count_limit, + attribute_value_length_limit, dropped_attributes_count_); +} const std::unordered_map & SpanDataEvent::GetAttributes() const noexcept @@ -40,9 +109,14 @@ SpanDataEvent::GetAttributes() const noexcept } SpanDataLink::SpanDataLink(opentelemetry::trace::SpanContext span_context, - const opentelemetry::common::KeyValueIterable &attributes) - : span_context_(std::move(span_context)), attribute_map_(attributes) -{} + const opentelemetry::common::KeyValueIterable &attributes, + std::uint32_t attribute_count_limit, + std::size_t attribute_value_length_limit) + : span_context_(std::move(span_context)) +{ + attribute_map_ = CreateAttributeMap(attributes, attribute_count_limit, + attribute_value_length_limit, dropped_attributes_count_); +} const std::unordered_map & SpanDataLink::GetAttributes() const noexcept @@ -93,22 +167,41 @@ void SpanData::SetIdentity(const opentelemetry::trace::SpanContext &span_context void SpanData::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { - attribute_map_.SetAttribute(key, value); + if (!SetAttributeImpl(attribute_map_, key, value, limits_.attribute_count_limit, + limits_.attribute_value_length_limit)) + { + ++dropped_attributes_count_; + } } void SpanData::AddEvent(nostd::string_view name, opentelemetry::common::SystemTimestamp timestamp, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataEvent event(std::string(name), timestamp, attributes); - events_.push_back(event); + if (events_.size() >= limits_.event_count_limit) + { + ++dropped_events_count_; + return; + } + events_.emplace_back(std::string(name), timestamp, attributes, + limits_.event_attribute_count_limit, limits_.attribute_value_length_limit); } void SpanData::AddLink(const opentelemetry::trace::SpanContext &span_context, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataLink link(span_context, attributes); - links_.push_back(link); + if (links_.size() >= limits_.link_count_limit) + { + ++dropped_links_count_; + return; + } + links_.emplace_back(span_context, attributes, limits_.link_attribute_count_limit, + limits_.attribute_value_length_limit); +} + +void SpanData::SetSpanLimits(const SpanLimits &limits) noexcept +{ + limits_ = limits; } void SpanData::SetStatus(opentelemetry::trace::StatusCode code, diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index 298873db31..96b2d81fc9 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -37,9 +37,6 @@ namespace sdk { namespace trace { -const std::shared_ptr Tracer::kNoopTracer = - std::make_shared(); - Tracer::Tracer(std::shared_ptr context, std::unique_ptr instrumentation_scope) noexcept : instrumentation_scope_{std::move(instrumentation_scope)}, @@ -58,6 +55,8 @@ nostd::shared_ptr Tracer::StartSpan( // Check if the tracer is enabled using the API Tracer::Enabled() accessor if available. if (!Enabled()) { + static const std::shared_ptr kNoopTracer = + std::make_shared(); return kNoopTracer->StartSpan(name, attributes, links, options); } @@ -76,7 +75,7 @@ nostd::shared_ptr Tracer::StartSpan( else if (const context::Context *context = nostd::get_if(&options.parent)) { // fetch span context from parent span stored in the context - auto parent_span_context = opentelemetry::trace::GetSpan(*context)->GetContext(); + auto parent_span_context = opentelemetry::trace::GetSpanContext(*context); if (parent_span_context.IsValid()) { parent_context = parent_span_context; diff --git a/sdk/src/trace/tracer_context.cc b/sdk/src/trace/tracer_context.cc index adb3cfe538..43bdabc93b 100644 --- a/sdk/src/trace/tracer_context.cc +++ b/sdk/src/trace/tracer_context.cc @@ -13,6 +13,7 @@ #include "opentelemetry/sdk/trace/multi_span_processor.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/version.h" @@ -24,17 +25,19 @@ namespace trace { namespace resource = opentelemetry::sdk::resource; -TracerContext::TracerContext(std::vector> &&processors, - const resource::Resource &resource, - std::unique_ptr sampler, - std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept +TracerContext::TracerContext( + std::vector> &&processors, + const resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept : resource_(resource), sampler_(std::move(sampler)), id_generator_(std::move(id_generator)), processor_(std::unique_ptr(new MultiSpanProcessor(std::move(processors)))), - tracer_configurator_(std::move(tracer_configurator)) + tracer_configurator_(std::move(tracer_configurator)), + span_limits_(span_limits) {} Sampler &TracerContext::GetSampler() const noexcept @@ -58,9 +61,13 @@ opentelemetry::sdk::trace::IdGenerator &TracerContext::GetIdGenerator() const no return *id_generator_; } -void TracerContext::AddProcessor(std::unique_ptr processor) noexcept +const SpanLimits &TracerContext::GetSpanLimits() const noexcept { + return span_limits_; +} +void TracerContext::AddProcessor(std::unique_ptr processor) noexcept +{ auto multi_processor = static_cast(processor_.get()); multi_processor->AddProcessor(std::move(processor)); } diff --git a/sdk/src/trace/tracer_provider.cc b/sdk/src/trace/tracer_provider.cc index 7f71a1ec47..47d3bb98b9 100644 --- a/sdk/src/trace/tracer_provider.cc +++ b/sdk/src/trace/tracer_provider.cc @@ -16,6 +16,7 @@ #include "opentelemetry/sdk/trace/id_generator.h" #include "opentelemetry/sdk/trace/processor.h" #include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -41,14 +42,14 @@ TracerProvider::TracerProvider( const resource::Resource &resource, std::unique_ptr sampler, std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept { std::vector> processors; processors.push_back(std::move(processor)); - context_ = - std::make_shared(std::move(processors), resource, std::move(sampler), - std::move(id_generator), std::move(tracer_configurator)); + context_ = std::make_shared(std::move(processors), resource, std::move(sampler), + std::move(id_generator), + std::move(tracer_configurator), span_limits); } TracerProvider::TracerProvider( @@ -56,13 +57,14 @@ TracerProvider::TracerProvider( const resource::Resource &resource, std::unique_ptr sampler, std::unique_ptr id_generator, - std::unique_ptr> - tracer_configurator) noexcept + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) noexcept : context_(std::make_shared(std::move(processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))) + std::move(tracer_configurator), + span_limits)) {} TracerProvider::~TracerProvider() @@ -163,6 +165,11 @@ const resource::Resource &TracerProvider::GetResource() const noexcept return context_->GetResource(); } +const opentelemetry::sdk::trace::SpanLimits &TracerProvider::GetSpanLimits() const noexcept +{ + return context_->GetSpanLimits(); +} + bool TracerProvider::Shutdown(std::chrono::microseconds timeout) noexcept { return context_->Shutdown(timeout); diff --git a/sdk/src/trace/tracer_provider_factory.cc b/sdk/src/trace/tracer_provider_factory.cc index 49d2aac12d..06f792c659 100644 --- a/sdk/src/trace/tracer_provider_factory.cc +++ b/sdk/src/trace/tracer_provider_factory.cc @@ -12,6 +12,7 @@ #include "opentelemetry/sdk/trace/random_id_generator_factory.h" #include "opentelemetry/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" #include "opentelemetry/sdk/trace/tracer_provider.h" @@ -68,11 +69,23 @@ std::unique_ptr TracerProviderFactory std::unique_ptr sampler, std::unique_ptr id_generator, std::unique_ptr> tracer_configurator) +{ + return Create(std::move(processor), resource, std::move(sampler), std::move(id_generator), + std::move(tracer_configurator), SpanLimits::NoLimits()); +} + +std::unique_ptr TracerProviderFactory::Create( + std::unique_ptr processor, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) { std::unique_ptr provider( new opentelemetry::sdk::trace::TracerProvider(std::move(processor), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))); + std::move(tracer_configurator), span_limits)); return provider; } @@ -120,11 +133,23 @@ std::unique_ptr TracerProviderFactory std::unique_ptr sampler, std::unique_ptr id_generator, std::unique_ptr> tracer_configurator) +{ + return Create(std::move(processors), resource, std::move(sampler), std::move(id_generator), + std::move(tracer_configurator), SpanLimits::NoLimits()); +} + +std::unique_ptr TracerProviderFactory::Create( + std::vector> &&processors, + const opentelemetry::sdk::resource::Resource &resource, + std::unique_ptr sampler, + std::unique_ptr id_generator, + std::unique_ptr> tracer_configurator, + SpanLimits span_limits) { std::unique_ptr provider( new opentelemetry::sdk::trace::TracerProvider(std::move(processors), resource, std::move(sampler), std::move(id_generator), - std::move(tracer_configurator))); + std::move(tracer_configurator), span_limits)); return provider; } diff --git a/sdk/src/version/version.cc b/sdk/src/version/version.cc index f7c6730f4f..5230bbdd6b 100644 --- a/sdk/src/version/version.cc +++ b/sdk/src/version/version.cc @@ -12,12 +12,12 @@ namespace sdk namespace version { const int major_version = 1; -const int minor_version = 28; +const int minor_version = 29; const int patch_version = 0; const char *pre_release = "dev"; const char *build_metadata = "none"; -const char *short_version = "1.28.0"; -const char *full_version = "1.28.0-dev"; +const char *short_version = "1.29.0"; +const char *full_version = "1.29.0-dev"; /** * Release date. * For published releases: YYYY-MM-DD diff --git a/sdk/test/common/attribute_utils_test.cc b/sdk/test/common/attribute_utils_test.cc index 466179594c..848ce86bc4 100644 --- a/sdk/test/common/attribute_utils_test.cc +++ b/sdk/test/common/attribute_utils_test.cc @@ -13,6 +13,7 @@ #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/utility.h" #include "opentelemetry/nostd/variant.h" @@ -193,3 +194,109 @@ TEST(AttributeMapTest, EqualTo) EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_size)); EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_all)); } + +TEST(AttributeMapTest, SetAttributeTruncation) +{ + opentelemetry::sdk::common::AttributeMap attribute_map; + opentelemetry::nostd::string_view value("abcdefghijk"); + attribute_map.SetAttribute("key", value, 5); + auto stored_value = attribute_map.GetAttributes().at("key"); + auto string_value = opentelemetry::nostd::get(stored_value); + EXPECT_EQ(string_value, "abcde"); +} + +TEST(OrderedAttributeMapTest, SetAttributeTruncation) +{ + opentelemetry::sdk::common::OrderedAttributeMap attribute_map; + opentelemetry::nostd::string_view value("abcdefghijk"); + attribute_map.SetAttribute("key", value, 5); + auto stored_value = attribute_map.GetAttributes().at("key"); + auto string_value = opentelemetry::nostd::get(stored_value); + EXPECT_EQ(string_value, "abcde"); +} + +// --------------------------------------------------------------------------- +// AttributeConverter truncation +// --------------------------------------------------------------------------- +// +// "h\xC3\xA9llo" is "héllo" in UTF-8 (6 bytes). 'é' occupies bytes 1-2 as a +// 2-byte sequence (0xC3 0xA9). String alternatives are truncated at a UTF-8 +// code-point boundary; the raw bytes alternative is cut at the exact byte. + +TEST(AttributeConverterTruncation, DefaultConverterDoesNotTruncate) +{ + opentelemetry::sdk::common::AttributeConverter converter; + auto v = converter(opentelemetry::nostd::string_view("0123456789")); + EXPECT_EQ(opentelemetry::nostd::get(v), "0123456789"); +} + +TEST(AttributeConverterTruncation, AsciiTruncatesAtByteCount) +{ + opentelemetry::sdk::common::AttributeConverter converter(5); + auto v = converter(opentelemetry::nostd::string_view("0123456789")); + EXPECT_EQ(opentelemetry::nostd::get(v), "01234"); +} + +TEST(AttributeConverterTruncation, KeepsCompleteMultiByteSequenceWithinBudget) +{ + // 'h' (1 byte) + 'é' (2 bytes) = 3 bytes exactly, still valid UTF-8. + opentelemetry::sdk::common::AttributeConverter converter(3); + auto v = converter(opentelemetry::nostd::string_view("h\xC3\xA9llo")); + EXPECT_EQ(opentelemetry::nostd::get(v), std::string("h\xC3\xA9")); +} + +TEST(AttributeConverterTruncation, DropsPartialMultiByteSequenceAtBoundary) +{ + // limit=2: 'h' fits at byte 0; 'é' would span bytes 1-2, ending past the + // limit, so the kept prefix stops at byte 1. + opentelemetry::sdk::common::AttributeConverter converter(2); + auto v = converter(opentelemetry::nostd::string_view("h\xC3\xA9llo")); + EXPECT_EQ(opentelemetry::nostd::get(v), "h"); +} + +TEST(AttributeConverterTruncation, StdStringIsTruncatedUtf8Safe) +{ + opentelemetry::sdk::common::AttributeConverter converter(2); + auto v = converter(std::string("h\xC3\xA9llo")); + EXPECT_EQ(opentelemetry::nostd::get(v), "h"); +} + +TEST(AttributeConverterTruncation, ConstCharPtrIsTruncatedUtf8Safe) +{ + opentelemetry::sdk::common::AttributeConverter converter(2); + const char *value = "h\xC3\xA9llo"; + auto v = converter(value); + EXPECT_EQ(opentelemetry::nostd::get(v), "h"); +} + +TEST(AttributeConverterTruncation, BytesAreCutAtRawByteBoundary) +{ + // Raw bytes have no encoding, so truncation is a plain byte cut even when the + // leading bytes look like a multi-byte UTF-8 sequence. + const uint8_t bytes[] = {0xC3, 0xA9, 0x01, 0x02}; + opentelemetry::sdk::common::AttributeConverter converter(1); + auto v = converter(opentelemetry::nostd::span(bytes, 4)); + const auto &stored = opentelemetry::nostd::get>(v); + ASSERT_EQ(stored.size(), 1u); + EXPECT_EQ(stored[0], 0xC3); +} + +TEST(AttributeConverterTruncation, StringArrayTruncatedPerElementUtf8Safe) +{ + opentelemetry::nostd::string_view values[] = {opentelemetry::nostd::string_view("h\xC3\xA9llo"), + opentelemetry::nostd::string_view("ab")}; + opentelemetry::sdk::common::AttributeConverter converter(2); + auto v = + converter(opentelemetry::nostd::span(values, 2)); + const auto &stored = opentelemetry::nostd::get>(v); + ASSERT_EQ(stored.size(), 2u); + EXPECT_EQ(stored[0], "h"); + EXPECT_EQ(stored[1], "ab"); +} + +TEST(AttributeConverterTruncation, ShortStringUnderLimitUnchanged) +{ + opentelemetry::sdk::common::AttributeConverter converter(100); + auto v = converter(opentelemetry::nostd::string_view("short")); + EXPECT_EQ(opentelemetry::nostd::get(v), "short"); +} diff --git a/sdk/test/common/circular_buffer_test.cc b/sdk/test/common/circular_buffer_test.cc index 6dc1b627cc..7617683fbb 100644 --- a/sdk/test/common/circular_buffer_test.cc +++ b/sdk/test/common/circular_buffer_test.cc @@ -23,7 +23,11 @@ using opentelemetry::sdk::common::AtomicUniquePtr; using opentelemetry::sdk::common::CircularBuffer; using opentelemetry::sdk::common::CircularBufferRange; -static thread_local std::mt19937 RandomNumberGenerator{std::random_device{}()}; +static std::mt19937 &RandomNumberGenerator() +{ + static thread_local std::mt19937 generator{std::random_device{}()}; + return generator; +} static void GenerateRandomNumbers(CircularBuffer &buffer, std::vector &numbers, @@ -31,7 +35,7 @@ static void GenerateRandomNumbers(CircularBuffer &buffer, { for (int i = 0; i < n; ++i) { - auto value = static_cast(RandomNumberGenerator()); + auto value = static_cast(RandomNumberGenerator()()); std::unique_ptr x{new uint32_t{value}}; if (buffer.Add(x)) { @@ -73,7 +77,8 @@ static void RunNumberConsumer(CircularBuffer &buffer, { return; } - auto n = std::uniform_int_distribution{0, buffer.Peek().size()}(RandomNumberGenerator); + auto n = + std::uniform_int_distribution{0, buffer.Peek().size()}(RandomNumberGenerator()); buffer.Consume(n, [&](CircularBufferRange> range) noexcept { assert(range.size() == n); range.ForEach([&](AtomicUniquePtr &ptr) noexcept { diff --git a/sdk/test/common/global_log_handle_test.cc b/sdk/test/common/global_log_handle_test.cc index 59d2c85fe4..6cbedd1392 100644 --- a/sdk/test/common/global_log_handle_test.cc +++ b/sdk/test/common/global_log_handle_test.cc @@ -10,6 +10,9 @@ #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/common/global_log_handler.h" +namespace +{ + class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler { public: @@ -76,3 +79,5 @@ TEST(GlobalLogHandleTest, CustomLogHandler) opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler(backup_log_handle); opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel(backup_log_level); } + +} // namespace diff --git a/sdk/test/configuration/BUILD b/sdk/test/configuration/BUILD index f9cd843105..f16cc267cc 100644 --- a/sdk/test/configuration/BUILD +++ b/sdk/test/configuration/BUILD @@ -3,6 +3,26 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test") +cc_test( + name = "sdk_builder_test", + srcs = [ + "sdk_builder_test.cc", + ], + tags = [ + "test", + "yaml", + ], + deps = [ + "//sdk:headers", + "//sdk/src/configuration", + "//sdk/src/logs", + "//sdk/src/metrics", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "yaml_logs_test", srcs = [ diff --git a/sdk/test/configuration/CMakeLists.txt b/sdk/test/configuration/CMakeLists.txt index 38ae21b19d..5385ddf0de 100644 --- a/sdk/test/configuration/CMakeLists.txt +++ b/sdk/test/configuration/CMakeLists.txt @@ -3,6 +3,7 @@ foreach( testname + sdk_builder_test yaml_logs_test yaml_metrics_test yaml_propagator_test diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc new file mode 100644 index 0000000000..22d07a8fc0 --- /dev/null +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "opentelemetry/logs/severity.h" +#include "opentelemetry/sdk/configuration/logger_config_configuration.h" +#include "opentelemetry/sdk/configuration/logger_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/logger_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/sdk_builder.h" +#include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/span_limits_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/logger_config.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/span_limits.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" + +using opentelemetry::sdk::configuration::Registry; +using opentelemetry::sdk::configuration::SdkBuilder; +using opentelemetry::sdk::configuration::SpanLimitsConfiguration; +using opentelemetry::sdk::configuration::TracerProviderConfiguration; +namespace config_sdk = opentelemetry::sdk::configuration; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace logs_sdk = opentelemetry::sdk::logs; + +TEST(SdkBuilder, SpanLimitsDefaults) +{ + auto model = std::make_unique(); + model->limits = nullptr; + + SdkBuilder builder(std::make_shared()); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto provider = builder.CreateTracerProvider(model, resource); + ASSERT_NE(provider, nullptr); + + const auto limits = provider->GetSpanLimits(); + const auto default_limits = opentelemetry::sdk::trace::SpanLimits{}; + + EXPECT_EQ(limits.attribute_count_limit, default_limits.attribute_count_limit); + EXPECT_EQ(limits.event_count_limit, default_limits.event_count_limit); + EXPECT_EQ(limits.link_count_limit, default_limits.link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, default_limits.event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, default_limits.link_attribute_count_limit); + EXPECT_EQ(limits.attribute_value_length_limit, default_limits.attribute_value_length_limit); +} + +TEST(SdkBuilder, SpanLimitsConfiguration) +{ + auto model = std::make_unique(); + model->limits = std::make_unique(); + model->limits->attribute_value_length_limit = 1111; + model->limits->attribute_count_limit = 2222; + model->limits->event_count_limit = 3333; + model->limits->link_count_limit = 4444; + model->limits->event_attribute_count_limit = 5555; + model->limits->link_attribute_count_limit = 6666; + + SdkBuilder builder(std::make_shared()); + auto resource = opentelemetry::sdk::resource::Resource::Create({}); + auto provider = builder.CreateTracerProvider(model, resource); + ASSERT_NE(provider, nullptr); + + auto limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.attribute_value_length_limit, model->limits->attribute_value_length_limit); + EXPECT_EQ(limits.attribute_count_limit, model->limits->attribute_count_limit); + EXPECT_EQ(limits.event_count_limit, model->limits->event_count_limit); + EXPECT_EQ(limits.link_count_limit, model->limits->link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, model->limits->event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, model->limits->link_attribute_count_limit); +} + +TEST(SdkBuilder, CreateLoggerConfigurator) +{ + config_sdk::LoggerConfigConfiguration default_config; + default_config.enabled = true; + default_config.minimum_severity = config_sdk::SeverityNumber::warn; + default_config.trace_based = false; + + config_sdk::LoggerMatcherAndConfigConfiguration matcher1; + matcher1.name = "enabled_minsev_error_not_trace_based"; + matcher1.config.enabled = true; + matcher1.config.minimum_severity = config_sdk::SeverityNumber::error3; + matcher1.config.trace_based = false; + + config_sdk::LoggerMatcherAndConfigConfiguration matcher2; + matcher2.name = "disabled_minsev_info_trace_based"; + matcher2.config.enabled = false; + matcher2.config.minimum_severity = config_sdk::SeverityNumber::debug; + matcher2.config.trace_based = true; + + auto model = std::make_unique(); + model->default_config = default_config; + model->loggers.push_back(matcher1); + model->loggers.push_back(matcher2); + + config_sdk::SdkBuilder builder(std::make_shared()); + + auto logger_configurator = builder.CreateLoggerConfigurator(model); + ASSERT_NE(logger_configurator, nullptr); + + auto default_scope = scope_sdk::InstrumentationScope::Create("default_scope"); + logs_sdk::LoggerConfig sdk_logger_config_default = + logger_configurator->ComputeConfig(*default_scope); + + auto scope_1 = scope_sdk::InstrumentationScope::Create(matcher1.name); + logs_sdk::LoggerConfig sdk_logger_config_1 = logger_configurator->ComputeConfig(*scope_1); + + auto scope_2 = scope_sdk::InstrumentationScope::Create(matcher2.name); + logs_sdk::LoggerConfig sdk_logger_config_2 = logger_configurator->ComputeConfig(*scope_2); + + EXPECT_TRUE(sdk_logger_config_default.IsEnabled()); + EXPECT_EQ(sdk_logger_config_default.GetMinimumSeverity(), opentelemetry::logs::Severity::kWarn); + EXPECT_FALSE(sdk_logger_config_default.IsTraceBased()); + + EXPECT_TRUE(sdk_logger_config_1.IsEnabled()); + EXPECT_EQ(sdk_logger_config_1.GetMinimumSeverity(), opentelemetry::logs::Severity::kError3); + EXPECT_FALSE(sdk_logger_config_1.IsTraceBased()); + + EXPECT_FALSE(sdk_logger_config_2.IsEnabled()); + EXPECT_EQ(sdk_logger_config_2.GetMinimumSeverity(), opentelemetry::logs::Severity::kDebug); + EXPECT_TRUE(sdk_logger_config_2.IsTraceBased()); +} diff --git a/sdk/test/configuration/yaml_trace_test.cc b/sdk/test/configuration/yaml_trace_test.cc index db93d08c69..f65868e507 100644 --- a/sdk/test/configuration/yaml_trace_test.cc +++ b/sdk/test/configuration/yaml_trace_test.cc @@ -593,6 +593,79 @@ file_format: "1.0-trace" ASSERT_EQ(config->tracer_provider->limits->link_attribute_count_limit, 6666); } +TEST(YamlTrace, limits_with_invalid_values) +{ + std::string invalid_attribute_count_yaml = R"( +file_format: "1.0-trace" +tracer_provider: + processors: + - simple: + exporter: + console: + limits: + attribute_count_limit: 4294967296 +)"; + + std::string invalid_event_count_yaml = R"( +file_format: "1.0-trace" +tracer_provider: + processors: + - simple: + exporter: + console: + limits: + event_count_limit: 4294967296 +)"; + + std::string invalid_link_count_yaml = R"( +file_format: "1.0-trace" +tracer_provider: + processors: + - simple: + exporter: + console: + limits: + link_count_limit: 4294967296 +)"; + + std::string invalid_event_attribute_count_yaml = R"( +file_format: "1.0-trace" +tracer_provider: + processors: + - simple: + exporter: + console: + limits: + event_attribute_count_limit: 4294967296 +)"; + + std::string invalid_link_attribute_count_yaml = R"( +file_format: "1.0-trace" +tracer_provider: + processors: + - simple: + exporter: + console: + limits: + link_attribute_count_limit: 4294967296 +)"; + + auto config = DoParse(invalid_attribute_count_yaml); + EXPECT_TRUE(config == nullptr); + + config = DoParse(invalid_event_count_yaml); + EXPECT_TRUE(config == nullptr); + + config = DoParse(invalid_link_count_yaml); + EXPECT_TRUE(config == nullptr); + + config = DoParse(invalid_event_attribute_count_yaml); + EXPECT_TRUE(config == nullptr); + + config = DoParse(invalid_link_attribute_count_yaml); + EXPECT_TRUE(config == nullptr); +} + TEST(YamlTrace, no_sampler) { std::string yaml = R"( diff --git a/sdk/test/logs/BUILD b/sdk/test/logs/BUILD index ccf035c163..8e2198c318 100644 --- a/sdk/test/logs/BUILD +++ b/sdk/test/logs/BUILD @@ -81,6 +81,22 @@ cc_test( ], ) +cc_test( + name = "log_record_limits_test", + srcs = [ + "log_record_limits_test.cc", + ], + tags = [ + "logs", + "test", + ], + deps = [ + "//api", + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "batch_log_record_processor_test", srcs = [ diff --git a/sdk/test/logs/CMakeLists.txt b/sdk/test/logs/CMakeLists.txt index 8ef79df68f..1fcbc3bb27 100644 --- a/sdk/test/logs/CMakeLists.txt +++ b/sdk/test/logs/CMakeLists.txt @@ -7,6 +7,7 @@ foreach( logger_provider_sdk_test logger_sdk_test log_record_test + log_record_limits_test simple_log_record_processor_test batch_log_record_processor_test logger_config_test) diff --git a/sdk/test/logs/batch_log_record_processor_test.cc b/sdk/test/logs/batch_log_record_processor_test.cc index 7bbbab93a1..ad868bae6f 100644 --- a/sdk/test/logs/batch_log_record_processor_test.cc +++ b/sdk/test/logs/batch_log_record_processor_test.cc @@ -36,6 +36,9 @@ using namespace opentelemetry::sdk::common; namespace nostd = opentelemetry::nostd; +namespace +{ + class MockLogRecordable final : public opentelemetry::sdk::logs::Recordable { public: @@ -474,3 +477,5 @@ TEST_F(BatchLogRecordProcessorTest, TestOptionsReadFromMultipleEnvVars) unsetenv("OTEL_BLRP_EXPORT_TIMEOUT"); unsetenv("OTEL_BLRP_MAX_EXPORT_BATCH_SIZE"); } + +} // namespace diff --git a/sdk/test/logs/log_record_limits_test.cc b/sdk/test/logs/log_record_limits_test.cc new file mode 100644 index 0000000000..cef7958b27 --- /dev/null +++ b/sdk/test/logs/log_record_limits_test.cc @@ -0,0 +1,348 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/common/timestamp.h" +#include "opentelemetry/logs/log_record.h" +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/log_record_limits.h" +#include "opentelemetry/sdk/logs/logger_config.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/logger_provider_factory.h" +#include "opentelemetry/sdk/logs/processor.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/logs/recordable.h" +#include "opentelemetry/sdk/resource/resource.h" + +using opentelemetry::sdk::logs::LogRecordLimits; +using opentelemetry::sdk::logs::ReadWriteLogRecord; +namespace nostd = opentelemetry::nostd; + +TEST(LogRecordLimits, DefaultRecordAppliesNoLimitUntilConfigured) +{ + // A ReadWriteLogRecord that never receives an explicit SetLogRecordLimits() + // call applies no cap. The spec-default count limit is injected by the + // LoggerProvider wiring, not by a bare recordable, so a recordable used + // outside a provider keeps every attribute (matching pre-limits behavior). + ReadWriteLogRecord record; + for (int i = 0; i < 200; ++i) + { + record.SetAttribute("attr_" + std::to_string(i), static_cast(i)); + } + ASSERT_EQ(record.GetAttributes().size(), 200u); + ASSERT_EQ(record.GetDroppedAttributesCount(), 0u); +} + +TEST(LogRecordLimits, CountLimitDropsExcessAttributes) +{ + LogRecordLimits limits; + limits.attribute_count_limit = 3; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("k1", static_cast(1)); + record.SetAttribute("k2", static_cast(2)); + record.SetAttribute("k3", static_cast(3)); + record.SetAttribute("k4", static_cast(4)); + record.SetAttribute("k5", static_cast(5)); + + ASSERT_EQ(record.GetAttributes().size(), 3u); + ASSERT_EQ(record.GetDroppedAttributesCount(), 2u); + ASSERT_TRUE(record.GetAttributes().count("k1") == 1); + ASSERT_TRUE(record.GetAttributes().count("k2") == 1); + ASSERT_TRUE(record.GetAttributes().count("k3") == 1); + ASSERT_TRUE(record.GetAttributes().count("k4") == 0); + ASSERT_TRUE(record.GetAttributes().count("k5") == 0); +} + +TEST(LogRecordLimits, CountLimitReplaceExistingKeyDoesNotDrop) +{ + LogRecordLimits limits; + limits.attribute_count_limit = 2; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("k1", static_cast(1)); + record.SetAttribute("k2", static_cast(2)); + // Replace existing key while at limit; must not increment dropped count. + record.SetAttribute("k1", static_cast(99)); + + ASSERT_EQ(record.GetAttributes().size(), 2u); + ASSERT_EQ(record.GetDroppedAttributesCount(), 0u); + ASSERT_EQ(nostd::get(record.GetAttributes().at("k1")), 99); +} + +TEST(LogRecordLimits, LengthLimitTruncatesString) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 5; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("k", nostd::string_view("0123456789")); + + ASSERT_EQ(record.GetDroppedAttributesCount(), 0u); + ASSERT_EQ(nostd::get(record.GetAttributes().at("k")), "01234"); +} + +TEST(LogRecordLimits, LengthLimitTruncatesConstCharPtr) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 5; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + // A const char* attribute value goes through the AttributeConverter + // const char* overload (which delegates to the string_view path) and must + // be truncated the same way. + const char *value = "0123456789"; + record.SetAttribute("k", value); + + ASSERT_EQ(record.GetDroppedAttributesCount(), 0u); + ASSERT_EQ(nostd::get(record.GetAttributes().at("k")), "01234"); +} + +TEST(LogRecordLimits, LengthLimitTruncatesEachStringInArray) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 3; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + nostd::string_view values[] = {nostd::string_view("aaaaaa"), nostd::string_view("bb"), + nostd::string_view("cccc")}; + record.SetAttribute("k", nostd::span(values, 3)); + + const auto &stored = nostd::get>(record.GetAttributes().at("k")); + ASSERT_EQ(stored.size(), 3u); + ASSERT_EQ(stored[0], "aaa"); + ASSERT_EQ(stored[1], "bb"); + ASSERT_EQ(stored[2], "ccc"); +} + +TEST(LogRecordLimits, LengthLimitKeepsUtf8Boundary) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 2; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + // "h\xC3\xA9llo" is "héllo"; 'é' occupies bytes 1-2. A budget of 2 must drop + // the partial multi-byte sequence and keep just "h", not a split byte that + // would leave the stored value as invalid UTF-8. + record.SetAttribute("k", nostd::string_view("h\xC3\xA9llo")); + + ASSERT_EQ(nostd::get(record.GetAttributes().at("k")), "h"); +} + +TEST(LogRecordLimits, LengthLimitDoesNotAffectNonStringTypes) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 1; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("i", static_cast(1234567890)); + record.SetAttribute("d", 3.14); + record.SetAttribute("b", true); + + ASSERT_EQ(nostd::get(record.GetAttributes().at("i")), 1234567890); + ASSERT_DOUBLE_EQ(nostd::get(record.GetAttributes().at("d")), 3.14); + ASSERT_EQ(nostd::get(record.GetAttributes().at("b")), true); +} + +TEST(LogRecordLimits, LengthLimitShorterThanValueLeavesValueAlone) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 100; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("k", nostd::string_view("short")); + + ASSERT_EQ(nostd::get(record.GetAttributes().at("k")), "short"); +} + +TEST(LogRecordLimits, CountAndLengthCombined) +{ + LogRecordLimits limits; + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 4; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + record.SetAttribute("k1", nostd::string_view("abcdefgh")); + record.SetAttribute("k2", nostd::string_view("12345678")); + record.SetAttribute("k3", nostd::string_view("ignored")); + + ASSERT_EQ(record.GetAttributes().size(), 2u); + ASSERT_EQ(record.GetDroppedAttributesCount(), 1u); + ASSERT_EQ(nostd::get(record.GetAttributes().at("k1")), "abcd"); + ASSERT_EQ(nostd::get(record.GetAttributes().at("k2")), "1234"); +} + +TEST(LogRecordLimits, LengthLimitTruncatesBytesAttribute) +{ + LogRecordLimits limits; + limits.attribute_value_length_limit = 3; + ReadWriteLogRecord record; + record.SetLogRecordLimits(limits); + + const uint8_t bytes_in[] = {0x01, 0x02, 0x03, 0x04, 0x05}; + record.SetAttribute("k", nostd::span(bytes_in, 5)); + + const auto &stored = nostd::get>(record.GetAttributes().at("k")); + ASSERT_EQ(stored.size(), 3u); + ASSERT_EQ(stored[0], 0x01); + ASSERT_EQ(stored[1], 0x02); + ASSERT_EQ(stored[2], 0x03); +} + +namespace +{ + +// Minimal Recordable that records whether SetLogRecordLimits was called and +// what limits arrived. Stubs every other Recordable method. +class TrackingRecordable final : public opentelemetry::sdk::logs::Recordable +{ +public: + int set_limits_call_count = 0; + std::size_t captured_count_limit = 0; + std::size_t captured_value_length_limit = 0; + + void SetLogRecordLimits(const LogRecordLimits &limits) noexcept override + { + ++set_limits_call_count; + captured_count_limit = limits.attribute_count_limit; + captured_value_length_limit = limits.attribute_value_length_limit; + } + + void SetResource(const opentelemetry::sdk::resource::Resource &) noexcept override {} + void SetInstrumentationScope( + const opentelemetry::sdk::instrumentationscope::InstrumentationScope &) noexcept override + {} + void SetTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {} + void SetObservedTimestamp(opentelemetry::common::SystemTimestamp) noexcept override {} + void SetSeverity(opentelemetry::logs::Severity) noexcept override {} + void SetBody(const opentelemetry::common::AttributeValue &) noexcept override {} + void SetEventId(int64_t, nostd::string_view) noexcept override {} + void SetTraceId(const opentelemetry::trace::TraceId &) noexcept override {} + void SetSpanId(const opentelemetry::trace::SpanId &) noexcept override {} + void SetTraceFlags(const opentelemetry::trace::TraceFlags &) noexcept override {} + void SetAttribute(nostd::string_view, + const opentelemetry::common::AttributeValue &) noexcept override + {} +}; + +// Processor that hands out TrackingRecordable instances and exposes a pointer +// to the most recently produced one for inspection. +class TrackingProcessor final : public opentelemetry::sdk::logs::LogRecordProcessor +{ +public: + explicit TrackingProcessor(bool enforces_limits = true) : enforces_limits_(enforces_limits) {} + + TrackingRecordable *last = nullptr; + + std::unique_ptr MakeRecordable() noexcept override + { + auto recordable = std::unique_ptr(new TrackingRecordable()); + last = recordable.get(); + return recordable; + } + + void OnEmit(std::unique_ptr &&record) noexcept override + { + auto dropped = std::move(record); + } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + + bool RecordableEnforcesLogRecordLimits() const noexcept override { return enforces_limits_; } + +private: + bool enforces_limits_; +}; + +} // namespace + +TEST(LogRecordLimitsWiring, LoggerForwardsContextLimitsToRecordable) +{ + using opentelemetry::sdk::instrumentationscope::ScopeConfigurator; + using opentelemetry::sdk::logs::LoggerConfig; + using opentelemetry::sdk::logs::LoggerProviderFactory; + using opentelemetry::sdk::logs::LogRecordProcessor; + + LogRecordLimits limits; + limits.attribute_count_limit = 7; + limits.attribute_value_length_limit = 42; + + std::vector> processors; + // Processor advertises that its recordable enforces limits (the default), so + // the Logger pushes the context limits onto each record it creates. + auto tracking_processor = std::unique_ptr(new TrackingProcessor(true)); + auto *tracker_ptr = tracking_processor.get(); + processors.push_back(std::move(tracking_processor)); + + auto configurator = std::make_unique>( + ScopeConfigurator::Builder(LoggerConfig::Default()).Build()); + + auto provider = LoggerProviderFactory::Create(std::move(processors), + opentelemetry::sdk::resource::Resource::Create({}), + std::move(configurator), limits); + + auto logger = provider->GetLogger("wiring_test", ""); + auto record = logger->CreateLogRecord(); + + ASSERT_NE(tracker_ptr->last, nullptr); + EXPECT_EQ(tracker_ptr->last->set_limits_call_count, 1); + EXPECT_EQ(tracker_ptr->last->captured_count_limit, 7u); + EXPECT_EQ(tracker_ptr->last->captured_value_length_limit, 42u); +} + +TEST(LogRecordLimitsWiring, LoggerSkipsLimitsWhenProcessorDoesNotEnforce) +{ + using opentelemetry::sdk::instrumentationscope::ScopeConfigurator; + using opentelemetry::sdk::logs::LoggerConfig; + using opentelemetry::sdk::logs::LoggerProviderFactory; + using opentelemetry::sdk::logs::LogRecordProcessor; + + LogRecordLimits limits; + limits.attribute_count_limit = 7; + limits.attribute_value_length_limit = 42; + + std::vector> processors; + // Processor reports that its recordable ignores limits, so the Logger must + // skip the per-record SetLogRecordLimits() call on the hot path. + auto tracking_processor = std::unique_ptr(new TrackingProcessor(false)); + auto *tracker_ptr = tracking_processor.get(); + processors.push_back(std::move(tracking_processor)); + + auto configurator = std::make_unique>( + ScopeConfigurator::Builder(LoggerConfig::Default()).Build()); + + auto provider = LoggerProviderFactory::Create(std::move(processors), + opentelemetry::sdk::resource::Resource::Create({}), + std::move(configurator), limits); + + auto logger = provider->GetLogger("wiring_test", ""); + auto record = logger->CreateLogRecord(); + + ASSERT_NE(tracker_ptr->last, nullptr); + EXPECT_EQ(tracker_ptr->last->set_limits_call_count, 0); +} diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc index 02c0921fe5..2283fc1dc7 100644 --- a/sdk/test/logs/log_record_test.cc +++ b/sdk/test/logs/log_record_test.cc @@ -85,6 +85,9 @@ TEST(ReadWriteLogRecord, SetAndGet) ASSERT_EQ(record.GetTimestamp().time_since_epoch(), now.time_since_epoch()); } +namespace +{ + // Define a basic Logger class class TestBodyLogger : public opentelemetry::logs::Logger { @@ -343,3 +346,5 @@ TEST(LogBody, BodyConversation) } } } + +} // namespace diff --git a/sdk/test/logs/logger_config_test.cc b/sdk/test/logs/logger_config_test.cc index af9b1fdfa9..b2d2925f61 100644 --- a/sdk/test/logs/logger_config_test.cc +++ b/sdk/test/logs/logger_config_test.cc @@ -54,40 +54,73 @@ TEST(LoggerConfig, CheckCreateWorksAsExpected) /** Tests to verify the behavior of logs_sdk::LoggerConfig::Default */ -static std::pair attr1 = { - "accept_single_attr", true}; -static std::pair attr2 = { - "accept_second_attr", "some other attr"}; -static std::pair attr3 = { - "accept_third_attr", 3}; - -static instrumentation_scope::InstrumentationScope test_scope_1 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); -static instrumentation_scope::InstrumentationScope test_scope_2 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); -static instrumentation_scope::InstrumentationScope test_scope_3 = - *instrumentation_scope::InstrumentationScope::Create( - "test_scope_3", - "0", - "https://opentelemetry.io/schemas/v1.18.0"); -static instrumentation_scope::InstrumentationScope test_scope_4 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_4", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1}); -static instrumentation_scope::InstrumentationScope test_scope_5 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_5", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1, attr2, attr3}); +static std::pair & +GetAttr1() +{ + static std::pair value{ + "accept_single_attr", true}; + return value; +} +static std::pair & +GetAttr2() +{ + static std::pair value{ + "accept_second_attr", "some other attr"}; + return value; +} +static std::pair & +GetAttr3() +{ + static std::pair value{ + "accept_third_attr", 3}; + return value; +} + +static instrumentation_scope::InstrumentationScope &GetTestScope1() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope2() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope3() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_3", "0", "https://opentelemetry.io/schemas/v1.18.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope4() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_4", "0", "https://opentelemetry.io/schemas/v1.18.0", {GetAttr1()}); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope5() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_5", "0", "https://opentelemetry.io/schemas/v1.18.0", + {GetAttr1(), GetAttr2(), GetAttr3()}); + return value; +} // This array could also directly contain the reference types, but that leads to 'uninitialized // value was created by heap allocation' errors in Valgrind memcheck. This is a bug in Googletest // library, see https://github.com/google/googletest/issues/3805#issuecomment-1397301790 for more // details. Using pointers is a workaround to prevent the Valgrind warnings. -const std::array instrumentation_scopes = { - &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, -}; +static const std::array & +GetInstrumentationScopes() +{ + static const std::array value = { + &GetTestScope1(), &GetTestScope2(), &GetTestScope3(), &GetTestScope4(), &GetTestScope5(), + }; + return value; +} + +namespace +{ // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultLoggerConfiguratorTestFixture @@ -118,10 +151,12 @@ TEST(LoggerConfig, ScopeConfiguratorPreservesCustomConfig) .AddConditionNameEquals("test_scope_1", matching_config) .Build(); - ASSERT_EQ(configurator.ComputeConfig(test_scope_1), matching_config); - ASSERT_EQ(configurator.ComputeConfig(test_scope_2), default_config); + ASSERT_EQ(configurator.ComputeConfig(GetTestScope1()), matching_config); + ASSERT_EQ(configurator.ComputeConfig(GetTestScope2()), default_config); } INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultLoggerConfiguratorTestFixture, - ::testing::ValuesIn(instrumentation_scopes)); + ::testing::ValuesIn(GetInstrumentationScopes())); + +} // namespace diff --git a/sdk/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc index e33c9d8116..28c2b35932 100644 --- a/sdk/test/logs/logger_provider_sdk_test.cc +++ b/sdk/test/logs/logger_provider_sdk_test.cc @@ -3,37 +3,50 @@ #include #include +#include #include +#include +#include +#include #include +#include #include #include #include #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" +#include "opentelemetry/logs/logger.h" #include "opentelemetry/logs/logger_provider.h" #include "opentelemetry/logs/provider.h" +#include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/utility.h" #include "opentelemetry/nostd/variant.h" +#include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/logs/event_logger_provider.h" // IWYU pragma: keep #include "opentelemetry/sdk/logs/event_logger_provider_factory.h" // IWYU pragma: keep #include "opentelemetry/sdk/logs/exporter.h" #include "opentelemetry/sdk/logs/logger.h" +#include "opentelemetry/sdk/logs/logger_config.h" #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/logger_provider.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/provider.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/logs/simple_log_record_processor.h" #include "opentelemetry/sdk/resource/resource.h" using namespace opentelemetry::sdk::logs; -namespace logs_api = opentelemetry::logs; -namespace logs_sdk = opentelemetry::sdk::logs; -namespace nostd = opentelemetry::nostd; +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace nostd = opentelemetry::nostd; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; TEST(LoggerProviderSDK, PushToAPI) { @@ -210,6 +223,8 @@ TEST(LoggerProviderSDK, GetLoggerInequalityCheck) EXPECT_NE(logger_attribute_1, logger_attribute_2); } +namespace +{ class DummyLogRecordable final : public opentelemetry::sdk::logs::Recordable { public: @@ -242,6 +257,7 @@ class DummyLogRecordable final : public opentelemetry::sdk::logs::Recordable class DummyProcessor : public LogRecordProcessor { +public: std::unique_ptr MakeRecordable() noexcept override { return std::unique_ptr(new DummyLogRecordable()); @@ -254,6 +270,7 @@ class DummyProcessor : public LogRecordProcessor bool ForceFlush(std::chrono::microseconds /* timeout */) noexcept override { return true; } bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } }; +} // namespace TEST(LoggerProviderSDK, GetResource) { @@ -291,3 +308,336 @@ TEST(LoggerProviderSDK, ForceFlush) EXPECT_TRUE(lp.ForceFlush()); } + +// --------------------------------------------------------------------------- +// UpdateLoggerConfigurator tests +// --------------------------------------------------------------------------- +namespace +{ + +// Minimal recording exporter used across the UpdateLoggerConfigurator tests. +class RecordingExporter final : public logs_sdk::LogRecordExporter +{ +public: + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + + opentelemetry::sdk::common::ExportResult Export( + const opentelemetry::nostd::span> &records) noexcept + override + { + std::lock_guard lock(mutex_); + for (auto &r : records) + { + records_.push_back(std::move(r)); + } + return opentelemetry::sdk::common::ExportResult::kSuccess; + } + + bool ForceFlush(std::chrono::microseconds /*timeout*/) noexcept override { return true; } + + bool Shutdown(std::chrono::microseconds /*timeout*/) noexcept override { return true; } + + std::size_t RecordCount() const + { + std::lock_guard lock(mutex_); + return records_.size(); + } + + void Reset() + { + std::lock_guard lock(mutex_); + records_.clear(); + } + +private: + mutable std::mutex mutex_; + std::vector> records_; +}; + +// Helper: make a ScopeConfigurator where all loggers default to enabled with the given min +// severity. +std::unique_ptr> AllAtSeverity( + logs_api::Severity severity) +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + logs_sdk::LoggerConfig::Create(true, severity, false)) + .Build()); +} + +// Helper: make a ScopeConfigurator where the named scope is disabled; default is enabled. +std::unique_ptr> DisableByName( + opentelemetry::nostd::string_view name) +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + logs_sdk::LoggerConfig::Default()) + .AddConditionNameEquals(name, logs_sdk::LoggerConfig::Disabled()) + .Build()); +} + +// Helper: make a ScopeConfigurator that disables all loggers. +std::unique_ptr> DisableAll() +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + logs_sdk::LoggerConfig::Disabled()) + .Build()); +} + +// Helper: make a ScopeConfigurator that enables all loggers (default config). +std::unique_ptr> EnableAll() +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + logs_sdk::LoggerConfig::Default()) + .Build()); +} + +// Helper: Info default for all scopes with the named scope set to a specific minimum severity +// override. +std::unique_ptr> InfoWithOverrideSeverity( + opentelemetry::nostd::string_view scope_name, + logs_api::Severity override_severity) +{ + return std::make_unique>( + scope_sdk::ScopeConfigurator::Builder( + logs_sdk::LoggerConfig::Create(true, logs_api::Severity::kInfo, false)) + .AddConditionNameEquals(scope_name, + logs_sdk::LoggerConfig::Create(true, override_severity, false)) + .Build()); +} + +// Builds a LoggerProvider whose RecordingExporter pointer is returned via the out parameter. +std::unique_ptr MakeProvider( + RecordingExporter *&exporter_out, + std::unique_ptr> configurator = + EnableAll()) +{ + auto exporter = std::make_unique(); + exporter_out = exporter.get(); + auto processor = std::make_unique(std::move(exporter)); + return std::make_unique( + std::move(processor), opentelemetry::sdk::resource::Resource::Create({}), + std::move(configurator)); +} + +} // namespace + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorDisableByName) +{ + RecordingExporter *exporter{}; + auto provider = MakeProvider(exporter); + ASSERT_TRUE(exporter != nullptr); + + auto logger_disabled_by_update = provider->GetLogger("logger.disabled", "scope.disabled"); + auto logger_unaffected = provider->GetLogger("logger.unaffected", "scope.unaffected"); + + // Both loggers are initially enabled. + ASSERT_TRUE(logger_disabled_by_update->Enabled(logs_api::Severity::kInfo)); + ASSERT_TRUE(logger_unaffected->Enabled(logs_api::Severity::kInfo)); + + // Disable scope.disabled by name; scope.unaffected must remain enabled. + provider->UpdateLoggerConfigurator(DisableByName("scope.disabled")); + + EXPECT_FALSE(logger_disabled_by_update->Enabled(logs_api::Severity::kInfo)); + EXPECT_TRUE(logger_unaffected->Enabled(logs_api::Severity::kInfo)); + + // Verify that emission is suppressed for the disabled logger. + exporter->Reset(); + logger_disabled_by_update->Info("should not appear"); + logger_unaffected->Info("should appear"); + EXPECT_EQ(exporter->RecordCount(), 1u); +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorReEnable) +{ + RecordingExporter *exporter{}; + auto provider = MakeProvider(exporter, DisableAll()); + ASSERT_TRUE(exporter != nullptr); + + auto existing_logger = provider->GetLogger("existing", "scope.existing"); + + ASSERT_FALSE(existing_logger->Enabled(logs_api::Severity::kInfo)); + + // enable all loggers. + provider->UpdateLoggerConfigurator(EnableAll()); + + // The existing logger handle must immediately reflect the updated config. + EXPECT_TRUE(existing_logger->Enabled(logs_api::Severity::kInfo)); + + // Loggers obtained after the update also reflect the new configurator. + auto logger_after = provider->GetLogger("after", "scope.after"); + EXPECT_TRUE(logger_after->Enabled(logs_api::Severity::kInfo)); +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorMinSeverityChange) +{ + RecordingExporter *exporter{}; + // Start with min severity = Info. + auto provider = MakeProvider(exporter, AllAtSeverity(logs_api::Severity::kInfo)); + + ASSERT_TRUE(exporter != nullptr); + + auto logger = provider->GetLogger("mylogger", "scope.test"); + + // At Info threshold: Debug is suppressed, Info passes. + EXPECT_FALSE(logger->Enabled(logs_api::Severity::kDebug)); + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kInfo)); + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kError)); + +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + // In ABIv2, emit filtering is implicit + exporter->Reset(); + ASSERT_EQ(exporter->RecordCount(), 0u); + logger->Debug("debug msg"); // filtered + logger->Info("info msg"); // passes + logger->Error("error msg"); // passes + EXPECT_EQ(exporter->RecordCount(), 2u); +#endif + + // Raise min severity to Warn. + provider->UpdateLoggerConfigurator(AllAtSeverity(logs_api::Severity::kWarn)); + + EXPECT_FALSE(logger->Enabled(logs_api::Severity::kInfo)); + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kWarn)); + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kError)); + +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + exporter->Reset(); + ASSERT_EQ(exporter->RecordCount(), 0u); + logger->Info("info msg"); // filtered + logger->Warn("warn msg"); // passes + logger->Error("error msg"); // passes + EXPECT_EQ(exporter->RecordCount(), 2u); +#endif + + // Drop min severity to Debug. + provider->UpdateLoggerConfigurator(AllAtSeverity(logs_api::Severity::kDebug)); + + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kDebug)); + +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + exporter->Reset(); + ASSERT_EQ(exporter->RecordCount(), 0u); + logger->Debug("debug msg"); // passes + logger->Info("info msg"); // passes + EXPECT_EQ(exporter->RecordCount(), 2u); +#endif +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorPerScopeMinSeverity) +{ + RecordingExporter *exporter{}; + // Default Info; "scope.verbose" gets Debug. + auto provider = + MakeProvider(exporter, InfoWithOverrideSeverity("scope.verbose", logs_api::Severity::kDebug)); + + ASSERT_TRUE(exporter != nullptr); + + auto logger_default = provider->GetLogger("default", "scope.default"); + auto logger_verbose = provider->GetLogger("verbose", "scope.verbose"); + + // scope.default filtered at Debug, passes at Info. + EXPECT_FALSE(logger_default->Enabled(logs_api::Severity::kDebug)); + EXPECT_TRUE(logger_default->Enabled(logs_api::Severity::kInfo)); + + // scope.verbose passes at Debug. + EXPECT_TRUE(logger_verbose->Enabled(logs_api::Severity::kDebug)); + + // Now raise scope.verbose to Warn; everything else stays at Info. + provider->UpdateLoggerConfigurator( + InfoWithOverrideSeverity("scope.verbose", logs_api::Severity::kWarn)); + + EXPECT_FALSE(logger_verbose->Enabled(logs_api::Severity::kInfo)); + EXPECT_TRUE(logger_verbose->Enabled(logs_api::Severity::kWarn)); + EXPECT_TRUE(logger_default->Enabled(logs_api::Severity::kInfo)); +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorNewLoggerUsesUpdatedConfig) +{ + RecordingExporter *exporter{}; + auto provider = MakeProvider(exporter); + ASSERT_TRUE(exporter != nullptr); + + // Install a configurator that disables "scope.disabled" before any logger for that scope exists. + provider->UpdateLoggerConfigurator(DisableByName("scope.disabled")); + + // A logger obtained after the update must reflect the new config. + auto logger_for_disabled_scope = provider->GetLogger("disabled", "scope.disabled"); + EXPECT_FALSE(logger_for_disabled_scope->Enabled(logs_api::Severity::kInfo)); + + // Scopes not named in the configurator remain enabled. + auto logger_for_unaffected_scope = provider->GetLogger("unaffected", "scope.unaffected"); + EXPECT_TRUE(logger_for_unaffected_scope->Enabled(logs_api::Severity::kInfo)); +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorNullIgnored) +{ + RecordingExporter *exporter{}; + auto provider = MakeProvider(exporter, AllAtSeverity(logs_api::Severity::kInfo)); + ASSERT_TRUE(exporter != nullptr); + + auto logger = provider->GetLogger("mylogger", "scope.test"); + ASSERT_TRUE(logger->Enabled(logs_api::Severity::kInfo)); + + // Passing nullptr must be a no-op (logged as an error internally, config unchanged). + provider->UpdateLoggerConfigurator(nullptr); + + EXPECT_TRUE(logger->Enabled(logs_api::Severity::kInfo)); +} + +TEST(LoggerProviderSDK, UpdateLoggerConfiguratorConcurrentEmit) +{ + RecordingExporter *exporter{}; + auto provider = MakeProvider(exporter, AllAtSeverity(logs_api::Severity::kInfo)); + ASSERT_TRUE(exporter != nullptr); + + auto logger = provider->GetLogger("mylogger", "scope.concurrent"); + + std::atomic stop{false}; + std::atomic worker_saw_suppressed{false}; + std::atomic worker_saw_emitted{false}; + + std::promise worker_ready; + std::future worker_ready_future = worker_ready.get_future(); + + // Worker: call Enabled() and emit in a tight loop; flag each observed state. + std::thread worker([&] { + worker_ready.set_value(); + while (!stop.load(std::memory_order_relaxed)) + { + if (logger->Enabled(logs_api::Severity::kInfo)) + { + logger->Info("emit"); + worker_saw_emitted.store(true, std::memory_order_relaxed); + } + else + { + worker_saw_suppressed.store(true, std::memory_order_relaxed); + } + } + }); + + worker_ready_future.wait(); + + // Raise min severity so Info is suppressed; wait for worker to observe it. + provider->UpdateLoggerConfigurator(AllAtSeverity(logs_api::Severity::kError)); + while (!worker_saw_suppressed.load(std::memory_order_relaxed)) + { + std::this_thread::yield(); + } + + // Lower min severity back to Info; wait for worker to observe it. + provider->UpdateLoggerConfigurator(AllAtSeverity(logs_api::Severity::kInfo)); + while (!worker_saw_emitted.load(std::memory_order_relaxed)) + { + std::this_thread::yield(); + } + + stop.store(true, std::memory_order_relaxed); + worker.join(); +} diff --git a/sdk/test/logs/logger_provider_set_test.cc b/sdk/test/logs/logger_provider_set_test.cc index a48d26eb47..aa899bbcbd 100644 --- a/sdk/test/logs/logger_provider_set_test.cc +++ b/sdk/test/logs/logger_provider_set_test.cc @@ -30,6 +30,7 @@ namespace logs_sdk = opentelemetry::sdk::logs; class TestProvider : public LoggerProvider { +public: nostd::shared_ptr GetLogger( nostd::string_view /* logger_name */, nostd::string_view /* library_name */, diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 84cd131566..70d34004af 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -773,62 +773,113 @@ class CustomLogConfiguratorTestData // constants used in VerifyCustomConfiguratorBehavior test static auto noop_logger = logs_api::NoopLogger(); -const std::string schema{"https://opentelemetry.io/schemas/1.11.0"}; +constexpr char schema[] = "https://opentelemetry.io/schemas/1.11.0"; // Generate test case data // Test Case 1 -static auto instrumentation_scope_1 = - *InstrumentationScope::Create("opentelemetry_library", "1.0.0", schema); -static auto test_log_recordable_1 = - create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); -static auto expected_log_recordable_1 = - create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); -static auto custom_log_configurator_test_data_1 = - CustomLogConfiguratorTestData(instrumentation_scope_1, - *test_log_recordable_1, - *expected_log_recordable_1, - false); +static InstrumentationScope &GetInstrumentationScope1() +{ + static auto value = *InstrumentationScope::Create("opentelemetry_library", "1.0.0", schema); + return value; +} +static std::unique_ptr &GetTestLogRecordable1() +{ + static auto value = + create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); + return value; +} +static std::unique_ptr &GetExpectedLogRecordable1() +{ + static auto value = + create_mock_log_recordable("Log Message", opentelemetry::logs::Severity::kWarn); + return value; +} +static CustomLogConfiguratorTestData &GetCustomLogConfiguratorTestData1() +{ + static auto value = CustomLogConfiguratorTestData( + GetInstrumentationScope1(), *GetTestLogRecordable1(), *GetExpectedLogRecordable1(), false); + return value; +} // Test Case 2 -static auto instrumentation_scope_2 = *InstrumentationScope::Create("bar_library", "1.0.0", schema); -static auto test_log_recordable_2 = - create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); -static auto expected_log_recordable_2 = - create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); -static auto custom_log_configurator_test_data_2 = - CustomLogConfiguratorTestData(instrumentation_scope_2, - *test_log_recordable_2, - *expected_log_recordable_2, - false); +static InstrumentationScope &GetInstrumentationScope2() +{ + static auto value = *InstrumentationScope::Create("bar_library", "1.0.0", schema); + return value; +} +static std::unique_ptr &GetTestLogRecordable2() +{ + static auto value = create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); + return value; +} +static std::unique_ptr &GetExpectedLogRecordable2() +{ + static auto value = create_mock_log_recordable("", opentelemetry::logs::Severity::kDebug); + return value; +} +static CustomLogConfiguratorTestData &GetCustomLogConfiguratorTestData2() +{ + static auto value = CustomLogConfiguratorTestData( + GetInstrumentationScope2(), *GetTestLogRecordable2(), *GetExpectedLogRecordable2(), false); + return value; +} // Test Case 3 -static auto instrumentation_scope_3 = *InstrumentationScope::Create("foo_library", "", schema); -static auto test_log_recordable_3 = - create_mock_log_recordable("Info message", opentelemetry::logs::Severity::kInfo); -static auto expected_log_recordable_3 = - create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); -static auto custom_log_configurator_test_data_3 = - CustomLogConfiguratorTestData(instrumentation_scope_3, - *test_log_recordable_3, - *expected_log_recordable_3, - true); +static InstrumentationScope &GetInstrumentationScope3() +{ + static auto value = *InstrumentationScope::Create("foo_library", "", schema); + return value; +} +static std::unique_ptr &GetTestLogRecordable3() +{ + static auto value = + create_mock_log_recordable("Info message", opentelemetry::logs::Severity::kInfo); + return value; +} +static std::unique_ptr &GetExpectedLogRecordable3() +{ + static auto value = create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); + return value; +} +static CustomLogConfiguratorTestData &GetCustomLogConfiguratorTestData3() +{ + static auto value = CustomLogConfiguratorTestData( + GetInstrumentationScope3(), *GetTestLogRecordable3(), *GetExpectedLogRecordable3(), true); + return value; +} // Test Case 4 -static auto instrumentation_scope_4 = *InstrumentationScope::Create("allowed_library", "", schema); -static auto test_log_recordable_4 = - create_mock_log_recordable("Scope version missing", opentelemetry::logs::Severity::kInfo); -static auto expected_log_recordable_4 = - create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); -static auto custom_log_configurator_test_data_4 = - CustomLogConfiguratorTestData(instrumentation_scope_4, - *test_log_recordable_4, - *expected_log_recordable_4, - true); +static InstrumentationScope &GetInstrumentationScope4() +{ + static auto value = *InstrumentationScope::Create("allowed_library", "", schema); + return value; +} +static std::unique_ptr &GetTestLogRecordable4() +{ + static auto value = + create_mock_log_recordable("Scope version missing", opentelemetry::logs::Severity::kInfo); + return value; +} +static std::unique_ptr &GetExpectedLogRecordable4() +{ + static auto value = create_mock_log_recordable("", opentelemetry::logs::Severity::kInvalid); + return value; +} +static CustomLogConfiguratorTestData &GetCustomLogConfiguratorTestData4() +{ + static auto value = CustomLogConfiguratorTestData( + GetInstrumentationScope4(), *GetTestLogRecordable4(), *GetExpectedLogRecordable4(), true); + return value; +} // This array could also directly contain the reference types, but that leads to 'uninitialized // value was created by heap allocation' errors in Valgrind memcheck. This is a bug in Googletest // library, see https://github.com/google/googletest/issues/3805#issuecomment-1397301790 for more // details. Using pointers is a workaround to prevent the Valgrind warnings. -constexpr std::array log_configurator_test_cases = { - &custom_log_configurator_test_data_1, &custom_log_configurator_test_data_2, - &custom_log_configurator_test_data_3, &custom_log_configurator_test_data_4}; +static const std::array &GetLogConfiguratorTestCases() +{ + static const std::array value = { + &GetCustomLogConfiguratorTestData1(), &GetCustomLogConfiguratorTestData2(), + &GetCustomLogConfiguratorTestData3(), &GetCustomLogConfiguratorTestData4()}; + return value; +} // Test fixture for VerifyCustomConfiguratorBehavior class CustomLoggerConfiguratorTestFixture @@ -899,7 +950,7 @@ TEST_P(CustomLoggerConfiguratorTestFixture, VerifyCustomConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(CustomLogConfiguratorTestData, CustomLoggerConfiguratorTestFixture, - ::testing::ValuesIn(log_configurator_test_cases)); + ::testing::ValuesIn(GetLogConfiguratorTestCases())); #if OPENTELEMETRY_ABI_VERSION_NO < 2 TEST(LoggerSDK, EventLog) diff --git a/sdk/test/metrics/async_metric_storage_test.cc b/sdk/test/metrics/async_metric_storage_test.cc index 4de1a95c4f..3987770420 100644 --- a/sdk/test/metrics/async_metric_storage_test.cc +++ b/sdk/test/metrics/async_metric_storage_test.cc @@ -28,7 +28,6 @@ #include "opentelemetry/sdk/metrics/view/attributes_processor.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -66,7 +65,7 @@ TEST_P(WritableMetricStorageTestFixture, TestAggregation) opentelemetry::sdk::metrics::AsyncMetricStorage storage( instr_desc, AggregationType::kSum, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); int64_t get_count1 = 20; @@ -161,7 +160,7 @@ TEST_P(WritableMetricStorageTestUpDownFixture, TestAggregation) opentelemetry::sdk::metrics::AsyncMetricStorage storage( instr_desc, AggregationType::kDefault, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); int64_t get_count1 = 20; @@ -257,7 +256,7 @@ TEST_P(WritableMetricStorageTestObservableGaugeFixture, TestAggregation) opentelemetry::sdk::metrics::AsyncMetricStorage storage( instr_desc, AggregationType::kLastValue, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); int64_t freq_cpu0 = 3; diff --git a/sdk/test/metrics/attributes_hashmap_test.cc b/sdk/test/metrics/attributes_hashmap_test.cc index bd243fd3f2..9aedf0aef2 100644 --- a/sdk/test/metrics/attributes_hashmap_test.cc +++ b/sdk/test/metrics/attributes_hashmap_test.cc @@ -82,6 +82,9 @@ TEST(AttributesHashMap, BasicTests) EXPECT_EQ(count, hash_map.Size()); } +namespace +{ + class MetricAttributeMapHashForCollision { public: @@ -180,6 +183,7 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) // Size should be exactly 'limit' (no overflow yet) EXPECT_EQ(map.Size(), limit); + EXPECT_EQ(map.Get(GetOverflowAttributes()), nullptr); // Insert one more distinct attribute; this should not increase the real attributes count MetricAttributes overflow_trigger = {{"k", "overflow"}}; @@ -187,9 +191,9 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) []() { return std::unique_ptr(new DropAggregation()); }) ->Aggregate(static_cast(1)); - EXPECT_EQ(map.Size(), limit); + EXPECT_EQ(map.Size(), limit + 1); - // Insert several more unique attributes - size must remain constant (limit) + // Insert several more unique attributes - size must remain constant (limit + overflow) for (size_t i = 0; i < limit - 1; ++i) { MetricAttributes extra_attr = {{"k", std::string("extra") + std::to_string(i)}}; @@ -197,13 +201,13 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) []() { return std::unique_ptr(new DropAggregation()); }) ->Aggregate(static_cast(1)); } - EXPECT_EQ(map.Size(), limit); + EXPECT_EQ(map.Size(), limit + 1); // Ensure overflow key was actually created and accessible via Get - EXPECT_NE(map.Get(kOverflowAttributes), nullptr); + EXPECT_NE(map.Get(GetOverflowAttributes()), nullptr); // Ensure original real attributes still present - for (size_t i = 0; i < limit - 1; ++i) + for (size_t i = 0; i < limit; ++i) { MetricAttributes attr = {{"k", std::to_string(i)}}; EXPECT_NE(map.Get(attr), nullptr); @@ -216,10 +220,12 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) return true; }); EXPECT_EQ(map_copy.Size(), map.Size()); - EXPECT_NE(map_copy.Get(kOverflowAttributes), nullptr); - for (size_t i = 0; i < limit - 1; ++i) + EXPECT_NE(map_copy.Get(GetOverflowAttributes()), nullptr); + for (size_t i = 0; i < limit; ++i) { MetricAttributes attr = {{"k", std::to_string(i)}}; EXPECT_NE(map_copy.Get(attr), nullptr); } } + +} // namespace diff --git a/sdk/test/metrics/bound_sync_instruments_test.cc b/sdk/test/metrics/bound_sync_instruments_test.cc index 9c2f233691..6b289c6aa4 100644 --- a/sdk/test/metrics/bound_sync_instruments_test.cc +++ b/sdk/test/metrics/bound_sync_instruments_test.cc @@ -40,7 +40,6 @@ # include "opentelemetry/sdk/metrics/view/attributes_processor.h" # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" # endif @@ -75,7 +74,6 @@ class StorageHolder } storage_ = std::make_shared(desc, agg_type, proc_, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), # endif cfg_); @@ -186,12 +184,12 @@ TEST(BoundSyncInstruments, BoundCounterBindInitializerList) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); AggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kSum, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kSum, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongCounter counter(desc, std::move(storage)); opentelemetry::metrics::Counter &api_counter = counter; @@ -238,12 +236,12 @@ TEST(BoundSyncInstruments, UnboundCounterDropsValueAboveInt64Max) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); AggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kSum, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kSum, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongCounter counter(desc, std::move(storage)); M attrs = {{"key", "v"}}; @@ -261,12 +259,12 @@ TEST(BoundSyncInstruments, BoundCounterDropsValueAboveInt64Max) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); AggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kSum, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kSum, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongCounter counter(desc, std::move(storage)); M attrs = {{"key", "v"}}; @@ -315,12 +313,12 @@ TEST(BoundSyncInstruments, UnboundHistogramDropsValueAboveInt64Max) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); HistogramAggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kHistogram, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kHistogram, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongHistogram histogram(desc, std::move(storage)); M attrs = {{"key", "v"}}; @@ -354,12 +352,12 @@ TEST(BoundSyncInstruments, BoundHistogramDropsValueAboveInt64Max) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); HistogramAggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kHistogram, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kHistogram, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongHistogram histogram(desc, std::move(storage)); M attrs = {{"key", "v"}}; @@ -394,12 +392,12 @@ TEST(BoundSyncInstruments, BoundHistogramBindInitializerList) InstrumentValueType::kLong}; std::shared_ptr proc(new DefaultAttributesProcessor{}); HistogramAggregationConfig cfg; - std::unique_ptr storage(new SyncMetricStorage( - desc, AggregationType::kHistogram, proc, + std::unique_ptr storage( + new SyncMetricStorage(desc, AggregationType::kHistogram, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif - &cfg)); + &cfg)); SyncMetricStorage *storage_ptr = storage.get(); LongHistogram histogram(desc, std::move(storage)); opentelemetry::metrics::Histogram &api_histogram = histogram; @@ -436,7 +434,6 @@ TEST(BoundSyncInstruments, BoundCounterRespectsDropAggregation) AggregationConfig cfg; SyncMetricStorage storage(desc, AggregationType::kDrop, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), # endif &cfg); @@ -472,7 +469,6 @@ TEST(BoundSyncInstruments, BoundCounterRespectsLastValueAggregation) AggregationConfig cfg; SyncMetricStorage storage(desc, AggregationType::kLastValue, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), # endif &cfg); @@ -511,7 +507,6 @@ TEST(BoundSyncInstruments, BoundHistogramRespectsCustomBuckets) cfg.boundaries_ = {10.0, 20.0}; SyncMetricStorage storage(desc, AggregationType::kHistogram, proc, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), # endif &cfg); @@ -638,8 +633,7 @@ TEST(BoundSyncInstruments, BoundAndUnboundShareDatapoint) // multiple overflow-bound handles aggregate into the same overflow datapoint. TEST(BoundSyncInstruments, BindingAtOverflowGoesToOverflowBucket) { - // limit = 3: two real distinct keys are admitted; the third new distinct - // key overflows because the overflow attribute set itself consumes a slot. + // limit = 3: three real distinct keys are admitted; the fourth overflows. StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); M a1 = {{"k", "1"}}; @@ -658,7 +652,7 @@ TEST(BoundSyncInstruments, BindingAtOverflowGoesToOverflowBucket) auto b4 = holder->Bind(KeyValueIterableView(a4)); b4->RecordLong(50); - // Overflow datapoint must equal sum of the overflow-bound writes (100 + 50). + // Overflow datapoint contains only the fourth bound write. std::shared_ptr collector( new MockCollectorHandle(AggregationTemporality::kDelta)); std::vector> collectors{collector}; @@ -678,7 +672,7 @@ TEST(BoundSyncInstruments, BindingAtOverflowGoesToOverflowBucket) return true; }); EXPECT_TRUE(seen); - EXPECT_EQ(ov_value, 150); + EXPECT_EQ(ov_value, 50); } // 7) Noop bound instruments compile and no-op. @@ -761,7 +755,7 @@ TEST(BoundSyncInstruments, DroppedBoundEntriesAreGarbageCollected) h->RecordLong(1); handles.push_back(h); } - // Expect no overflow yet (3 + 1 < 5). + // Expect no overflow yet (3 < 5). EXPECT_FALSE(HasOverflowPoint(*holder, AggregationTemporality::kDelta)); } @@ -769,14 +763,12 @@ TEST(BoundSyncInstruments, DroppedBoundEntriesAreGarbageCollected) // Bind() to reuse it without consuming new cardinality. TEST(BoundSyncInstruments, BindOnExistingUnboundKeyDoesNotOverflow) { - // limit = 3: two real keys are admitted; a third new distinct key would - // overflow (overflow itself consumes a slot). + // limit = 3: three real keys are admitted; a fourth new distinct key would overflow. StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); M a1 = {{"k", "1"}}; M a2 = {{"k", "2"}}; - // Two distinct unbound keys consume the two admitted slots (limit=3 with - // overflow consuming a slot leaves room for two real keys). + // Two distinct unbound keys consume two of the three admitted slots. holder->RecordLong(10, KeyValueIterableView(a1), opentelemetry::context::Context{}); holder->RecordLong(20, KeyValueIterableView(a2), opentelemetry::context::Context{}); @@ -863,9 +855,9 @@ TEST(BoundSyncInstruments, UnboundOnExistingBoundKeyDoesNotOverflow) // unbound keys correctly overflow. TEST(BoundSyncInstruments, RetainedBoundEntriesCountAfterDeltaCollect) { - StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); + StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 2); - // Two bound keys consume the two real admitted slots (overflow takes the 3rd). + // Two bound keys consume the two admitted slots. M a1 = {{"bound", "1"}}; M a2 = {{"bound", "2"}}; auto b1 = holder->Bind(KeyValueIterableView(a1)); @@ -886,7 +878,7 @@ TEST(BoundSyncInstruments, RetainedBoundEntriesCountAfterDeltaCollect) // Strengthened: assert the overflow datapoint VALUE, not just its presence. TEST(BoundSyncInstruments, RetainedBoundEntriesOverflowValue) { - StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); + StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 2); M a1 = {{"bound", "1"}}; M a2 = {{"bound", "2"}}; auto b1 = holder->Bind(KeyValueIterableView(a1)); @@ -917,12 +909,11 @@ TEST(BoundSyncInstruments, RetainedBoundEntriesOverflowValue) } // No-attribute unbound RecordLong must follow the unified cardinality policy. -// With limit=3, two real distinct bound keys are admitted; the empty-attr -// unbound write would be a third new key and routes to overflow (overflow -// itself consumes a slot). +// With limit=2, two real distinct bound keys are admitted; the empty-attr +// unbound write is a third new key and routes to overflow. TEST(BoundSyncInstruments, NoAttributeUnboundFollowsUnifiedPolicyLong) { - StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); + StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 2); M a1 = {{"k", "1"}}; M a2 = {{"k", "2"}}; auto b1 = holder->Bind(KeyValueIterableView(a1)); @@ -954,7 +945,7 @@ TEST(BoundSyncInstruments, NoAttributeUnboundFollowsUnifiedPolicyLong) // Same coverage for double counter no-attribute path. TEST(BoundSyncInstruments, NoAttributeUnboundFollowsUnifiedPolicyDouble) { - StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kDouble, 3); + StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kDouble, 2); M a1 = {{"k", "1"}}; M a2 = {{"k", "2"}}; auto b1 = holder->Bind(KeyValueIterableView(a1)); @@ -1042,7 +1033,7 @@ TEST(BoundSyncInstruments, DirtyDroppedBoundEntriesReleaseCardinality) { StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); - // Bind two distinct keys (consume the two real admitted slots), record so + // Bind two distinct keys, record so // they're dirty, then drop the handles BEFORE Collect(). The pre-rotation // GC cannot remove them (they're dirty); rotation flushes their data; the // post-rotation cleanup must release their slots. @@ -1076,36 +1067,36 @@ TEST(BoundSyncInstruments, OverflowParityAllowsFillingRemainingSlot) { StorageHolder holder(InstrumentType::kCounter, InstrumentValueType::kLong, 3); - // Trigger overflow first: bind k1, k2, then k3 (which routes to overflow, - // so bound_entries_ ends up with k1, k2, and the overflow entry). + // Trigger overflow first: bind k1, k2, k3, then k4, which routes to overflow. M a1 = {{"k", "1"}}; M a2 = {{"k", "2"}}; M a3 = {{"k", "3"}}; + M a4 = {{"k", "4"}}; auto b1 = holder->Bind(KeyValueIterableView(a1)); auto b2 = holder->Bind(KeyValueIterableView(a2)); - auto bov = holder->Bind(KeyValueIterableView(a3)); // routes to overflow + auto b3 = holder->Bind(KeyValueIterableView(a3)); + auto bov = holder->Bind(KeyValueIterableView(a4)); // routes to overflow b1->RecordLong(1); b2->RecordLong(1); + b3->RecordLong(1); bov->RecordLong(100); // Drop k1 only. After Collect(), the M1 cleanup releases its slot, leaving - // active_keys_ = { k2, overflow }. With limit=3, has_overflow=true and - // active_keys_.size()==2, the existing AttributesHashMap semantics admit - // one more real key. The pre-fix preview path would have routed it to - // overflow (off-by-one). After M2, it must be admitted. + // active_keys_ = { k2, k3, overflow }. With limit=3, the existing + // AttributesHashMap semantics admit one more real key. b1.reset(); - EXPECT_EQ(CollectAndCountPoints(*holder, AggregationTemporality::kDelta), 3u); + EXPECT_EQ(CollectAndCountPoints(*holder, AggregationTemporality::kDelta), 4u); // Fresh real key in the next interval must be admitted, not routed to overflow. - M a4 = {{"fresh", "key"}}; - holder->RecordLong(42, KeyValueIterableView(a4), opentelemetry::context::Context{}); + M a5 = {{"fresh", "key"}}; + holder->RecordLong(42, KeyValueIterableView(a5), opentelemetry::context::Context{}); std::shared_ptr collector( new MockCollectorHandle(AggregationTemporality::kDelta)); std::vector> collectors{collector}; bool overflow_seen = false; - bool a4_seen = false; - int64_t a4_value = 0; + bool a5_seen = false; + int64_t a5_value = 0; holder->Collect(collector.get(), collectors, std::chrono::system_clock::now(), std::chrono::system_clock::now(), [&](const MetricData &md) { for (const auto &p : md.point_data_attr_) @@ -1118,15 +1109,15 @@ TEST(BoundSyncInstruments, OverflowParityAllowsFillingRemainingSlot) if (it != p.attributes.end() && opentelemetry::nostd::get(it->second) == "key") { - a4_seen = true; + a5_seen = true; const auto &sp = opentelemetry::nostd::get(p.point_data); - a4_value = opentelemetry::nostd::get(sp.value_); + a5_value = opentelemetry::nostd::get(sp.value_); } } return true; }); - EXPECT_TRUE(a4_seen); - EXPECT_EQ(a4_value, 42); + EXPECT_TRUE(a5_seen); + EXPECT_EQ(a5_value, 42); EXPECT_FALSE(overflow_seen); } diff --git a/sdk/test/metrics/cardinality_limit_test.cc b/sdk/test/metrics/cardinality_limit_test.cc index 01aaf79b0a..cd38641a79 100644 --- a/sdk/test/metrics/cardinality_limit_test.cc +++ b/sdk/test/metrics/cardinality_limit_test.cc @@ -32,7 +32,6 @@ #include "opentelemetry/sdk/metrics/view/attributes_processor.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -47,7 +46,7 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) []() -> std::unique_ptr { return std::unique_ptr(new LongSumAggregation(true)); }; - // add 10 unique metric points. 9 should be added to hashmap, 10th should be overflow. + // Add 10 unique metric points. All should be independently aggregated without overflow. int64_t record_value = 100; for (auto i = 0; i < 10; i++) { @@ -56,15 +55,16 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) ->Aggregate(record_value); } EXPECT_EQ(hash_map.Size(), 10); + EXPECT_EQ(hash_map.Get(GetOverflowAttributes()), nullptr); // add 5 unique metric points above limit, they all should get consolidated as single - // overflowmetric point. + // overflow metric point. for (auto i = 10; i < 15; i++) { FilteredOrderedAttributeMap attributes = {{"key", std::to_string(i)}}; static_cast(hash_map.GetOrSetDefault(attributes, aggregation_callback)) ->Aggregate(record_value); } - EXPECT_EQ(hash_map.Size(), 10); // only one more metric point should be added as overflow. + EXPECT_EQ(hash_map.Size(), 11); // one additional metric point should be added as overflow. // record 5 more measurements to already existing (and not-overflow) metric points. They // should get aggregated to these existing metric points. for (auto i = 0; i < 5; i++) @@ -73,16 +73,16 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) static_cast(hash_map.GetOrSetDefault(attributes, aggregation_callback)) ->Aggregate(record_value); } - EXPECT_EQ(hash_map.Size(), 10); // no new metric point added + EXPECT_EQ(hash_map.Size(), 11); // no new metric point added // get the overflow metric point - auto agg1 = hash_map.GetOrSetDefault(kOverflowAttributes, aggregation_callback); + auto agg1 = hash_map.GetOrSetDefault(GetOverflowAttributes(), aggregation_callback); EXPECT_NE(agg1, nullptr); auto sum_agg1 = static_cast(agg1); EXPECT_EQ(nostd::get(nostd::get(sum_agg1->ToPoint()).value_), - record_value * 6); // 1 from previous 10, 5 from current 5. + record_value * 5); // get remaining metric points - for (auto i = 0; i < 9; i++) + for (auto i = 0; i < 10; i++) { FilteredOrderedAttributeMap attributes = {{"key", std::to_string(i)}}; auto agg2 = hash_map.GetOrSetDefault(attributes, aggregation_callback); @@ -101,6 +101,9 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) } } +namespace +{ + class WritableMetricStorageCardinalityLimitTestFixture : public ::testing::TestWithParam {}; @@ -116,13 +119,12 @@ TEST_P(WritableMetricStorageCardinalityLimitTestFixture, LongCounterSumAggregati new DefaultAttributesProcessor{}}; SyncMetricStorage storage(instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), #endif &aggConfig); int64_t record_value = 100; - // add 9 unique metric points, and 6 more above limit. + // Add 10 unique metric points, and 5 more above limit. for (auto i = 0; i < 15; i++) { std::map attributes = {{"key", std::to_string(i)}}; @@ -151,13 +153,13 @@ TEST_P(WritableMetricStorageCardinalityLimitTestFixture, LongCounterSumAggregati // value `true`. EXPECT_EQ(data_attr.attributes.size(), 1u); EXPECT_EQ(nostd::get(data_attr.attributes.begin()->second), true); - EXPECT_EQ(nostd::get(data.value_), record_value * 6); + EXPECT_EQ(nostd::get(data.value_), record_value * 5); overflow_present = true; } } return true; }); - EXPECT_EQ(count_attributes, attributes_limit); + EXPECT_EQ(count_attributes, attributes_limit + 1); EXPECT_EQ(overflow_present, true); } INSTANTIATE_TEST_SUITE_P(All, @@ -174,12 +176,14 @@ INSTANTIATE_TEST_SUITE_P(All, // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits TEST(CardinalityLimitOverflowAttribute, MatchesSpecLiteral) { - EXPECT_EQ(opentelemetry::sdk::metrics::kAttributesLimitOverflowKey, "otel.metric.overflow"); + EXPECT_STREQ(opentelemetry::sdk::metrics::kAttributesLimitOverflowKey, "otel.metric.overflow"); EXPECT_EQ(opentelemetry::sdk::metrics::kAttributesLimitOverflowValue, true); // The precomputed overflow attribute set MUST contain exactly the spec key // mapped to the boolean value `true`. - ASSERT_EQ(opentelemetry::sdk::metrics::kOverflowAttributes.size(), 1u); - const auto &entry = *opentelemetry::sdk::metrics::kOverflowAttributes.begin(); + ASSERT_EQ(opentelemetry::sdk::metrics::GetOverflowAttributes().size(), 1u); + const auto &entry = *opentelemetry::sdk::metrics::GetOverflowAttributes().begin(); EXPECT_EQ(entry.first, "otel.metric.overflow"); EXPECT_EQ(nostd::get(entry.second), true); } + +} // namespace diff --git a/sdk/test/metrics/circular_buffer_counter_test.cc b/sdk/test/metrics/circular_buffer_counter_test.cc index d98077c9bc..b79289563d 100644 --- a/sdk/test/metrics/circular_buffer_counter_test.cc +++ b/sdk/test/metrics/circular_buffer_counter_test.cc @@ -10,6 +10,9 @@ using namespace opentelemetry::sdk::metrics; +namespace +{ + class AdaptingIntegerArrayTest : public testing::TestWithParam {}; @@ -149,3 +152,5 @@ TEST(AdaptingCircularBufferCounterTest, Clear) counter.Clear(); EXPECT_TRUE(counter.Empty()); } + +} // namespace diff --git a/sdk/test/metrics/common.h b/sdk/test/metrics/common.h index 7d83c03c1d..323696f847 100644 --- a/sdk/test/metrics/common.h +++ b/sdk/test/metrics/common.h @@ -42,13 +42,13 @@ class MockMetricReader : public opentelemetry::sdk::metrics::MetricReader opentelemetry::sdk::metrics::AggregationTemporality GetAggregationTemporality( opentelemetry::sdk::metrics::InstrumentType) const noexcept override; +private: bool OnForceFlush(std::chrono::microseconds) noexcept override; bool OnShutDown(std::chrono::microseconds) noexcept override; void OnInitialized() noexcept override; -private: std::unique_ptr exporter_; }; diff --git a/sdk/test/metrics/exemplar/BUILD b/sdk/test/metrics/exemplar/BUILD index 9b11a64b9c..10973fd000 100644 --- a/sdk/test/metrics/exemplar/BUILD +++ b/sdk/test/metrics/exemplar/BUILD @@ -53,3 +53,20 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "filtered_exemplar_reservoir_test", + srcs = [ + "filtered_exemplar_reservoir_test.cc", + ], + tags = [ + "metrics", + "test", + ], + deps = [ + "//api", + "//sdk:headers", + "//sdk/src/metrics", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/metrics/exemplar/CMakeLists.txt b/sdk/test/metrics/exemplar/CMakeLists.txt index 04a715b2f8..32f755e747 100644 --- a/sdk/test/metrics/exemplar/CMakeLists.txt +++ b/sdk/test/metrics/exemplar/CMakeLists.txt @@ -1,9 +1,10 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -foreach(testname - no_exemplar_reservoir_test - aligned_histogram_bucket_exemplar_reservoir_test reservoir_cell_test) +foreach( + testname + no_exemplar_reservoir_test aligned_histogram_bucket_exemplar_reservoir_test + reservoir_cell_test filtered_exemplar_reservoir_test) add_executable(${testname} "${testname}.cc") target_link_libraries( ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} diff --git a/sdk/test/metrics/exemplar/always_sample_filter_test.cc b/sdk/test/metrics/exemplar/always_sample_filter_test.cc deleted file mode 100644 index f78cd412bd..0000000000 --- a/sdk/test/metrics/exemplar/always_sample_filter_test.cc +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#include - -#include "opentelemetry/context/context.h" -#include "opentelemetry/sdk/metrics/exemplar/filter.h" - -using namespace opentelemetry::sdk::metrics; - -TEST(AlwaysSampleFilter, SampleMeasurement) -{ - auto filter = opentelemetry::sdk::metrics::ExemplarFilter::GetAlwaysSampleFilter(); - ASSERT_TRUE( - filter->ShouldSampleMeasurement(1.0, MetricAttributes{}, opentelemetry::context::Context{})); - ASSERT_TRUE(filter->ShouldSampleMeasurement(static_cast(1), MetricAttributes{}, - opentelemetry::context::Context{})); -} diff --git a/sdk/test/metrics/exemplar/filtered_exemplar_reservoir_test.cc b/sdk/test/metrics/exemplar/filtered_exemplar_reservoir_test.cc new file mode 100644 index 0000000000..22d4616370 --- /dev/null +++ b/sdk/test/metrics/exemplar/filtered_exemplar_reservoir_test.cc @@ -0,0 +1,125 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW + +# include +# include +# include +# include +# include + +# include "opentelemetry/common/timestamp.h" +# include "opentelemetry/context/context.h" +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/nostd/span.h" +# include "opentelemetry/sdk/metrics/data/exemplar_data.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" +# include "opentelemetry/sdk/metrics/exemplar/reservoir.h" +# include "opentelemetry/trace/context.h" +# include "opentelemetry/trace/default_span.h" +# include "opentelemetry/trace/span.h" +# include "opentelemetry/trace/span_context.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_flags.h" +# include "opentelemetry/trace/trace_id.h" + +namespace +{ +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace trace_api = opentelemetry::trace; +namespace context_api = opentelemetry::context; +namespace nostd = opentelemetry::nostd; + +class CountingReservoir : public metrics_sdk::ExemplarReservoir +{ +public: + void OfferMeasurement(int64_t, + const metrics_sdk::MetricAttributes &, + const context_api::Context &, + const opentelemetry::common::SystemTimestamp &) noexcept override + { + ++offered; + } + + void OfferMeasurement(double, + const metrics_sdk::MetricAttributes &, + const context_api::Context &, + const opentelemetry::common::SystemTimestamp &) noexcept override + { + ++offered; + } + + std::vector> CollectAndReset( + const metrics_sdk::MetricAttributes &) noexcept override + { + return {}; + } + + int offered = 0; +}; + +context_api::Context ContextWithSpan(bool sampled) +{ + const uint8_t trace_id_bytes[trace_api::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + const uint8_t span_id_bytes[trace_api::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; + trace_api::SpanContext span_context( + trace_api::TraceId(trace_id_bytes), trace_api::SpanId(span_id_bytes), + sampled ? trace_api::TraceFlags(trace_api::TraceFlags::kIsSampled) : trace_api::TraceFlags(), + false); + nostd::shared_ptr span(new trace_api::DefaultSpan(span_context)); + context_api::Context context; + return trace_api::SetSpan(context, span); +} + +int ForwardedCount(metrics_sdk::ExemplarFilterType filter_type, const context_api::Context &context) +{ + auto *spy = new CountingReservoir(); + auto filtered = metrics_sdk::ExemplarReservoir::GetSimpleFilteredExemplarReservoir( + filter_type, nostd::shared_ptr(spy)); + filtered->OfferMeasurement(1.0, metrics_sdk::MetricAttributes{}, context, + std::chrono::system_clock::now()); + return spy->offered; +} +} // namespace + +TEST(FilteredExemplarReservoir, AlwaysOnOffersMeasurement) +{ + EXPECT_EQ(ForwardedCount(metrics_sdk::ExemplarFilterType::kAlwaysOn, context_api::Context{}), 1); +} + +TEST(FilteredExemplarReservoir, AlwaysOnOffersLongMeasurement) +{ + auto *spy = new CountingReservoir(); + auto filtered = metrics_sdk::ExemplarReservoir::GetSimpleFilteredExemplarReservoir( + metrics_sdk::ExemplarFilterType::kAlwaysOn, + nostd::shared_ptr(spy)); + filtered->OfferMeasurement(static_cast(1), metrics_sdk::MetricAttributes{}, + context_api::Context{}, std::chrono::system_clock::now()); + EXPECT_EQ(spy->offered, 1); +} + +TEST(FilteredExemplarReservoir, AlwaysOffDropsMeasurement) +{ + EXPECT_EQ(ForwardedCount(metrics_sdk::ExemplarFilterType::kAlwaysOff, context_api::Context{}), 0); +} + +TEST(FilteredExemplarReservoir, TraceBasedDropsWithoutSpan) +{ + EXPECT_EQ(ForwardedCount(metrics_sdk::ExemplarFilterType::kTraceBased, context_api::Context{}), + 0); +} + +TEST(FilteredExemplarReservoir, TraceBasedOffersWithSampledSpan) +{ + EXPECT_EQ(ForwardedCount(metrics_sdk::ExemplarFilterType::kTraceBased, ContextWithSpan(true)), 1); +} + +TEST(FilteredExemplarReservoir, TraceBasedDropsUnsampledSpan) +{ + EXPECT_EQ(ForwardedCount(metrics_sdk::ExemplarFilterType::kTraceBased, ContextWithSpan(false)), + 0); +} + +#endif // ENABLE_METRICS_EXEMPLAR_PREVIEW diff --git a/sdk/test/metrics/exemplar/with_trace_sample_filter_test.cc b/sdk/test/metrics/exemplar/with_trace_sample_filter_test.cc deleted file mode 100644 index fcf9d6344e..0000000000 --- a/sdk/test/metrics/exemplar/with_trace_sample_filter_test.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#include -#include "opentelemetry/context/context.h" -#include "opentelemetry/sdk/metrics/exemplar/filter.h" - -using namespace opentelemetry::sdk::metrics; - -TEST(WithTraceSampleFilter, SampleMeasurement) -{ - auto filter = opentelemetry::sdk::metrics::ExemplarFilter::GetWithTraceSampleFilter(); - ASSERT_FALSE( - filter->ShouldSampleMeasurement(1.0, MetricAttributes{}, opentelemetry::context::Context{})); - ASSERT_FALSE(filter->ShouldSampleMeasurement(static_cast(1), MetricAttributes{}, - opentelemetry::context::Context{})); -} diff --git a/sdk/test/metrics/measurements_benchmark.cc b/sdk/test/metrics/measurements_benchmark.cc index b534e84813..75a5c34d14 100644 --- a/sdk/test/metrics/measurements_benchmark.cc +++ b/sdk/test/metrics/measurements_benchmark.cc @@ -62,7 +62,7 @@ size_t GetBenchmarkThreads() const char *env = std::getenv("BENCHMARK_THREADS"); if (env != nullptr && env[0] != '\0') { - int val = std::atoi(env); + int val = static_cast(std::strtol(env, nullptr, 10)); if (val > 0) { return static_cast(val); diff --git a/sdk/test/metrics/meter_config_test.cc b/sdk/test/metrics/meter_config_test.cc index a13db6e3e2..cd41277a88 100644 --- a/sdk/test/metrics/meter_config_test.cc +++ b/sdk/test/metrics/meter_config_test.cc @@ -36,40 +36,73 @@ TEST(MeterConfig, CheckDefaultConfigWorksAccToSpec) /** Tests to verify the behavior of metrics_sdk::MeterConfig::Default */ -static std::pair attr1 = { - "accept_single_attr", true}; -static std::pair attr2 = { - "accept_second_attr", "some other attr"}; -static std::pair attr3 = { - "accept_third_attr", 3}; - -static instrumentation_scope::InstrumentationScope test_scope_1 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); -static instrumentation_scope::InstrumentationScope test_scope_2 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); -static instrumentation_scope::InstrumentationScope test_scope_3 = - *instrumentation_scope::InstrumentationScope::Create( - "test_scope_3", - "0", - "https://opentelemetry.io/schemas/v1.18.0"); -static instrumentation_scope::InstrumentationScope test_scope_4 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_4", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1}); -static instrumentation_scope::InstrumentationScope test_scope_5 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_5", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1, attr2, attr3}); +static std::pair & +GetAttr1() +{ + static std::pair value{ + "accept_single_attr", true}; + return value; +} +static std::pair & +GetAttr2() +{ + static std::pair value{ + "accept_second_attr", "some other attr"}; + return value; +} +static std::pair & +GetAttr3() +{ + static std::pair value{ + "accept_third_attr", 3}; + return value; +} + +static instrumentation_scope::InstrumentationScope &GetTestScope1() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope2() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope3() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_3", "0", "https://opentelemetry.io/schemas/v1.18.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope4() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_4", "0", "https://opentelemetry.io/schemas/v1.18.0", {GetAttr1()}); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope5() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_5", "0", "https://opentelemetry.io/schemas/v1.18.0", + {GetAttr1(), GetAttr2(), GetAttr3()}); + return value; +} // This array could also directly contain the reference types, but that leads to 'uninitialized // value was created by heap allocation' errors in Valgrind memcheck. This is a bug in Googletest // library, see https://github.com/google/googletest/issues/3805#issuecomment-1397301790 for more // details. Using pointers is a workaround to prevent the Valgrind warnings. -const std::array instrumentation_scopes = { - &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, -}; +static const std::array & +GetInstrumentationScopes() +{ + static const std::array value = { + &GetTestScope1(), &GetTestScope2(), &GetTestScope3(), &GetTestScope4(), &GetTestScope5(), + }; + return value; +} + +namespace +{ // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultMeterConfiguratorTestFixture @@ -90,4 +123,6 @@ TEST_P(DefaultMeterConfiguratorTestFixture, VerifyDefaultConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultMeterConfiguratorTestFixture, - ::testing::ValuesIn(instrumentation_scopes)); + ::testing::ValuesIn(GetInstrumentationScopes())); + +} // namespace diff --git a/sdk/test/metrics/metric_test_stress.cc b/sdk/test/metrics/metric_test_stress.cc index 3673af988e..0a4ac6faac 100644 --- a/sdk/test/metrics/metric_test_stress.cc +++ b/sdk/test/metrics/metric_test_stress.cc @@ -35,6 +35,9 @@ using namespace opentelemetry; using namespace opentelemetry::sdk::instrumentationscope; using namespace opentelemetry::sdk::metrics; +namespace +{ + class MockMetricExporterForStress : public opentelemetry::sdk::metrics::PushMetricExporter { public: @@ -175,3 +178,5 @@ TEST(HistogramStress, UnsignedInt64) ASSERT_EQ(expected_count, collected_count); ASSERT_EQ(*expected_sum, collected_sum); } + +} // namespace diff --git a/sdk/test/metrics/multi_metric_storage_test.cc b/sdk/test/metrics/multi_metric_storage_test.cc index 5c1a284e57..3168f54c1d 100644 --- a/sdk/test/metrics/multi_metric_storage_test.cc +++ b/sdk/test/metrics/multi_metric_storage_test.cc @@ -14,6 +14,9 @@ using namespace opentelemetry; using namespace opentelemetry::sdk::metrics; +namespace +{ + class TestMetricStorage : public SyncWritableMetricStorage { public: @@ -62,3 +65,5 @@ TEST(MultiMetricStorageTest, BasicTests) EXPECT_EQ(static_cast(storage.get())->num_calls_long, 3); EXPECT_EQ(static_cast(storage.get())->num_calls_double, 1); } + +} // namespace diff --git a/sdk/test/metrics/periodic_exporting_metric_reader_test.cc b/sdk/test/metrics/periodic_exporting_metric_reader_test.cc index 5ac001b0a8..134e67c834 100644 --- a/sdk/test/metrics/periodic_exporting_metric_reader_test.cc +++ b/sdk/test/metrics/periodic_exporting_metric_reader_test.cc @@ -29,6 +29,9 @@ using namespace opentelemetry; using namespace opentelemetry::sdk::instrumentationscope; using namespace opentelemetry::sdk::metrics; +namespace +{ + class MockPushMetricExporter : public PushMetricExporter { public: @@ -168,3 +171,5 @@ TEST(PeriodicExportingMetricReaderOptions, UsesDefault) EXPECT_EQ(options.export_interval_millis, std::chrono::milliseconds(60000)); EXPECT_EQ(options.export_timeout_millis, std::chrono::milliseconds(30000)); } + +} // namespace diff --git a/sdk/test/metrics/sum_aggregation_test.cc b/sdk/test/metrics/sum_aggregation_test.cc index 99aa5df6d5..7aaed33c9d 100644 --- a/sdk/test/metrics/sum_aggregation_test.cc +++ b/sdk/test/metrics/sum_aggregation_test.cc @@ -194,7 +194,7 @@ TEST(HistogramToSumFilterAttributesWithCardinalityLimit, Double) { for (const MetricData &md : smd.metric_data_) { - EXPECT_EQ(cardinality_limit, md.point_data_attr_.size()); + EXPECT_EQ(cardinality_limit + 1, md.point_data_attr_.size()); for (size_t i = 0; i < md.point_data_attr_.size(); i++) { EXPECT_EQ(1, md.point_data_attr_[i].attributes.size()); @@ -378,11 +378,8 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double) for (const MetricData &md : smd.metric_data_) { // When the number of unique attribute sets exceeds the cardinality limit, the - // implementation emits up to (cardinality_limit - 1) unique sets and one overflow set, - // resulting in a total of cardinality_limit sets. This test checks that the number of - // emitted attribute sets is within the expected range, accounting for the overflow - // behavior. - EXPECT_EQ(cardinality_limit, md.point_data_attr_.size()); + // implementation emits cardinality_limit unique sets and one overflow set. + EXPECT_EQ(cardinality_limit + 1, md.point_data_attr_.size()); for (size_t i = 0; i < md.point_data_attr_.size(); i++) { EXPECT_EQ(1, md.point_data_attr_[i].attributes.size()); @@ -414,6 +411,9 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double) } } +namespace +{ + class UpDownCounterToSumFixture : public ::testing::TestWithParam {}; @@ -471,3 +471,5 @@ INSTANTIATE_TEST_SUITE_P(UpDownCounterToSum, UpDownCounterToSumFixture, ::testing::Values(true, false)); #endif + +} // namespace diff --git a/sdk/test/metrics/sync_instruments_test.cc b/sdk/test/metrics/sync_instruments_test.cc index a6402c5d66..5128a43d80 100644 --- a/sdk/test/metrics/sync_instruments_test.cc +++ b/sdk/test/metrics/sync_instruments_test.cc @@ -11,18 +11,14 @@ #include "opentelemetry/common/key_value_iterable_view.h" #include "opentelemetry/context/context.h" #include "opentelemetry/nostd/utility.h" -#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/metrics/instruments.h" #include "opentelemetry/sdk/metrics/state/metric_storage.h" #include "opentelemetry/sdk/metrics/state/multi_metric_storage.h" #include "opentelemetry/sdk/metrics/sync_instruments.h" using namespace opentelemetry; -using namespace opentelemetry::sdk::instrumentationscope; using namespace opentelemetry::sdk::metrics; -static auto instrumentation_scope = InstrumentationScope::Create("opentelemetry-cpp", "0.1.0"); - using M = std::map; TEST(SyncInstruments, LongCounter) diff --git a/sdk/test/metrics/sync_metric_storage_counter_test.cc b/sdk/test/metrics/sync_metric_storage_counter_test.cc index 89e8df46ff..f31599a8af 100644 --- a/sdk/test/metrics/sync_metric_storage_counter_test.cc +++ b/sdk/test/metrics/sync_metric_storage_counter_test.cc @@ -27,7 +27,6 @@ #include "opentelemetry/sdk/metrics/view/attributes_processor.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -53,7 +52,7 @@ TEST_P(WritableMetricStorageTestFixture, LongCounterSumAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -193,7 +192,7 @@ TEST_P(WritableMetricStorageTestFixture, DoubleCounterSumAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -329,7 +328,7 @@ TEST(SyncMetricStorageTest, DeltaCounterStartTimestampTracksEmptyCycles) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -407,7 +406,7 @@ TEST(SyncMetricStorageTest, DeltaCounterFirstIntervalUsesInstrumentCreationTime) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); auto after_creation = std::chrono::system_clock::now(); @@ -460,7 +459,7 @@ TEST(SyncMetricStorageTest, DeltaCounterMultiCollectorFirstIntervalUsesInstrumen opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); auto after_creation = std::chrono::system_clock::now(); diff --git a/sdk/test/metrics/sync_metric_storage_gauge_test.cc b/sdk/test/metrics/sync_metric_storage_gauge_test.cc index 1dc0a4d2cd..2675a9aaf8 100644 --- a/sdk/test/metrics/sync_metric_storage_gauge_test.cc +++ b/sdk/test/metrics/sync_metric_storage_gauge_test.cc @@ -26,7 +26,6 @@ # include "opentelemetry/nostd/variant.h" # include "opentelemetry/sdk/metrics/data/metric_data.h" # include "opentelemetry/sdk/metrics/data/point_data.h" -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" # include "opentelemetry/sdk/metrics/state/metric_collector.h" # include "opentelemetry/sdk/metrics/state/sync_metric_storage.h" @@ -58,7 +57,7 @@ TEST_P(WritableMetricStorageTestFixture, LongGaugeLastValueAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kLastValue, default_attributes_processor, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif nullptr); @@ -142,7 +141,7 @@ TEST_P(WritableMetricStorageTestFixture, DoubleGaugeLastValueAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kLastValue, default_attributes_processor, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif nullptr); @@ -236,7 +235,7 @@ TEST_P(WritableMetricStorageDeltaMultiReaderTestFixture, opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kLastValue, default_attributes_processor, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif nullptr); auto after_creation = std::chrono::system_clock::now(); @@ -417,7 +416,7 @@ TEST_P(WritableMetricStorageDeltaMultiReaderTestFixture, opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kLastValue, default_attributes_processor, # ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), # endif nullptr); auto after_creation = std::chrono::system_clock::now(); diff --git a/sdk/test/metrics/sync_metric_storage_histogram_test.cc b/sdk/test/metrics/sync_metric_storage_histogram_test.cc index 9f608bcd16..d363a6413f 100644 --- a/sdk/test/metrics/sync_metric_storage_histogram_test.cc +++ b/sdk/test/metrics/sync_metric_storage_histogram_test.cc @@ -27,13 +27,15 @@ #include "opentelemetry/sdk/metrics/view/attributes_processor.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif using namespace opentelemetry::sdk::metrics; using namespace opentelemetry::common; +namespace +{ + class WritableMetricStorageHistogramTestFixture : public ::testing::TestWithParam {}; @@ -54,7 +56,7 @@ TEST_P(WritableMetricStorageHistogramTestFixture, LongHistogram) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kHistogram, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -195,7 +197,7 @@ TEST_P(WritableMetricStorageHistogramTestFixture, DoubleHistogram) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kHistogram, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -343,7 +345,7 @@ TEST_P(WritableMetricStorageHistogramTestFixture, Base2ExponentialDoubleHistogra opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kBase2ExponentialHistogram, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -538,3 +540,5 @@ INSTANTIATE_TEST_SUITE_P(WritableMetricStorageHistogramTestBase2ExponentialDoubl WritableMetricStorageHistogramTestFixture, ::testing::Values(AggregationTemporality::kCumulative, AggregationTemporality::kDelta)); + +} // namespace diff --git a/sdk/test/metrics/sync_metric_storage_up_down_counter_test.cc b/sdk/test/metrics/sync_metric_storage_up_down_counter_test.cc index cf54165341..3b36aaa940 100644 --- a/sdk/test/metrics/sync_metric_storage_up_down_counter_test.cc +++ b/sdk/test/metrics/sync_metric_storage_up_down_counter_test.cc @@ -26,7 +26,6 @@ #include "opentelemetry/sdk/metrics/view/attributes_processor.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" # include "opentelemetry/sdk/metrics/exemplar/reservoir.h" #endif @@ -52,7 +51,7 @@ TEST_P(WritableMetricStorageTestFixture, LongUpDownCounterSumAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); @@ -202,7 +201,7 @@ TEST_P(WritableMetricStorageTestFixture, DoubleUpDownCounterSumAggregation) opentelemetry::sdk::metrics::SyncMetricStorage storage( instr_desc, AggregationType::kSum, default_attributes_processor, #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW - ExemplarFilterType::kAlwaysOff, ExemplarReservoir::GetNoExemplarReservoir(), + ExemplarReservoir::GetNoExemplarReservoir(), #endif nullptr); diff --git a/sdk/test/resource/resource_test.cc b/sdk/test/resource/resource_test.cc index 696509f892..101a7a5a32 100644 --- a/sdk/test/resource/resource_test.cc +++ b/sdk/test/resource/resource_test.cc @@ -27,6 +27,9 @@ using namespace opentelemetry::sdk::resource; namespace nostd = opentelemetry::nostd; namespace semconv = opentelemetry::semconv; +namespace +{ + class TestResource : public Resource { public: @@ -292,3 +295,5 @@ TEST(ResourceTest, DerivedResourceDetector) EXPECT_EQ(resource.GetSchemaURL(), detector.schema_url); EXPECT_TRUE(received_attributes.find("key") != received_attributes.end()); } + +} // namespace diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 7f536dc57e..3e4393345a 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -205,3 +205,19 @@ otel_cc_benchmark( "//sdk/src/trace", ], ) + +otel_cc_benchmark( + name = "span_data_benchmark", + srcs = ["span_data_benchmark.cc"], + tags = [ + "benchmark", + "test", + "trace", + ], + deps = [ + "//exporters/memory:in_memory_span_exporter", + "//sdk/src/resource", + "//sdk/src/trace", + "//test_common:headers", + ], +) diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index ca0ae383bc..e299deb502 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -36,4 +36,14 @@ if(WITH_BENCHMARK) sampler_benchmark benchmark::benchmark ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace opentelemetry_resources opentelemetry_exporter_in_memory) + + add_executable(span_data_benchmark span_data_benchmark.cc) + target_link_libraries( + span_data_benchmark + benchmark::benchmark + ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_trace + opentelemetry_resources + opentelemetry_exporter_in_memory + opentelemetry_test_common) endif() diff --git a/sdk/test/trace/composable_sampler_test.cc b/sdk/test/trace/composable_sampler_test.cc index 8c8641511f..e5885e7ab7 100644 --- a/sdk/test/trace/composable_sampler_test.cc +++ b/sdk/test/trace/composable_sampler_test.cc @@ -20,9 +20,9 @@ #include "opentelemetry/sdk/trace/samplers/composable_always_off.h" #include "opentelemetry/sdk/trace/samplers/composable_always_on.h" #include "opentelemetry/sdk/trace/samplers/composable_parent_threshold.h" +#include "opentelemetry/sdk/trace/samplers/composable_probability.h" #include "opentelemetry/sdk/trace/samplers/composable_rule_based.h" #include "opentelemetry/sdk/trace/samplers/composable_sampler.h" -#include "opentelemetry/sdk/trace/samplers/composable_trace_id_ratio.h" #include "opentelemetry/sdk/trace/samplers/composite_sampler.h" #include "opentelemetry/sdk/trace/samplers/composite_sampler_factory.h" #include "opentelemetry/sdk/trace/samplers/predicate.h" @@ -40,9 +40,9 @@ namespace common = opentelemetry::common; using opentelemetry::sdk::trace::ComposableAlwaysOffSampler; using opentelemetry::sdk::trace::ComposableAlwaysOnSampler; using opentelemetry::sdk::trace::ComposableParentThresholdSampler; +using opentelemetry::sdk::trace::ComposableProbabilitySampler; using opentelemetry::sdk::trace::ComposableRuleBasedSampler; using opentelemetry::sdk::trace::ComposableSampler; -using opentelemetry::sdk::trace::ComposableTraceIdRatioBasedSampler; using opentelemetry::sdk::trace::CompositeSampler; using opentelemetry::sdk::trace::CompositeSamplerFactory; using opentelemetry::sdk::trace::Decision; @@ -207,12 +207,11 @@ TEST(ComposableSampler, AlwaysOffDrops) TEST(ComposableSampler, TraceIdRatioBoundaries) { auto always = - CompositeSamplerFactory::Create(std::make_shared(1.0)); + CompositeSamplerFactory::Create(std::make_shared(1.0)); EXPECT_EQ(Decision::RECORD_AND_SAMPLE, Sample(*always, trace_api::SpanContext::GetInvalid(), MakeTraceId(0x00))); - auto never = - CompositeSamplerFactory::Create(std::make_shared(0.0)); + auto never = CompositeSamplerFactory::Create(std::make_shared(0.0)); EXPECT_EQ(Decision::DROP, Sample(*never, trace_api::SpanContext::GetInvalid(), MakeTraceId(0xFF))); } @@ -220,7 +219,7 @@ TEST(ComposableSampler, TraceIdRatioBoundaries) TEST(ComposableSampler, TraceIdRatioUsesTraceRandomness) { auto sampler = - CompositeSamplerFactory::Create(std::make_shared(0.5)); + CompositeSamplerFactory::Create(std::make_shared(0.5)); // Threshold for p=0.5 is 2^55, encoded as th:8. Maximum randomness keeps. opentelemetry::sdk::trace::SamplingResult kept; @@ -236,7 +235,7 @@ TEST(ComposableSampler, TraceIdRatioUsesTraceRandomness) TEST(ComposableSampler, ExplicitRandomnessOverridesTraceId) { auto sampler = - CompositeSamplerFactory::Create(std::make_shared(0.5)); + CompositeSamplerFactory::Create(std::make_shared(0.5)); // rv present and at maximum: keep regardless of the (zero) trace id bits, and // the explicit randomness must be preserved in the outgoing tracestate. @@ -315,8 +314,8 @@ TEST(ComposableSampler, RuleBasedNoMatchDrops) TEST(ComposableSampler, Descriptions) { - ComposableTraceIdRatioBasedSampler ratio(0.5); - EXPECT_EQ("ComposableTraceIdRatioBasedSampler{0.500000}", std::string(ratio.GetDescription())); + ComposableProbabilitySampler ratio(0.5); + EXPECT_EQ("ComposableProbabilitySampler{0.500000}", std::string(ratio.GetDescription())); CompositeSampler composite(std::make_shared()); EXPECT_EQ("CompositeSampler{ComposableAlwaysOnSampler}", std::string(composite.GetDescription())); @@ -365,13 +364,12 @@ TEST(ComposableSampler, DropsTrailingSubkeysOverSizeLimit) TEST(ComposableSampler, RatioClampedToValidRange) { - auto over = - CompositeSamplerFactory::Create(std::make_shared(2.0)); + auto over = CompositeSamplerFactory::Create(std::make_shared(2.0)); EXPECT_EQ(Decision::RECORD_AND_SAMPLE, Sample(*over, trace_api::SpanContext::GetInvalid(), MakeTraceId(0x00))); auto under = - CompositeSamplerFactory::Create(std::make_shared(-1.0)); + CompositeSamplerFactory::Create(std::make_shared(-1.0)); EXPECT_EQ(Decision::DROP, Sample(*under, trace_api::SpanContext::GetInvalid(), MakeTraceId(0xFF))); } diff --git a/sdk/test/trace/simple_processor_test.cc b/sdk/test/trace/simple_processor_test.cc index 755fd483e8..b88e89eb72 100644 --- a/sdk/test/trace/simple_processor_test.cc +++ b/sdk/test/trace/simple_processor_test.cc @@ -42,6 +42,9 @@ TEST(SimpleProcessor, ToInMemorySpanExporter) EXPECT_TRUE(processor.Shutdown()); } +namespace +{ + // An exporter that does nothing but record (and give back ) the # of times Shutdown was called. class RecordShutdownExporter final : public SpanExporter { @@ -141,3 +144,5 @@ TEST(SimpleSpanProcessor, ForceFlushFail) std::unique_ptr{new FailShutDownForceFlushExporter()}); EXPECT_EQ(false, processor.ForceFlush()); } + +} // namespace diff --git a/sdk/test/trace/span_data_benchmark.cc b/sdk/test/trace/span_data_benchmark.cc new file mode 100644 index 0000000000..6bf0418a01 --- /dev/null +++ b/sdk/test/trace/span_data_benchmark.cc @@ -0,0 +1,213 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// clang-format off +// +// ~/build/sdk/test/trace/span_data_benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only=true +// 2026-07-01T15:59:10+00:00 +// Running /home/devuser/build/sdk/test/trace/span_data_benchmark +// Run on (32 X 5700 MHz CPU s) +// CPU Caches: +// L1 Data 48 KiB (x16) +// L1 Instruction 32 KiB (x16) +// L2 Unified 2048 KiB (x16) +// L3 Unified 36864 KiB (x1) +// Load Average: 1.35, 1.52, 1.62 +// ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. +// -------------------------------------------------------------------------------------------------------------- +// Benchmark Time CPU Iterations +// -------------------------------------------------------------------------------------------------------------- +// SpanDataFixture/RecordMinimalSpan_mean 341 ns 343 ns 5 +// SpanDataFixture/RecordMinimalSpan_median 341 ns 343 ns 5 +// SpanDataFixture/RecordMinimalSpan_stddev 0.950 ns 0.988 ns 5 +// SpanDataFixture/RecordMinimalSpan_cv 0.28 % 0.29 % 5 +// SpanDataFixture/RecordNominalSpan_mean 581 ns 584 ns 5 +// SpanDataFixture/RecordNominalSpan_median 588 ns 590 ns 5 +// SpanDataFixture/RecordNominalSpan_stddev 15.3 ns 15.1 ns 5 +// SpanDataFixture/RecordNominalSpan_cv 2.63 % 2.59 % 5 +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_mean 390 ns 394 ns 5 items_per_second=2.5415M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_median 391 ns 395 ns 5 items_per_second=2.53367M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_stddev 4.17 ns 4.40 ns 5 items_per_second=28.4487k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:1_cv 1.07 % 1.12 % 5 items_per_second=1.12% +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_mean 705 ns 708 ns 5 items_per_second=14.1257M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_median 707 ns 711 ns 5 items_per_second=14.0739M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_stddev 6.35 ns 6.36 ns 5 items_per_second=127.942k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:10_cv 0.90 % 0.90 % 5 items_per_second=0.91% +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_mean 7479 ns 7484 ns 5 items_per_second=17.1122M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_median 7539 ns 7543 ns 5 items_per_second=16.969M/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_stddev 189 ns 189 ns 5 items_per_second=438.424k/s +// SpanDataFixture/RecordSpanWithAttributes/attribute_count:128_cv 2.53 % 2.53 % 5 items_per_second=2.56% +// SpanDataFixture/RecordSpanWithEvents/event_count:1_mean 456 ns 459 ns 5 items_per_second=2.17751M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_median 456 ns 459 ns 5 items_per_second=2.178M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_stddev 2.89 ns 3.02 ns 5 items_per_second=14.3182k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:1_cv 0.63 % 0.66 % 5 items_per_second=0.66% +// SpanDataFixture/RecordSpanWithEvents/event_count:10_mean 1584 ns 1587 ns 5 items_per_second=6.30345M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_median 1580 ns 1583 ns 5 items_per_second=6.318M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_stddev 24.3 ns 24.4 ns 5 items_per_second=95.4207k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:10_cv 1.54 % 1.54 % 5 items_per_second=1.51% +// SpanDataFixture/RecordSpanWithEvents/event_count:128_mean 16434 ns 16438 ns 5 items_per_second=7.78791M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_median 16429 ns 16432 ns 5 items_per_second=7.78954M/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_stddev 211 ns 211 ns 5 items_per_second=100.089k/s +// SpanDataFixture/RecordSpanWithEvents/event_count:128_cv 1.28 % 1.28 % 5 items_per_second=1.29% +// SpanDataFixture/RecordSpanWithLinks/link_count:1_mean 435 ns 437 ns 5 items_per_second=2.29077M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_median 435 ns 436 ns 5 items_per_second=2.29368M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_stddev 2.12 ns 2.39 ns 5 items_per_second=12.5108k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:1_cv 0.49 % 0.55 % 5 items_per_second=0.55% +// SpanDataFixture/RecordSpanWithLinks/link_count:10_mean 1368 ns 1371 ns 5 items_per_second=7.2967M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_median 1375 ns 1378 ns 5 items_per_second=7.25823M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_stddev 22.3 ns 22.4 ns 5 items_per_second=121.346k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:10_cv 1.63 % 1.63 % 5 items_per_second=1.66% +// SpanDataFixture/RecordSpanWithLinks/link_count:128_mean 15304 ns 15308 ns 5 items_per_second=8.38477M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_median 14933 ns 14937 ns 5 items_per_second=8.56932M/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_stddev 930 ns 930 ns 5 items_per_second=474.386k/s +// SpanDataFixture/RecordSpanWithLinks/link_count:128_cv 6.08 % 6.08 % 5 items_per_second=5.66% +// +// clang-format on + +#include + +#include +#include +#include +#include +#include + +#include "opentelemetry/exporters/memory/in_memory_span_exporter.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/test_common/sdk/trace/test_utils.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" + +namespace trace_sdk = opentelemetry::sdk::trace; +namespace test_utils = opentelemetry::test_common; + +namespace +{ + +class SpanDataFixture : public benchmark::Fixture +{ +public: + using benchmark::Fixture::SetUp; + + void SetUp(benchmark::State &) override + { + auto exporter = std::make_unique(); + auto processor = std::make_unique(std::move(exporter)); + provider_ = std::make_shared(std::move(processor)); + tracer_ = provider_->GetTracer(test_utils::TestScope().GetName(), + test_utils::TestScope().GetVersion(), + test_utils::TestScope().GetSchemaURL()); + test_utils::InitializeSpanTestData(); + } + +protected: + std::shared_ptr provider_; + opentelemetry::nostd::shared_ptr tracer_; +}; + +} // namespace + +// Baseline: start a span and end it immediately with no attributes, links, or events. +BENCHMARK_DEFINE_F(SpanDataFixture, RecordMinimalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartMinimalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordMinimalSpan)->Unit(benchmark::kNanosecond); + +// RecordNominalSpan span: representative mix of attribute types passed at creation. +BENCHMARK_DEFINE_F(SpanDataFixture, RecordNominalSpan)(benchmark::State &state) +{ + for (auto _ : state) + { + auto span = test_utils::StartNominalSpan(*tracer_); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordNominalSpan)->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithAttributes)(benchmark::State &state) +{ + const int64_t attribute_count = state.range(0); + const std::vector attributes = + test_utils::MakeAttributes(attribute_count); + for (auto _ : state) + { + auto span = test_utils::StartSpanWithAttributes(*tracer_, attributes); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * attribute_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithAttributes) + ->ArgName("attribute_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanAttributeLimit) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithEvents)(benchmark::State &state) +{ + const int64_t event_count = state.range(0); + + for (auto _ : state) + { + auto span = test_utils::StartSpanWithEvents(*tracer_, static_cast(event_count)); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * event_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithEvents) + ->ArgName("event_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanEventLimit) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_DEFINE_F(SpanDataFixture, RecordSpanWithLinks)(benchmark::State &state) +{ + const int64_t link_count = state.range(0); + const std::vector link_entries = + test_utils::MakeLinkEntries(static_cast(link_count)); + + for (auto _ : state) + { + auto span = test_utils::StartSpanWithLinks(*tracer_, link_entries); + span->End(); + state.PauseTiming(); + provider_->ForceFlush(); + state.ResumeTiming(); + } + state.SetItemsProcessed(state.iterations() * link_count); +} +BENCHMARK_REGISTER_F(SpanDataFixture, RecordSpanWithLinks) + ->ArgName("link_count") + ->Arg(1) + ->Arg(10) + ->Arg(test_utils::kSpanLinkLimit) + ->Unit(benchmark::kNanosecond); + +int main(int argc, char **argv) +{ + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index 3e3c27ce4d..d7aad3353c 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "opentelemetry/common/key_value_iterable_view.h" @@ -16,6 +17,7 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" @@ -149,3 +151,144 @@ TEST(SpanData, Links) values[i]); } } + +TEST(SpanData, SpanLimits) +{ + SpanData recordable; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 1; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 1; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 1; + + constexpr const char *kKey1 = "one"; + constexpr const char *kKey2 = "two"; + constexpr const char *kValue1 = "1234"; + constexpr const char *kValue2 = "5678"; + constexpr const char *kValue3 = "9012"; + + std::map attribute_collection{{kKey1, kValue1}, {kKey2, kValue2}}; + + recordable.SetSpanLimits(limits); + + // span attribute count and length limits + recordable.SetAttribute(kKey1, kValue1); + recordable.SetAttribute(kKey2, kValue2); + recordable.SetAttribute(kKey1, kValue3); // overwrite existing attribute + + EXPECT_EQ(recordable.GetAttributes().size(), 1); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 1); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at(kKey1)), "90"); + + // event count and per-event attribute count limits + recordable.AddEvent("event1", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + recordable.AddEvent("event2", std::chrono::system_clock::now(), + common::MakeAttributes(attribute_collection)); + + ASSERT_EQ(recordable.GetEvents().size(), 1); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 1); + const auto event = recordable.GetEvents().at(0); + EXPECT_EQ(event.GetDroppedAttributesCount(), 1); + EXPECT_EQ(event.GetName(), "event1"); + const auto &event_attributes = event.GetAttributes(); + EXPECT_EQ(event_attributes.size(), 1); + EXPECT_EQ(opentelemetry::nostd::get(event_attributes.at(kKey1)), "12"); + + // link count and per-link attribute count limits + recordable.AddLink(trace_api::SpanContext::GetInvalid(), + common::MakeAttributes(attribute_collection)); + recordable.AddLink(trace_api::SpanContext::GetInvalid(), + common::MakeAttributes(attribute_collection)); + ASSERT_EQ(recordable.GetLinks().size(), 1); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 1); + const auto link = recordable.GetLinks().at(0); + EXPECT_EQ(link.GetDroppedAttributesCount(), 1); + const auto &link_attributes = link.GetAttributes(); + EXPECT_EQ(link_attributes.size(), 1); + EXPECT_EQ(opentelemetry::nostd::get(link_attributes.at(kKey1)), "12"); +} + +TEST(SpanData, SpanLimitsNoLimitDefault) +{ + constexpr std::uint32_t kMaxCount = 500; + SpanData recordable; + std::map attributes; + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + attributes["attribute_" + std::to_string(i)] = std::to_string(i); + } + + for (std::uint32_t i = 0; i < kMaxCount; ++i) + { + recordable.SetAttribute("attribute_" + std::to_string(i), i); + recordable.AddEvent("event_" + std::to_string(i), std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + recordable.AddLink(trace_api::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + } + + EXPECT_EQ(recordable.GetAttributes().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(recordable.GetEvents().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 0u); + EXPECT_EQ(recordable.GetLinks().size(), kMaxCount); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 0u); + EXPECT_EQ(recordable.GetEvents().at(0).GetDroppedAttributesCount(), 0u); + EXPECT_EQ(recordable.GetLinks().at(0).GetDroppedAttributesCount(), 0u); +} + +TEST(SpanData, SpanLimitsDuplicateAttributeKeys) +{ + SpanData recordable; + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 2; + limits.link_count_limit = 1; + limits.link_attribute_count_limit = 2; + limits.event_count_limit = 1; + limits.event_attribute_count_limit = 2; + + recordable.SetSpanLimits(limits); + + // Create three attributes. One has a duplicate key. + std::vector> attributes{ + {"attribute_one", "AA"}, {"attribute_two", "BB"}, {"attribute_one", "CC"}}; + + for (const auto &keyvalue : attributes) + { + recordable.SetAttribute(keyvalue.first, keyvalue.second); + } + + recordable.AddEvent("event", std::chrono::system_clock::now(), + common::MakeAttributes(attributes)); + + recordable.AddLink(trace_api::SpanContext::GetInvalid(), common::MakeAttributes(attributes)); + + EXPECT_EQ(recordable.GetAttributes().size(), 2u); + EXPECT_EQ(recordable.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at("attribute_one")), + "CC"); + EXPECT_EQ(opentelemetry::nostd::get(recordable.GetAttributes().at("attribute_two")), + "BB"); + + EXPECT_EQ(recordable.GetEvents().size(), 1u); + EXPECT_EQ(recordable.GetDroppedEventsCount(), 0u); + const auto &event = recordable.GetEvents().at(0); + EXPECT_EQ(event.GetAttributes().size(), 2u); + EXPECT_EQ(event.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(event.GetAttributes().at("attribute_one")), + "CC"); + EXPECT_EQ(opentelemetry::nostd::get(event.GetAttributes().at("attribute_two")), + "BB"); + + EXPECT_EQ(recordable.GetLinks().size(), 1u); + EXPECT_EQ(recordable.GetDroppedLinksCount(), 0u); + const auto &link = recordable.GetLinks().at(0); + EXPECT_EQ(link.GetAttributes().size(), 2u); + EXPECT_EQ(link.GetDroppedAttributesCount(), 0u); + EXPECT_EQ(opentelemetry::nostd::get(link.GetAttributes().at("attribute_one")), "CC"); + EXPECT_EQ(opentelemetry::nostd::get(link.GetAttributes().at("attribute_two")), "BB"); +} diff --git a/sdk/test/trace/tracer_config_test.cc b/sdk/test/trace/tracer_config_test.cc index 4f342ba11c..f890d8ecb6 100644 --- a/sdk/test/trace/tracer_config_test.cc +++ b/sdk/test/trace/tracer_config_test.cc @@ -36,40 +36,73 @@ TEST(TracerConfig, CheckDefaultConfigWorksAccToSpec) /** Tests to verify the behavior of trace_sdk::TracerConfig::DefaultConfigurator */ -static std::pair attr1 = { - "accept_single_attr", true}; -static std::pair attr2 = { - "accept_second_attr", "some other attr"}; -static std::pair attr3 = { - "accept_third_attr", 3}; - -static instrumentation_scope::InstrumentationScope test_scope_1 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); -static instrumentation_scope::InstrumentationScope test_scope_2 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); -static instrumentation_scope::InstrumentationScope test_scope_3 = - *instrumentation_scope::InstrumentationScope::Create( - "test_scope_3", - "0", - "https://opentelemetry.io/schemas/v1.18.0"); -static instrumentation_scope::InstrumentationScope test_scope_4 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_4", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1}); -static instrumentation_scope::InstrumentationScope test_scope_5 = - *instrumentation_scope::InstrumentationScope::Create("test_scope_5", - "0", - "https://opentelemetry.io/schemas/v1.18.0", - {attr1, attr2, attr3}); +static std::pair & +GetAttr1() +{ + static std::pair value{ + "accept_single_attr", true}; + return value; +} +static std::pair & +GetAttr2() +{ + static std::pair value{ + "accept_second_attr", "some other attr"}; + return value; +} +static std::pair & +GetAttr3() +{ + static std::pair value{ + "accept_third_attr", 3}; + return value; +} + +static instrumentation_scope::InstrumentationScope &GetTestScope1() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_1"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope2() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create("test_scope_2", "1.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope3() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_3", "0", "https://opentelemetry.io/schemas/v1.18.0"); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope4() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_4", "0", "https://opentelemetry.io/schemas/v1.18.0", {GetAttr1()}); + return value; +} +static instrumentation_scope::InstrumentationScope &GetTestScope5() +{ + static auto value = *instrumentation_scope::InstrumentationScope::Create( + "test_scope_5", "0", "https://opentelemetry.io/schemas/v1.18.0", + {GetAttr1(), GetAttr2(), GetAttr3()}); + return value; +} // This array could also directly contain the reference types, but that leads to 'uninitialized // value was created by heap allocation' errors in Valgrind memcheck. This is a bug in Googletest // library, see https://github.com/google/googletest/issues/3805#issuecomment-1397301790 for more // details. Using pointers is a workaround to prevent the Valgrind warnings. -const std::array instrumentation_scopes = { - &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, -}; +static const std::array & +GetInstrumentationScopes() +{ + static const std::array value = { + &GetTestScope1(), &GetTestScope2(), &GetTestScope3(), &GetTestScope4(), &GetTestScope5(), + }; + return value; +} + +namespace +{ // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultTracerConfiguratorTestFixture @@ -90,4 +123,6 @@ TEST_P(DefaultTracerConfiguratorTestFixture, VerifyDefaultConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultTracerConfiguratorTestFixture, - ::testing::ValuesIn(instrumentation_scopes)); + ::testing::ValuesIn(GetInstrumentationScopes())); + +} // namespace diff --git a/sdk/test/trace/tracer_provider_set_test.cc b/sdk/test/trace/tracer_provider_set_test.cc index 395b173e93..3991582264 100644 --- a/sdk/test/trace/tracer_provider_set_test.cc +++ b/sdk/test/trace/tracer_provider_set_test.cc @@ -26,7 +26,7 @@ namespace trace_sdk = opentelemetry::sdk::trace; class TestProvider : public TracerProvider { - +public: #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::shared_ptr GetTracer( nostd::string_view /* name */, diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index 687f63d07f..66efbbf522 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -2,13 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include -#include -#include - #include +#include +#include #include +#include +#include #include +#include +#include #include "opentelemetry/common/macros.h" #include "opentelemetry/exporters/memory/in_memory_span_exporter.h" @@ -26,6 +28,7 @@ #include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -35,7 +38,6 @@ #include "opentelemetry/trace/tracer.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -# include # include # include # include @@ -518,3 +520,66 @@ TEST(TracerProvider, UpdateTracerConfiguratorConcurrentStartSpan) EXPECT_TRUE(tracer->Enabled()); #endif } + +TEST(TracerProvider, SpanLimitsSpecDefaults) +{ + SpanLimits limits; + EXPECT_EQ(limits.attribute_count_limit, 128u); + EXPECT_EQ(limits.event_count_limit, 128u); + EXPECT_EQ(limits.link_count_limit, 128u); + EXPECT_EQ(limits.event_attribute_count_limit, 128u); + EXPECT_EQ(limits.link_attribute_count_limit, 128u); + EXPECT_EQ(limits.attribute_value_length_limit, (std::numeric_limits::max)()); +} + +TEST(TracerProvider, SpanLimitsNoLimits) +{ + SpanLimits limits = SpanLimits::NoLimits(); + EXPECT_EQ(limits.attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.attribute_value_length_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.event_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.event_attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_attribute_count_limit, (std::numeric_limits::max)()); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreate) +{ + SpanLimits limits; + limits.attribute_count_limit = 5; + limits.attribute_value_length_limit = 10; + limits.event_count_limit = 3; + limits.link_count_limit = 2; + limits.event_attribute_count_limit = 4; + limits.link_attribute_count_limit = 4; + + auto provider = TracerProviderFactory::Create( + std::make_unique(nullptr), Resource::Create({}), + std::make_unique(), std::make_unique(), + std::make_unique>( + ScopeConfigurator::Builder(TracerConfig::Default()).Build()), + limits); + + const auto &stored = provider->GetSpanLimits(); + EXPECT_EQ(stored.attribute_count_limit, limits.attribute_count_limit); + EXPECT_EQ(stored.attribute_value_length_limit, limits.attribute_value_length_limit); + EXPECT_EQ(stored.event_count_limit, limits.event_count_limit); + EXPECT_EQ(stored.link_count_limit, limits.link_count_limit); + EXPECT_EQ(stored.event_attribute_count_limit, limits.event_attribute_count_limit); + EXPECT_EQ(stored.link_attribute_count_limit, limits.link_attribute_count_limit); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefault) +{ + auto provider = TracerProviderFactory::Create(std::make_unique(nullptr)); + + const auto no_limits = SpanLimits::NoLimits(); + + const auto &limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.attribute_count_limit, no_limits.attribute_count_limit); + EXPECT_EQ(limits.event_count_limit, no_limits.event_count_limit); + EXPECT_EQ(limits.link_count_limit, no_limits.link_count_limit); + EXPECT_EQ(limits.event_attribute_count_limit, no_limits.event_attribute_count_limit); + EXPECT_EQ(limits.link_attribute_count_limit, no_limits.link_attribute_count_limit); + EXPECT_EQ(limits.attribute_value_length_limit, no_limits.attribute_value_length_limit); +} diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index 8a2fbcd5fd..d3107db47a 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -38,6 +38,7 @@ #include "opentelemetry/sdk/trace/samplers/parent.h" #include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/sdk/trace/tracer_config.h" #include "opentelemetry/sdk/trace/tracer_context.h" @@ -188,7 +189,8 @@ std::shared_ptr initTracer( IdGenerator *id_generator = new RandomIdGenerator, const ScopeConfigurator &tracer_configurator = ScopeConfigurator::Builder(TracerConfig::Default()).Build(), - std::unique_ptr scope = InstrumentationScope::Create("")) + std::unique_ptr scope = InstrumentationScope::Create(""), + opentelemetry::sdk::trace::SpanLimits span_limits = SpanLimits::NoLimits()) { auto processor = std::unique_ptr(new SimpleSpanProcessor(std::move(exporter))); std::vector> processors; @@ -197,7 +199,7 @@ std::shared_ptr initTracer( auto context = std::make_shared( std::move(processors), resource, std::unique_ptr(sampler), std::unique_ptr(id_generator), - std::make_unique>(tracer_configurator)); + std::make_unique>(tracer_configurator), span_limits); return std::shared_ptr(new Tracer(context, std::move(scope))); } @@ -1482,3 +1484,51 @@ TEST(Tracer, SpanSamplerDecision) } EXPECT_EQ(3, span_data->GetSpans().size()); } + +TEST(Tracer, SpanLimits) +{ + opentelemetry::sdk::trace::SpanLimits limits; + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 5; + limits.event_count_limit = 2; + limits.event_attribute_count_limit = 2; + + InMemorySpanExporter *exporter = new InMemorySpanExporter(); + std::shared_ptr span_data = exporter->GetData(); + auto tracer = initTracer( + std::unique_ptr{exporter}, new AlwaysOnSampler(), new RandomIdGenerator, + ScopeConfigurator::Builder(TracerConfig::Default()).Build(), + InstrumentationScope::Create(""), limits); + + std::vector> attributes = { + {"one", "1_value"}, {"two", "2_value"}, {"three", "3_value"}}; + + { + auto span = tracer->StartSpan("span_with_limits", attributes); + span->SetAttribute("four", "4_value"); + span->AddEvent("event1", attributes); + span->AddEvent("event2", attributes); + span->AddEvent("event3", attributes); + span->End(); + } + + const auto span_batch = span_data->GetSpans(); + ASSERT_EQ(1, span_batch.size()); + + const auto &recordable = span_batch.at(0); + + ASSERT_EQ(limits.attribute_count_limit, recordable->GetAttributes().size()); + EXPECT_EQ(limits.attribute_value_length_limit, + nostd::get(recordable->GetAttributes().at("one")).size()); + EXPECT_EQ("1_val", nostd::get(recordable->GetAttributes().at("one"))); + EXPECT_EQ("2_val", nostd::get(recordable->GetAttributes().at("two"))); + + const auto &events = recordable->GetEvents(); + ASSERT_EQ(limits.event_count_limit, events.size()); + EXPECT_EQ("event1", events.at(0).GetName()); + EXPECT_EQ("event2", events.at(1).GetName()); + EXPECT_EQ(limits.event_attribute_count_limit, events.at(0).GetAttributes().size()); + EXPECT_EQ(limits.event_attribute_count_limit, events.at(1).GetAttributes().size()); + EXPECT_EQ(limits.attribute_value_length_limit, + nostd::get(events.at(0).GetAttributes().at("one")).size()); +} diff --git a/tbump.toml b/tbump.toml index dc0218ae06..3992a3a56a 100644 --- a/tbump.toml +++ b/tbump.toml @@ -21,7 +21,7 @@ github_url = "https://github.com/open-telemetry/opentelemetry-cpp" [version] -current = "1.28.0-dev" +current = "1.29.0-dev" # Example of a semver regexp. # Make sure this matches current_version before diff --git a/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h b/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h new file mode 100644 index 0000000000..78f0305fbb --- /dev/null +++ b/test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h @@ -0,0 +1,228 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_flags.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace test_common +{ + +// A span processor that queues completed recordables into an internal buffer +// without exporting or destroying them, mirroring the batch processor's +// recording path. ForceFlush clears the buffer so benchmarks can drop recorded +// spans between iterations without measuring deallocation. +class BufferingSpanProcessor final : public sdk::trace::SpanProcessor +{ +public: + using Buffer = std::vector>; + + explicit BufferingSpanProcessor(std::unique_ptr exporter) + : exporter_(std::move(exporter)) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return exporter_->MakeRecordable(); + } + + void OnStart(sdk::trace::Recordable &, const trace::SpanContext &) noexcept override {} + + void OnEnd(std::unique_ptr &&span) noexcept override + { + buffer_.push_back(std::move(span)); + } + + bool ForceFlush(std::chrono::microseconds) noexcept override + { + buffer_.clear(); + return true; + } + + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::unique_ptr exporter_; + Buffer buffer_; +}; + +inline const sdk::resource::Resource &TestResource() +{ + static const auto kResource = + sdk::resource::Resource::Create({{"service.name", nostd::string_view{"benchmark_service"}}, + {"service.version", nostd::string_view{"1.0.0"}}}); + return kResource; +} + +inline const sdk::instrumentationscope::InstrumentationScope &TestScope() +{ + static const auto kScope = sdk::instrumentationscope::InstrumentationScope::Create( + "benchmark_scope", "1.0.0", "https://opentelemetry.io/schemas/1.24.0", + {{"scope.source", "benchmark"}}); + return *kScope; +} + +constexpr std::size_t kSpanAttributeLimit = 128; +constexpr std::size_t kSpanEventLimit = 128; +constexpr std::size_t kSpanLinkLimit = 128; +constexpr std::size_t kSpanEventAttributesCount = 2; +constexpr std::size_t kSpanLinkAttributesCount = 2; + +// Pre-built unique attribute keys (up to the default limit) for attribute sweeps. +inline const std::vector &AttributeKeys() +{ + static const std::vector kAttributeKeys = []() { + std::vector result; + result.reserve(kSpanAttributeLimit); + for (std::size_t key_idx = 0; key_idx < kSpanAttributeLimit; ++key_idx) + { + result.push_back("attribute." + std::to_string(key_idx)); + } + return result; + }(); + return kAttributeKeys; +} + +// A representative mix of attribute types used by the nominal span benchmark. +using SpanAttribute = std::pair; +inline const std::vector &NominalAttributes() +{ + static const std::vector kNominalAttributes = { + {"benchmark.string.short", nostd::string_view{"abcdefgh"}}, + {"benchmark.string.medium", nostd::string_view{"abcdefghijklmnopqrstuvwxyz012345"}}, + {"benchmark.string.long", + nostd::string_view{"abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"}}, + {"benchmark.int", common::AttributeValue{static_cast(1)}}, + {"benchmark.double", common::AttributeValue{3.14159265358979}}, + {"benchmark.bool", common::AttributeValue{true}}, + }; + return kNominalAttributes; +} + +inline std::vector MakeAttributes(std::size_t count) +{ + const auto &keys = AttributeKeys(); + std::vector attributes; + attributes.reserve(count); + for (std::size_t i = 0; i < count; ++i) + { + attributes.emplace_back(keys.at(i), nostd::string_view{"value-string-attribute"}); + } + return attributes; +} + +inline const std::vector &SpanEventAttributes() +{ + static const std::vector kSpanEventAttributes = + MakeAttributes(kSpanEventAttributesCount); + return kSpanEventAttributes; +} + +inline const trace::SpanContext &TestSpanContext() +{ + static constexpr uint8_t kTraceId[trace::TraceId::kSize] = {2, 3, 4, 5, 6, 7, 8, 9, + 2, 3, 4, 5, 6, 7, 8, 9}; + static constexpr uint8_t kSpanId[trace::SpanId::kSize] = {9, 8, 7, 6, 5, 4, 3, 2}; + static const trace::SpanContext kTestSpanContext{trace::TraceId{kTraceId}, trace::SpanId{kSpanId}, + trace::TraceFlags{trace::TraceFlags::kIsSampled}, + true}; + return kTestSpanContext; +} + +inline const std::vector &SpanLinkAttributes() +{ + static const std::vector kSpanLinkAttributes = + MakeAttributes(kSpanLinkAttributesCount); + return kSpanLinkAttributes; +} + +using LinkEntry = std::pair>; + +inline std::vector MakeLinkEntries(std::size_t count) +{ + std::vector entries; + entries.reserve(count); + for (std::size_t i = 0; i < count; ++i) + { + entries.emplace_back(TestSpanContext(), SpanLinkAttributes()); + } + return entries; +} + +// Starts a span with no attributes, events, or links +inline nostd::shared_ptr StartMinimalSpan(trace::Tracer &tracer) +{ + return tracer.StartSpan("benchmark_span"); +} + +// Starts a span with a representative mix of attributes +inline nostd::shared_ptr StartNominalSpan(trace::Tracer &tracer) +{ + return tracer.StartSpan("benchmark_span", NominalAttributes()); +} + +// Starts a span with pre-built attributes +inline nostd::shared_ptr StartSpanWithAttributes( + trace::Tracer &tracer, + const std::vector &attributes) +{ + return tracer.StartSpan("benchmark_span", attributes); +} + +// Starts a span with pre-built links +inline nostd::shared_ptr StartSpanWithLinks(trace::Tracer &tracer, + const std::vector &link_entries) +{ + return tracer.StartSpan("benchmark_span", nostd::span{}, link_entries); +} + +// Starts a span and adds `num_events` +inline nostd::shared_ptr StartSpanWithEvents(trace::Tracer &tracer, + std::size_t num_events) +{ + auto span = tracer.StartSpan("benchmark_span"); + for (std::size_t i = 0; i < num_events; ++i) + { + span->AddEvent("benchmark_event", SpanEventAttributes()); + } + return span; +} + +// Initialize all the static test data in singletons above. To be called in test fixture SetUp() +inline void InitializeSpanTestData() +{ + TestResource(); + TestScope(); + AttributeKeys(); + NominalAttributes(); + SpanEventAttributes(); + SpanLinkAttributes(); + TestSpanContext(); +} + +} // namespace test_common +OPENTELEMETRY_END_NAMESPACE diff --git a/third_party_release b/third_party_release index fb7939f726..b1ff1cfc1e 100644 --- a/third_party_release +++ b/third_party_release @@ -16,8 +16,8 @@ abseil=20250512.1 zlib=v1.3.2 curl=curl-8_21_0 -protobuf=v33.6 -grpc=v1.81.1 +protobuf=v35.1 +grpc=v1.82.1 benchmark=v1.9.4 googletest=v1.17.0 ms-gsl=v4.2.1