From cd65df181def82499313542ddb7e6b59d922ad0e Mon Sep 17 00:00:00 2001 From: ayush-that Date: Sun, 7 Jun 2026 13:31:12 +0530 Subject: [PATCH 01/61] [SDK] Implement the ProbabilitySampler Fixes #4127. --- CHANGELOG.md | 3 + docs/public/sdk/GettingStarted.rst | 3 +- .../sdk/trace/samplers/probability.h | 59 +++ .../sdk/trace/samplers/probability_factory.h | 31 ++ sdk/src/configuration/sdk_builder.cc | 10 +- sdk/src/trace/CMakeLists.txt | 2 + sdk/src/trace/samplers/probability.cc | 228 ++++++++++ sdk/src/trace/samplers/probability_factory.cc | 22 + sdk/test/trace/BUILD | 16 + sdk/test/trace/CMakeLists.txt | 1 + sdk/test/trace/probability_sampler_test.cc | 395 ++++++++++++++++++ 11 files changed, 768 insertions(+), 2 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/trace/samplers/probability.h create mode 100644 sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h create mode 100644 sdk/src/trace/samplers/probability.cc create mode 100644 sdk/src/trace/samplers/probability_factory.cc create mode 100644 sdk/test/trace/probability_sampler_test.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c462112a7..ce5849d5b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,9 @@ Increment the: ParentThreshold, RuleBased) [#4028](https://github.com/open-telemetry/opentelemetry-cpp/issues/4028) +* [SDK] Implement the ProbabilitySampler + [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) + * [BUILD] Fix protobuf build failure [#4154](https://github.com/open-telemetry/opentelemetry-cpp/pull/4154) diff --git a/docs/public/sdk/GettingStarted.rst b/docs/public/sdk/GettingStarted.rst index cb7f207574..e250d4b617 100644 --- a/docs/public/sdk/GettingStarted.rst +++ b/docs/public/sdk/GettingStarted.rst @@ -108,12 +108,13 @@ Sampler ^^^^^^^ Sampling is mechanism to control/reducing the number of samples of traces collected and sent to the backend. -OpenTelemetry C++ SDK offers four samplers out of the box: +OpenTelemetry C++ SDK offers five samplers out of the box: - AlwaysOnSampler which samples every trace regardless of upstream sampling decisions. - AlwaysOffSampler which doesn’t sample any trace, regardless of upstream sampling decisions. - ParentBased which uses the parent span to make sampling decisions, if present. - TraceIdRatioBased which samples a configurable percentage of traces. +- ProbabilitySampler which samples a configurable ratio of traces following the consistent probability sampling specification. .. code:: cpp diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h new file mode 100644 index 0000000000..c1e57510dc --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/trace/span_metadata.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +/** + * The ProbabilitySampler computes and returns a decision based on the + * span's 56-bit randomness value and the configured ratio, following the + * consistent probability sampling specification. + */ +class ProbabilitySampler : public Sampler +{ +public: + /** + * @param ratio a required value, 1.0 >= ratio >= 0.0. If the randomness + * value of the span is greater than or equal to the rejection threshold + * derived from the ratio, ShouldSample will return RECORD_AND_SAMPLE. + */ + explicit ProbabilitySampler(double ratio); + + /** + * @return Returns either RECORD_AND_SAMPLE or DROP based on the comparison + * of the span's randomness value against the configured rejection + * threshold. The parent SampledFlag is ignored. + */ + SamplingResult ShouldSample( + const opentelemetry::trace::SpanContext &parent_context, + opentelemetry::trace::TraceId trace_id, + nostd::string_view /*name*/, + opentelemetry::trace::SpanKind /*span_kind*/, + const opentelemetry::common::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::SpanContextKeyValueIterable & /*links*/) noexcept override; + + /** + * @return Description MUST be ProbabilitySampler{0.000100} + */ + nostd::string_view GetDescription() const noexcept override; + +private: + std::string description_; + const uint64_t threshold_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h new file mode 100644 index 0000000000..4dd573d62f --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +/** + * Factory class for ProbabilitySampler. + */ +class ProbabilitySamplerFactory +{ +public: + /** + * Create a ProbabilitySampler. + */ + static std::unique_ptr Create(double ratio); +}; + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index f60362fa9e..1574d1cab0 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -168,6 +168,7 @@ #include "opentelemetry/sdk/trace/samplers/always_off_factory.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" #include "opentelemetry/sdk/trace/samplers/parent_factory.h" +#include "opentelemetry/sdk/trace/samplers/probability_factory.h" #include "opentelemetry/sdk/trace/samplers/trace_id_ratio_factory.h" #include "opentelemetry/sdk/trace/simple_processor_factory.h" #include "opentelemetry/sdk/trace/tracer_config.h" @@ -388,7 +389,14 @@ class SamplerBuilder : public opentelemetry::sdk::configuration::SamplerConfigur const opentelemetry::sdk::configuration::ComposableProbabilitySamplerConfiguration *model) override { - sampler = opentelemetry::sdk::trace::TraceIdRatioBasedSamplerFactory::Create(model->ratio); + if (model->ratio > 0.0) + { + sampler = opentelemetry::sdk::trace::ProbabilitySamplerFactory::Create(model->ratio); + } + else + { + sampler = opentelemetry::sdk::trace::AlwaysOffSamplerFactory::Create(); + } } void VisitComposableParentThreshold( diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index cc9d2fabfd..9444dc1cd6 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -20,6 +20,8 @@ add_library( samplers/always_off_factory.cc samplers/parent.cc samplers/parent_factory.cc + samplers/probability.cc + samplers/probability_factory.cc samplers/trace_id_ratio.cc samplers/trace_id_ratio_factory.cc samplers/ot_trace_state.cc diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc new file mode 100644 index 0000000000..a3bb93bb3b --- /dev/null +++ b/sdk/src/trace/samplers/probability.cc @@ -0,0 +1,228 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/nostd/function_ref.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/samplers/probability.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/trace_flags.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/trace/trace_state.h" +#include "opentelemetry/version.h" + +namespace trace_api = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; + +namespace +{ +constexpr char kOtTraceStateKey[] = "ot"; + +// Rejection threshold for a ratio of zero, 2^56: drops every span. +constexpr uint64_t kMaxThreshold = 1ULL << 56; + +/** + * Converts a ratio in [0, 1] to a 56-bit rejection threshold. + */ +uint64_t CalculateRejectionThreshold(double ratio) noexcept +{ + // The negated comparison also routes NaN to the never-sample threshold. + if (!(ratio > 0.0)) + return kMaxThreshold; + if (ratio >= 1.0) + return 0; + return kMaxThreshold - + static_cast(std::llround(ratio * static_cast(kMaxThreshold))); +} + +bool ParseHex56(nostd::string_view hex, uint64_t *value) noexcept +{ + if (hex.size() != 14) + return false; + uint64_t result = 0; + for (char c : hex) + { + result <<= 4; + if (c >= '0' && c <= '9') + result |= static_cast(c - '0'); + else if (c >= 'a' && c <= 'f') + result |= static_cast(c - 'a' + 10); + else + return false; + } + *value = result; + return true; +} + +/** + * Encodes a 56-bit threshold as lowercase hex with trailing zeros removed, + * e.g. 2^55 becomes "8" and 0 becomes "0". + */ +std::string EncodeThreshold(uint64_t threshold) +{ + if (threshold == 0) + return "0"; + static const char kHex[] = "0123456789abcdef"; + std::string hex(14, '0'); + for (int i = 13; i >= 0; --i) + { + hex[i] = kHex[threshold & 0xf]; + threshold >>= 4; + } + hex.erase(hex.find_last_not_of('0') + 1); + return hex; +} + +uint64_t RandomnessFromTraceId(const trace_api::TraceId &trace_id) noexcept +{ + static_assert(trace_api::TraceId::kSize >= 7, "TraceID must be at least 7 bytes long."); + + const uint8_t *data = trace_id.Id().data(); + uint64_t result = 0; + for (size_t i = trace_api::TraceId::kSize - 7; i < trace_api::TraceId::kSize; ++i) + { + result = (result << 8) | data[i]; + } + return result; +} + +// Returns true when the ot value carries a valid rv sub-key. +bool ExplicitRandomness(nostd::string_view ot_value, uint64_t *randomness) noexcept +{ + while (!ot_value.empty()) + { + size_t end = ot_value.find(';'); + nostd::string_view sub_key = ot_value.substr(0, end); + ot_value = end == nostd::string_view::npos ? "" : ot_value.substr(end + 1); + + if (sub_key.substr(0, 3) == "rv:") + return ParseHex56(sub_key.substr(3), randomness); + } + return false; +} + +// Sub-keys that are empty, lack a key:value separator, or duplicate th are dropped. +std::string SetThresholdSubKey(nostd::string_view ot_value, const std::string &threshold) +{ + std::string result; + bool replaced = false; + while (!ot_value.empty()) + { + size_t end = ot_value.find(';'); + nostd::string_view sub_key = ot_value.substr(0, end); + ot_value = end == nostd::string_view::npos ? "" : ot_value.substr(end + 1); + + if (sub_key.empty() || sub_key.find(':') == nostd::string_view::npos) + continue; + if (sub_key.substr(0, 3) == "th:") + { + if (replaced) + continue; + replaced = true; + if (!result.empty()) + result += ';'; + result += "th:" + threshold; + continue; + } + if (!result.empty()) + result += ';'; + result.append(sub_key.data(), sub_key.size()); + } + if (!replaced) + { + if (!result.empty()) + result += ';'; + result += "th:" + threshold; + } + return result; +} +} // namespace + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +ProbabilitySampler::ProbabilitySampler(double ratio) + : threshold_(CalculateRejectionThreshold(ratio)) +{ + if (!(ratio > 0.0)) + ratio = 0.0; + if (ratio > 1.0) + ratio = 1.0; + description_ = "ProbabilitySampler{" + std::to_string(ratio) + "}"; +} + +SamplingResult ProbabilitySampler::ShouldSample( + const trace_api::SpanContext &parent_context, + trace_api::TraceId trace_id, + nostd::string_view /*name*/, + trace_api::SpanKind /*span_kind*/, + const opentelemetry::common::KeyValueIterable & /*attributes*/, + const trace_api::SpanContextKeyValueIterable & /*links*/) noexcept +{ + if (threshold_ == kMaxThreshold) + return {Decision::DROP, nullptr, {}}; + + auto parent_trace_state = parent_context.trace_state(); + std::string ot_value; + bool has_ot = parent_trace_state->Get(kOtTraceStateKey, ot_value); + + uint64_t randomness = 0; + if (!ExplicitRandomness(ot_value, &randomness)) + { + if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom()) + { + static std::atomic warned{false}; + if (!warned.exchange(true)) + { + OTEL_INTERNAL_LOG_WARN( + "ProbabilitySampler presumes TraceID randomness, but the W3C random trace flag is " + "not set. Upgrade the caller to W3C Trace Context Level 2."); + } + } + randomness = RandomnessFromTraceId(trace_id); + } + + if (randomness < threshold_) + return {Decision::DROP, nullptr, {}}; + + std::string new_ot_value = SetThresholdSubKey(ot_value, EncodeThreshold(threshold_)); + if (!trace_api::TraceState::IsValidValue(new_ot_value)) + return {Decision::RECORD_AND_SAMPLE, nullptr, {}}; + + if (!has_ot) + { + size_t entry_count = 0; + parent_trace_state->GetAllEntries([&entry_count](nostd::string_view, nostd::string_view) { + ++entry_count; + return true; + }); + if (entry_count >= trace_api::TraceState::kMaxKeyValuePairs) + return {Decision::RECORD_AND_SAMPLE, nullptr, {}}; + } + + auto trace_state = + has_ot ? parent_trace_state->Delete(kOtTraceStateKey)->Set(kOtTraceStateKey, new_ot_value) + : parent_trace_state->Set(kOtTraceStateKey, new_ot_value); + + return {Decision::RECORD_AND_SAMPLE, nullptr, trace_state}; +} + +nostd::string_view ProbabilitySampler::GetDescription() const noexcept +{ + return description_; +} +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/samplers/probability_factory.cc b/sdk/src/trace/samplers/probability_factory.cc new file mode 100644 index 0000000000..6e0e9e0290 --- /dev/null +++ b/sdk/src/trace/samplers/probability_factory.cc @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/sdk/trace/samplers/probability_factory.h" +#include "opentelemetry/sdk/trace/samplers/probability.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ + +std::unique_ptr ProbabilitySamplerFactory::Create(double ratio) +{ + std::unique_ptr sampler(new ProbabilitySampler(ratio)); + return sampler; +} + +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 7f536dc57e..6f2230e6fb 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -145,6 +145,22 @@ cc_test( ], ) +cc_test( + name = "probability_sampler_test", + srcs = [ + "probability_sampler_test.cc", + ], + tags = [ + "test", + "trace", + ], + deps = [ + "//sdk/src/common:random", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "trace_id_ratio_sampler_test", srcs = [ diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index ca0ae383bc..566422a928 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -11,6 +11,7 @@ foreach( always_off_sampler_test always_on_sampler_test parent_sampler_test + probability_sampler_test trace_id_ratio_sampler_test composable_sampler_test batch_span_processor_test diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc new file mode 100644 index 0000000000..d0a271642f --- /dev/null +++ b/sdk/test/trace/probability_sampler_test.cc @@ -0,0 +1,395 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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/sdk/trace/sampler.h" +#include "opentelemetry/sdk/trace/samplers/probability.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/trace/span_context_kv_iterable_view.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/trace_state.h" +#include "src/common/random.h" + +using opentelemetry::sdk::common::Random; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::sdk::trace::ProbabilitySampler; +namespace trace_api = opentelemetry::trace; +namespace common = opentelemetry::common; + +namespace +{ +/* + * Helper function for building a trace id whose rightmost 7 bytes hold the + * given 56-bit randomness value. + */ +trace_api::TraceId TraceIdWithRandomness(uint64_t randomness) +{ + uint8_t buf[trace_api::TraceId::kSize] = {0}; + for (int i = 0; i < 7; ++i) + { + buf[15 - i] = static_cast(randomness >> (8 * i)); + } + return trace_api::TraceId(buf); +} + +/* + * Helper function for running ProbabilitySampler tests. + * Given a span context, sampler, and number of iterations this function + * will return the number of RECORD_AND_SAMPLE decision based on randomly + * generated traces. + */ +int RunShouldSampleCountDecision(trace_api::SpanContext &context, + ProbabilitySampler &sampler, + int iterations) +{ + int actual_count = 0; + + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + + using M = std::map; + M m1 = {{}}; + + using L = std::vector>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + common::KeyValueIterableView view{m1}; + trace_api::SpanContextKeyValueIterableView links{l1}; + + for (int i = 0; i < iterations; ++i) + { + uint8_t buf[16] = {0}; + Random::GenerateRandomBuffer(buf); + + trace_api::TraceId trace_id(buf); + + auto result = sampler.ShouldSample(context, trace_id, "", span_kind, view, links); + if (result.decision == Decision::RECORD_AND_SAMPLE) + { + ++actual_count; + } + } + + return actual_count; +} + +opentelemetry::sdk::trace::SamplingResult SampleWithContext(ProbabilitySampler &sampler, + const trace_api::SpanContext &context, + trace_api::TraceId trace_id) +{ + trace_api::SpanKind span_kind = trace_api::SpanKind::kInternal; + + using M = std::map; + M m1 = {{}}; + + using L = std::vector>>; + L l1 = {{trace_api::SpanContext(false, false), {}}, {trace_api::SpanContext(false, false), {}}}; + + common::KeyValueIterableView view{m1}; + trace_api::SpanContextKeyValueIterableView links{l1}; + + return sampler.ShouldSample(context, trace_id, "", span_kind, view, links); +} +} // namespace + +TEST(ProbabilitySampler, ShouldSampleAlways) +{ + ProbabilitySampler s1(1.0); + + auto sampling_result = + SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), TraceIdWithRandomness(0)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string th_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", th_value)); + ASSERT_EQ("th:0", th_value); +} + +TEST(ProbabilitySampler, ShouldSampleNever) +{ + ProbabilitySampler s1(0.0); + + auto sampling_result = SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), + TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + ASSERT_EQ(nullptr, sampling_result.trace_state); +} + +TEST(ProbabilitySampler, ShouldSampleAtThreshold) +{ + // For ratio 0.5 the rejection threshold is 2^55 (0x80000000000000). + ProbabilitySampler s1(0.5); + + auto sampling_result = SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), + TraceIdWithRandomness(0x80000000000000)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + + std::string th_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", th_value)); + ASSERT_EQ("th:8", th_value); + + sampling_result = SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), + TraceIdWithRandomness(0x7fffffffffffff)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.trace_state); +} + +TEST(ProbabilitySampler, ExplicitRandomnessTakesPrecedence) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // The rv sub-key wins over the trace id randomness, which would drop here. + auto trace_state = trace_api::TraceState::FromHeader("ot=rv:ffffffffffffff"); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + + std::string ot_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value)); + ASSERT_EQ("rv:ffffffffffffff;th:8", ot_value); + + // An rv of zero drops even when the trace id randomness would sample. + trace_state = trace_api::TraceState::FromHeader("ot=rv:00000000000000"); + trace_api::SpanContext context2(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + sampling_result = SampleWithContext(s1, context2, TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); +} + +TEST(ProbabilitySampler, InvalidExplicitRandomnessIsIgnored) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // Wrong length, invalid characters: fall back to the trace id randomness. + for (const char *header : {"ot=rv:ffffffffffff", "ot=rv:ffffffffffffffff", "ot=rv:FFFFFFFFFFFFFF", + "ot=rv:gggggggggggggg", "ot=th:8"}) + { + auto trace_state = trace_api::TraceState::FromHeader(header); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision) << header; + + sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + ASSERT_EQ(Decision::DROP, sampling_result.decision) << header; + } +} + +TEST(ProbabilitySampler, ThresholdReplacesExistingValue) +{ + // For ratio 0.25 the rejection threshold is 0xc0000000000000. + ProbabilitySampler s1(0.25); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + auto trace_state = trace_api::TraceState::FromHeader("ot=th:8;rv:ffffffffffffff,foo=bar"); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + + std::string ot_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value)); + ASSERT_EQ("th:c;rv:ffffffffffffff", ot_value); + + std::string foo_value; + ASSERT_TRUE(sampling_result.trace_state->Get("foo", foo_value)); + ASSERT_EQ("bar", foo_value); +} + +TEST(ProbabilitySampler, MalformedSubKeysAreDropped) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // Empty, key-only, and duplicate th sub-keys are dropped from the output. + for (auto p : {std::make_pair("ot=a:1;;rv:ffffffffffffff", "a:1;rv:ffffffffffffff;th:8"), + std::make_pair("ot=foo", "th:8"), std::make_pair("ot=th:1;th:2", "th:8")}) + { + auto trace_state = trace_api::TraceState::FromHeader(p.first); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision) << p.first; + + std::string ot_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value)) << p.first; + ASSERT_EQ(p.second, ot_value) << p.first; + } +} + +TEST(ProbabilitySampler, FullTraceStateKeepsParentTraceState) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // 32 entries and no ot entry: there is no room to add th, so the span is + // sampled but the trace state is left untouched. + std::string header = "k0=0"; + for (int i = 1; i < 32; ++i) + { + header += ",k" + std::to_string(i) + "=" + std::to_string(i); + } + auto trace_state = trace_api::TraceState::FromHeader(header); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.trace_state); +} + +TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // Appending the th sub-key would exceed the 256 character value limit, so + // the span is sampled but the trace state is left untouched. + std::string ot_value = "a:" + std::string(253, 'b'); + auto trace_state = trace_api::TraceState::FromHeader("ot=" + ot_value); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.trace_state); +} + +TEST(ProbabilitySampler, IgnoresParentSampledFlag) +{ + ProbabilitySampler s1(1.0); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // Parent not sampled, ratio 1: the parent flag must be ignored. + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, true); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + + // Parent sampled, ratio 0: same. + ProbabilitySampler s2(0.0); + trace_api::SpanContext context2(trace_id, span_id, + trace_api::TraceFlags{trace_api::TraceFlags::kIsSampled}, true); + + sampling_result = SampleWithContext(s2, context2, TraceIdWithRandomness(0xffffffffffffff)); + ASSERT_EQ(Decision::DROP, sampling_result.decision); +} + +TEST(ProbabilitySampler, ProbabilitySamplerHalf) +{ + double ratio = 0.5; + int iterations = 100000; + int expected_count = static_cast(iterations * ratio); + int variance = static_cast(iterations * 0.01); + + trace_api::SpanContext c(true, true); + ProbabilitySampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_TRUE(actual_count < (expected_count + variance)); + ASSERT_TRUE(actual_count > (expected_count - variance)); +} + +TEST(ProbabilitySampler, ProbabilitySamplerOnePercent) +{ + double ratio = 0.01; + int iterations = 100000; + int expected_count = static_cast(iterations * ratio); + int variance = static_cast(iterations * 0.01); + + trace_api::SpanContext c(true, true); + ProbabilitySampler s(ratio); + + int actual_count = RunShouldSampleCountDecision(c, s, iterations); + + ASSERT_TRUE(actual_count < (expected_count + variance)); + ASSERT_TRUE(actual_count > (expected_count - variance)); +} + +TEST(ProbabilitySampler, GetDescription) +{ + ProbabilitySampler s1(0.01); + ASSERT_EQ("ProbabilitySampler{0.010000}", s1.GetDescription()); + + ProbabilitySampler s2(0.00); + ASSERT_EQ("ProbabilitySampler{0.000000}", s2.GetDescription()); + + ProbabilitySampler s3(1.00); + ASSERT_EQ("ProbabilitySampler{1.000000}", s3.GetDescription()); + + ProbabilitySampler s4(3.00); + ASSERT_EQ("ProbabilitySampler{1.000000}", s4.GetDescription()); + + ProbabilitySampler s5(-3.00); + ASSERT_EQ("ProbabilitySampler{0.000000}", s5.GetDescription()); + + ProbabilitySampler s6(std::nan("")); + ASSERT_EQ("ProbabilitySampler{0.000000}", s6.GetDescription()); +} + +TEST(ProbabilitySampler, ShouldSampleNeverWithNaN) +{ + ProbabilitySampler s1(std::nan("")); + + auto sampling_result = SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), + TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); +} From 710b267cd44bf6599d5d615f102a4c7461627c0a Mon Sep 17 00:00:00 2001 From: ayush-that Date: Tue, 16 Jun 2026 00:39:05 +0530 Subject: [PATCH 02/61] reuse shared CalculateThreshold and GetRandomnessFromTraceId, strip stale th on the drop path --- sdk/src/trace/samplers/probability.cc | 86 +++++++++------------- sdk/test/trace/probability_sampler_test.cc | 62 +++++++++++++++- 2 files changed, 95 insertions(+), 53 deletions(-) diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index a3bb93bb3b..cacfda4dd1 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -21,28 +20,21 @@ #include "opentelemetry/trace/trace_state.h" #include "opentelemetry/version.h" +#include "ot_trace_state.h" + namespace trace_api = opentelemetry::trace; namespace nostd = opentelemetry::nostd; namespace { -constexpr char kOtTraceStateKey[] = "ot"; - -// Rejection threshold for a ratio of zero, 2^56: drops every span. -constexpr uint64_t kMaxThreshold = 1ULL << 56; - -/** - * Converts a ratio in [0, 1] to a 56-bit rejection threshold. - */ -uint64_t CalculateRejectionThreshold(double ratio) noexcept +// Clamps ratio to [0, 1]. NaN and negatives map to 0 (never-sample). +double ClampProbability(double ratio) noexcept { - // The negated comparison also routes NaN to the never-sample threshold. if (!(ratio > 0.0)) - return kMaxThreshold; - if (ratio >= 1.0) - return 0; - return kMaxThreshold - - static_cast(std::llround(ratio * static_cast(kMaxThreshold))); + return 0.0; + if (ratio > 1.0) + return 1.0; + return ratio; } bool ParseHex56(nostd::string_view hex, uint64_t *value) noexcept @@ -83,19 +75,6 @@ std::string EncodeThreshold(uint64_t threshold) return hex; } -uint64_t RandomnessFromTraceId(const trace_api::TraceId &trace_id) noexcept -{ - static_assert(trace_api::TraceId::kSize >= 7, "TraceID must be at least 7 bytes long."); - - const uint8_t *data = trace_id.Id().data(); - uint64_t result = 0; - for (size_t i = trace_api::TraceId::kSize - 7; i < trace_api::TraceId::kSize; ++i) - { - result = (result << 8) | data[i]; - } - return result; -} - // Returns true when the ot value carries a valid rv sub-key. bool ExplicitRandomness(nostd::string_view ot_value, uint64_t *randomness) noexcept { @@ -154,14 +133,9 @@ namespace sdk namespace trace { ProbabilitySampler::ProbabilitySampler(double ratio) - : threshold_(CalculateRejectionThreshold(ratio)) -{ - if (!(ratio > 0.0)) - ratio = 0.0; - if (ratio > 1.0) - ratio = 1.0; - description_ = "ProbabilitySampler{" + std::to_string(ratio) + "}"; -} + : description_("ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + "}"), + threshold_(CalculateThreshold(ClampProbability(ratio))) +{} SamplingResult ProbabilitySampler::ShouldSample( const trace_api::SpanContext &parent_context, @@ -171,31 +145,41 @@ SamplingResult ProbabilitySampler::ShouldSample( const opentelemetry::common::KeyValueIterable & /*attributes*/, const trace_api::SpanContextKeyValueIterable & /*links*/) noexcept { - if (threshold_ == kMaxThreshold) - return {Decision::DROP, nullptr, {}}; - auto parent_trace_state = parent_context.trace_state(); std::string ot_value; bool has_ot = parent_trace_state->Get(kOtTraceStateKey, ot_value); - uint64_t randomness = 0; - if (!ExplicitRandomness(ot_value, &randomness)) + bool drop = threshold_ == kMaxThreshold; + if (!drop) { - if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom()) + uint64_t randomness = 0; + if (!ExplicitRandomness(ot_value, &randomness)) { - static std::atomic warned{false}; - if (!warned.exchange(true)) + if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom()) { - OTEL_INTERNAL_LOG_WARN( - "ProbabilitySampler presumes TraceID randomness, but the W3C random trace flag is " - "not set. Upgrade the caller to W3C Trace Context Level 2."); + static std::atomic warned{false}; + if (!warned.exchange(true)) + { + OTEL_INTERNAL_LOG_WARN( + "ProbabilitySampler presumes TraceID randomness, but the W3C random trace flag is " + "not set. Upgrade the caller to W3C Trace Context Level 2."); + } } + randomness = GetRandomnessFromTraceId(trace_id); } - randomness = RandomnessFromTraceId(trace_id); + drop = randomness < threshold_; } - if (randomness < threshold_) - return {Decision::DROP, nullptr, {}}; + if (drop) + { + OtelTraceState ot_state = OtelTraceState::Parse(ot_value); + ot_state.has_threshold = false; + std::string dropped_ot = ot_state.Serialize(); + auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); + if (!dropped_ot.empty()) + trace_state = trace_state->Set(kOtTraceStateKey, dropped_ot); + return {Decision::DROP, nullptr, trace_state}; + } std::string new_ot_value = SetThresholdSubKey(ot_value, EncodeThreshold(threshold_)); if (!trace_api::TraceState::IsValidValue(new_ot_value)) diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index d0a271642f..225ed8a84f 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -131,7 +131,9 @@ TEST(ProbabilitySampler, ShouldSampleNever) ASSERT_EQ(Decision::DROP, sampling_result.decision); ASSERT_EQ(nullptr, sampling_result.attributes); - ASSERT_EQ(nullptr, sampling_result.trace_state); + ASSERT_NE(nullptr, sampling_result.trace_state); + std::string ot_value; + ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value)); } TEST(ProbabilitySampler, ShouldSampleAtThreshold) @@ -152,7 +154,9 @@ TEST(ProbabilitySampler, ShouldSampleAtThreshold) TraceIdWithRandomness(0x7fffffffffffff)); ASSERT_EQ(Decision::DROP, sampling_result.decision); - ASSERT_EQ(nullptr, sampling_result.trace_state); + ASSERT_NE(nullptr, sampling_result.trace_state); + std::string dropped_ot; + ASSERT_FALSE(sampling_result.trace_state->Get("ot", dropped_ot)); } TEST(ProbabilitySampler, ExplicitRandomnessTakesPrecedence) @@ -235,6 +239,60 @@ TEST(ProbabilitySampler, ThresholdReplacesExistingValue) ASSERT_EQ("bar", foo_value); } +TEST(ProbabilitySampler, DropClearsStaleThreshold) +{ + ProbabilitySampler s1(0.5); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + // rv of zero is below the 0.5 threshold, so the span is dropped. + auto trace_state = + trace_api::TraceState::FromHeader("ot=th:8;rv:00000000000000;vendor:xyz,foo=bar"); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string ot_value; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value)); + ASSERT_EQ("rv:00000000000000;vendor:xyz", ot_value); + + std::string foo_value; + ASSERT_TRUE(sampling_result.trace_state->Get("foo", foo_value)); + ASSERT_EQ("bar", foo_value); +} + +TEST(ProbabilitySampler, DropDeletesOtWhenOnlyThreshold) +{ + // ot=th:8 with no other sub-keys: stripping th leaves an empty value, so the ot key is deleted. + ProbabilitySampler s1(0.0); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + auto trace_state = trace_api::TraceState::FromHeader("ot=th:8,foo=bar"); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0)); + + ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string ot_value; + ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value)); + + std::string foo_value; + ASSERT_TRUE(sampling_result.trace_state->Get("foo", foo_value)); + ASSERT_EQ("bar", foo_value); +} + TEST(ProbabilitySampler, MalformedSubKeysAreDropped) { ProbabilitySampler s1(0.5); From 3b30ff14589c5bac968ae4f508e909e74a8f2610 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Sun, 21 Jun 2026 11:24:19 +0530 Subject: [PATCH 03/61] drop unused nostd includes flagged by include-what-you-use --- sdk/src/trace/samplers/probability.cc | 1 - sdk/test/trace/probability_sampler_test.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index cacfda4dd1..419d148f35 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -9,7 +9,6 @@ #include #include "opentelemetry/nostd/function_ref.h" -#include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/trace/sampler.h" diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index 225ed8a84f..f6dbda861f 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -12,7 +12,6 @@ #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/sdk/trace/sampler.h" #include "opentelemetry/sdk/trace/samplers/probability.h" From 297388e29c8879ed285d7f66f35d4341e49a8a22 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Thu, 25 Jun 2026 13:26:42 +0530 Subject: [PATCH 04/61] strip stale th on the invalid-ot sample path Signed-off-by: ayush-that --- sdk/src/trace/samplers/probability.cc | 28 +++++++++++++----- sdk/test/trace/probability_sampler_test.cc | 34 ++++++++++++++++++++-- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index 419d148f35..6b931f7374 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -9,6 +9,7 @@ #include #include "opentelemetry/nostd/function_ref.h" +#include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/trace/sampler.h" @@ -131,6 +132,23 @@ namespace sdk { namespace trace { +namespace +{ +// Removes the inherited (now stale) "th" sub-key, keeping the other ot sub-keys. +nostd::shared_ptr EraseThreshold( + const nostd::shared_ptr &parent_trace_state, + const std::string &ot_value) +{ + OtelTraceState ot_state = OtelTraceState::Parse(ot_value); + ot_state.has_threshold = false; + std::string stripped = ot_state.Serialize(); + auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); + if (!stripped.empty()) + trace_state = trace_state->Set(kOtTraceStateKey, stripped); + return trace_state; +} +} // namespace + ProbabilitySampler::ProbabilitySampler(double ratio) : description_("ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + "}"), threshold_(CalculateThreshold(ClampProbability(ratio))) @@ -171,18 +189,12 @@ SamplingResult ProbabilitySampler::ShouldSample( if (drop) { - OtelTraceState ot_state = OtelTraceState::Parse(ot_value); - ot_state.has_threshold = false; - std::string dropped_ot = ot_state.Serialize(); - auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); - if (!dropped_ot.empty()) - trace_state = trace_state->Set(kOtTraceStateKey, dropped_ot); - return {Decision::DROP, nullptr, trace_state}; + return {Decision::DROP, nullptr, EraseThreshold(parent_trace_state, ot_value)}; } std::string new_ot_value = SetThresholdSubKey(ot_value, EncodeThreshold(threshold_)); if (!trace_api::TraceState::IsValidValue(new_ot_value)) - return {Decision::RECORD_AND_SAMPLE, nullptr, {}}; + return {Decision::RECORD_AND_SAMPLE, nullptr, EraseThreshold(parent_trace_state, ot_value)}; if (!has_ot) { diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index f6dbda861f..5f26bb4916 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -352,8 +352,7 @@ TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; trace_api::SpanId span_id{span_id_buffer}; - // Appending the th sub-key would exceed the 256 character value limit, so - // the span is sampled but the trace state is left untouched. + // The oversized ot value is re-emitted unchanged because it has no th to erase. std::string ot_value = "a:" + std::string(253, 'b'); auto trace_state = trace_api::TraceState::FromHeader("ot=" + ot_value); trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); @@ -361,7 +360,36 @@ TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); - ASSERT_EQ(nullptr, sampling_result.trace_state); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string result_ot; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); + ASSERT_EQ(ot_value, result_ot); +} + +TEST(ProbabilitySampler, OversizedValueErasesStaleThreshold) +{ + // ratio 0.1's 17-char th overflows the 256-char limit, so the stale th is erased. + ProbabilitySampler s1(0.1); + + uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; + trace_api::TraceId trace_id{trace_id_buffer}; + uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; + trace_api::SpanId span_id{span_id_buffer}; + + std::string rest = "x:" + std::string(245, 'b'); + auto trace_state = trace_api::TraceState::FromHeader("ot=th:8;" + rest); + trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); + + auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string result_ot; + ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); + ASSERT_EQ(rest, result_ot); + ASSERT_EQ(std::string::npos, result_ot.find("th:")); } TEST(ProbabilitySampler, IgnoresParentSampledFlag) From 210adcb9f3c0f57bdb0670dcd2a269ce10bc3f9b Mon Sep 17 00:00:00 2001 From: ayush-that Date: Wed, 1 Jul 2026 21:05:14 +0530 Subject: [PATCH 05/61] reuse OtelTraceState for ot value handling in the probability sampler Signed-off-by: ayush-that --- sdk/src/trace/samplers/probability.cc | 157 ++++----------------- sdk/test/trace/probability_sampler_test.cc | 31 ++-- 2 files changed, 48 insertions(+), 140 deletions(-) diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index 6b931f7374..003642837f 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -1,14 +1,11 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include #include #include #include -#include #include -#include "opentelemetry/nostd/function_ref.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/global_log_handler.h" @@ -23,7 +20,6 @@ #include "ot_trace_state.h" namespace trace_api = opentelemetry::trace; -namespace nostd = opentelemetry::nostd; namespace { @@ -36,95 +32,6 @@ double ClampProbability(double ratio) noexcept return 1.0; return ratio; } - -bool ParseHex56(nostd::string_view hex, uint64_t *value) noexcept -{ - if (hex.size() != 14) - return false; - uint64_t result = 0; - for (char c : hex) - { - result <<= 4; - if (c >= '0' && c <= '9') - result |= static_cast(c - '0'); - else if (c >= 'a' && c <= 'f') - result |= static_cast(c - 'a' + 10); - else - return false; - } - *value = result; - return true; -} - -/** - * Encodes a 56-bit threshold as lowercase hex with trailing zeros removed, - * e.g. 2^55 becomes "8" and 0 becomes "0". - */ -std::string EncodeThreshold(uint64_t threshold) -{ - if (threshold == 0) - return "0"; - static const char kHex[] = "0123456789abcdef"; - std::string hex(14, '0'); - for (int i = 13; i >= 0; --i) - { - hex[i] = kHex[threshold & 0xf]; - threshold >>= 4; - } - hex.erase(hex.find_last_not_of('0') + 1); - return hex; -} - -// Returns true when the ot value carries a valid rv sub-key. -bool ExplicitRandomness(nostd::string_view ot_value, uint64_t *randomness) noexcept -{ - while (!ot_value.empty()) - { - size_t end = ot_value.find(';'); - nostd::string_view sub_key = ot_value.substr(0, end); - ot_value = end == nostd::string_view::npos ? "" : ot_value.substr(end + 1); - - if (sub_key.substr(0, 3) == "rv:") - return ParseHex56(sub_key.substr(3), randomness); - } - return false; -} - -// Sub-keys that are empty, lack a key:value separator, or duplicate th are dropped. -std::string SetThresholdSubKey(nostd::string_view ot_value, const std::string &threshold) -{ - std::string result; - bool replaced = false; - while (!ot_value.empty()) - { - size_t end = ot_value.find(';'); - nostd::string_view sub_key = ot_value.substr(0, end); - ot_value = end == nostd::string_view::npos ? "" : ot_value.substr(end + 1); - - if (sub_key.empty() || sub_key.find(':') == nostd::string_view::npos) - continue; - if (sub_key.substr(0, 3) == "th:") - { - if (replaced) - continue; - replaced = true; - if (!result.empty()) - result += ';'; - result += "th:" + threshold; - continue; - } - if (!result.empty()) - result += ';'; - result.append(sub_key.data(), sub_key.size()); - } - if (!replaced) - { - if (!result.empty()) - result += ';'; - result += "th:" + threshold; - } - return result; -} } // namespace OPENTELEMETRY_BEGIN_NAMESPACE @@ -132,22 +39,6 @@ namespace sdk { namespace trace { -namespace -{ -// Removes the inherited (now stale) "th" sub-key, keeping the other ot sub-keys. -nostd::shared_ptr EraseThreshold( - const nostd::shared_ptr &parent_trace_state, - const std::string &ot_value) -{ - OtelTraceState ot_state = OtelTraceState::Parse(ot_value); - ot_state.has_threshold = false; - std::string stripped = ot_state.Serialize(); - auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); - if (!stripped.empty()) - trace_state = trace_state->Set(kOtTraceStateKey, stripped); - return trace_state; -} -} // namespace ProbabilitySampler::ProbabilitySampler(double ratio) : description_("ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + "}"), @@ -163,14 +54,26 @@ SamplingResult ProbabilitySampler::ShouldSample( const trace_api::SpanContextKeyValueIterable & /*links*/) noexcept { auto parent_trace_state = parent_context.trace_state(); + if (!parent_trace_state) + { + parent_trace_state = trace_api::TraceState::GetDefault(); + } + std::string ot_value; - bool has_ot = parent_trace_state->Get(kOtTraceStateKey, ot_value); + parent_trace_state->Get(kOtTraceStateKey, ot_value); + OtelTraceState ot_state = OtelTraceState::Parse(ot_value); bool drop = threshold_ == kMaxThreshold; if (!drop) { uint64_t randomness = 0; - if (!ExplicitRandomness(ot_value, &randomness)) + if (ot_state.has_random_value) + { + // An explicit "rv" from an upstream Level 2 participant wins over the + // trace id, keeping the sampling decision consistent across the trace. + randomness = ot_state.random_value; + } + else { if (parent_context.IsValid() && !parent_context.trace_flags().IsRandom()) { @@ -187,31 +90,27 @@ SamplingResult ProbabilitySampler::ShouldSample( drop = randomness < threshold_; } + // Record the effective threshold when sampling; a dropped span carries no + // probability, so its inherited (now stale) "th" must be erased. The "rv" + // sub-key and any other "ot" sub-keys are preserved by OtelTraceState. if (drop) { - return {Decision::DROP, nullptr, EraseThreshold(parent_trace_state, ot_value)}; + ot_state.has_threshold = false; } - - std::string new_ot_value = SetThresholdSubKey(ot_value, EncodeThreshold(threshold_)); - if (!trace_api::TraceState::IsValidValue(new_ot_value)) - return {Decision::RECORD_AND_SAMPLE, nullptr, EraseThreshold(parent_trace_state, ot_value)}; - - if (!has_ot) + else { - size_t entry_count = 0; - parent_trace_state->GetAllEntries([&entry_count](nostd::string_view, nostd::string_view) { - ++entry_count; - return true; - }); - if (entry_count >= trace_api::TraceState::kMaxKeyValuePairs) - return {Decision::RECORD_AND_SAMPLE, nullptr, {}}; + ot_state.has_threshold = true; + ot_state.threshold = threshold_; } - auto trace_state = - has_ot ? parent_trace_state->Delete(kOtTraceStateKey)->Set(kOtTraceStateKey, new_ot_value) - : parent_trace_state->Set(kOtTraceStateKey, new_ot_value); + std::string new_ot_value = ot_state.Serialize(); + auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); + if (!new_ot_value.empty()) + { + trace_state = trace_state->Set(kOtTraceStateKey, new_ot_value); + } - return {Decision::RECORD_AND_SAMPLE, nullptr, trace_state}; + return {drop ? Decision::DROP : Decision::RECORD_AND_SAMPLE, nullptr, trace_state}; } nostd::string_view ProbabilitySampler::GetDescription() const noexcept diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index 5f26bb4916..7d4d67dcbe 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -177,7 +177,7 @@ TEST(ProbabilitySampler, ExplicitRandomnessTakesPrecedence) std::string ot_value; ASSERT_TRUE(sampling_result.trace_state->Get("ot", ot_value)); - ASSERT_EQ("rv:ffffffffffffff;th:8", ot_value); + ASSERT_EQ("th:8;rv:ffffffffffffff", ot_value); // An rv of zero drops even when the trace id randomness would sample. trace_state = trace_api::TraceState::FromHeader("ot=rv:00000000000000"); @@ -302,7 +302,7 @@ TEST(ProbabilitySampler, MalformedSubKeysAreDropped) trace_api::SpanId span_id{span_id_buffer}; // Empty, key-only, and duplicate th sub-keys are dropped from the output. - for (auto p : {std::make_pair("ot=a:1;;rv:ffffffffffffff", "a:1;rv:ffffffffffffff;th:8"), + for (auto p : {std::make_pair("ot=a:1;;rv:ffffffffffffff", "th:8;rv:ffffffffffffff;a:1"), std::make_pair("ot=foo", "th:8"), std::make_pair("ot=th:1;th:2", "th:8")}) { auto trace_state = trace_api::TraceState::FromHeader(p.first); @@ -328,7 +328,7 @@ TEST(ProbabilitySampler, FullTraceStateKeepsParentTraceState) trace_api::SpanId span_id{span_id_buffer}; // 32 entries and no ot entry: there is no room to add th, so the span is - // sampled but the trace state is left untouched. + // sampled and the parent entries are preserved without an ot entry. std::string header = "k0=0"; for (int i = 1; i < 32; ++i) { @@ -340,10 +340,17 @@ TEST(ProbabilitySampler, FullTraceStateKeepsParentTraceState) auto sampling_result = SampleWithContext(s1, context, TraceIdWithRandomness(0xffffffffffffff)); ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); - ASSERT_EQ(nullptr, sampling_result.trace_state); + ASSERT_NE(nullptr, sampling_result.trace_state); + + std::string ot_value; + ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value)); + + std::string k0_value; + ASSERT_TRUE(sampling_result.trace_state->Get("k0", k0_value)); + ASSERT_EQ("0", k0_value); } -TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) +TEST(ProbabilitySampler, OversizedSubKeyDroppedToRecordThreshold) { ProbabilitySampler s1(0.5); @@ -352,7 +359,8 @@ TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; trace_api::SpanId span_id{span_id_buffer}; - // The oversized ot value is re-emitted unchanged because it has no th to erase. + // A sampled span must record th; the foreign sub-key would push the ot value + // past the 256 char limit, so it is dropped to make room for th. std::string ot_value = "a:" + std::string(253, 'b'); auto trace_state = trace_api::TraceState::FromHeader("ot=" + ot_value); trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); @@ -364,12 +372,11 @@ TEST(ProbabilitySampler, OversizedValueKeepsParentTraceState) std::string result_ot; ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); - ASSERT_EQ(ot_value, result_ot); + ASSERT_EQ("th:8", result_ot); } -TEST(ProbabilitySampler, OversizedValueErasesStaleThreshold) +TEST(ProbabilitySampler, OversizedSubKeyDroppedReplacesStaleThreshold) { - // ratio 0.1's 17-char th overflows the 256-char limit, so the stale th is erased. ProbabilitySampler s1(0.1); uint8_t trace_id_buffer[trace_api::TraceId::kSize] = {1}; @@ -377,6 +384,8 @@ TEST(ProbabilitySampler, OversizedValueErasesStaleThreshold) uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; trace_api::SpanId span_id{span_id_buffer}; + // The inherited th is replaced by this sampler's th; the foreign sub-key + // overflows the 256 char limit and is dropped, keeping the fresh th. std::string rest = "x:" + std::string(245, 'b'); auto trace_state = trace_api::TraceState::FromHeader("ot=th:8;" + rest); trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); @@ -388,8 +397,8 @@ TEST(ProbabilitySampler, OversizedValueErasesStaleThreshold) std::string result_ot; ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); - ASSERT_EQ(rest, result_ot); - ASSERT_EQ(std::string::npos, result_ot.find("th:")); + ASSERT_NE(std::string::npos, result_ot.find("th:")); + ASSERT_EQ(std::string::npos, result_ot.find("x:")); } TEST(ProbabilitySampler, IgnoresParentSampledFlag) From 1815c46dc37329c5288b11dcd4513abb84ad29d4 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:55:53 -0400 Subject: [PATCH 06/61] [API] fix the trace state regex to comply with the w3c trace context level 2 (#4194) --- CHANGELOG.md | 9 ++++++++ api/include/opentelemetry/trace/trace_state.h | 22 +++++-------------- api/test/trace/trace_state_test.cc | 22 +++++++++++++++++++ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5849d5b5..24547e1068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ Increment the: ## [Unreleased] +* [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`). + [#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194) + * [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example [#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065) 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/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) From 874262a570403fcceb5dc1fed093f4decde33b42 Mon Sep 17 00:00:00 2001 From: Mateen Anjum Date: Thu, 2 Jul 2026 02:51:36 -0400 Subject: [PATCH 07/61] [CODE HEALTH] Move trace and baggage propagation test classes into anonymous namespace (#4199) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ api/test/baggage/propagation/baggage_propagator_test.cc | 5 +++++ api/test/trace/propagation/b3_propagation_test.cc | 5 +++++ api/test/trace/propagation/http_text_format_test.cc | 5 +++++ api/test/trace/propagation/jaeger_propagation_test.cc | 5 +++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index d95edcab17..fa7407d999 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 328 + warning_limit: 324 - cmake_options: all-options-abiv2-preview - warning_limit: 338 + warning_limit: 334 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 24547e1068..53bb11421c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,9 @@ Increment the: * [CONFIGURATION] Apply default sampler when none is specified [#4170](https://github.com/open-telemetry/opentelemetry-cpp/pull/4170) +* [CODE HEALTH] Move trace and baggage propagation test classes into anonymous namespace + [#4199](https://github.com/open-telemetry/opentelemetry-cpp/pull/4199) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev diff --git a/api/test/baggage/propagation/baggage_propagator_test.cc b/api/test/baggage/propagation/baggage_propagator_test.cc index 6335b2984b..fcb3a7f56d 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: @@ -120,3 +123,5 @@ TEST(BaggagePropagatorTest, InjectEmptyHeader) EXPECT_EQ(carrier.headers_.find(baggage::kBaggageHeader), carrier.headers_.end()); } } + +} // namespace 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 From 9268358b3546a238675a3549aafc874bc435e4b7 Mon Sep 17 00:00:00 2001 From: WenTao Ou Date: Thu, 2 Jul 2026 22:00:09 +0800 Subject: [PATCH 08/61] [CMAKE] Add pkg-config support for OpenTelemetry proto installation (#4192) --- cmake/opentelemetry-proto.cmake | 36 ++++++++++++++++++++++++--------- cmake/pkgconfig.cmake | 2 ++ 2 files changed, 29 insertions(+), 9 deletions(-) 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) From d5842f986ed6d5ab66e2fe8b90035e14201dae31 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:48:28 -0400 Subject: [PATCH 09/61] [CMAKE] Fix CMake WITH_API_ONLY option (#4201) --- .github/workflows/ci.yml | 22 +++ CHANGELOG.md | 5 +- CMakeLists.txt | 54 ++++++-- ci/do_ci.sh | 12 +- .../test/cmake/api_only_test/CMakeLists.txt | 117 ++++++++++++++++ install/test/src/test_api.cc | 128 +++++++++++++++++- 6 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 install/test/cmake/api_only_test/CMakeLists.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97d89c74c..9098076489 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bb11421c..b08cdea1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CMAKE] Fix and test WITH_API_ONLY option + [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) + * [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. @@ -25,7 +28,7 @@ Increment the: [#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194) * [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example - [#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065) + [#4065](https://github.com/open-telemetry/opentelemetry-cpp/pull/4065) * [RELEASE] Bump main branch to 1.28.0-dev [#4081](https://github.com/open-telemetry/opentelemetry-cpp/pull/4081) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1431475bf6..e44aa1544f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/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/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); +} From 605dd459df0e01bd1820cfe0bae0f12a89d2c97e Mon Sep 17 00:00:00 2001 From: Mateen Anjum Date: Thu, 2 Jul 2026 19:17:09 -0400 Subject: [PATCH 10/61] [CODE HEALTH] Move context propagation test classes into anonymous namespace (#4200) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ api/test/context/propagation/composite_propagator_test.cc | 5 +++++ api/test/context/propagation/environment_carrier_test.cc | 5 +++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index fa7407d999..1225f05511 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 324 + warning_limit: 321 - cmake_options: all-options-abiv2-preview - warning_limit: 334 + warning_limit: 331 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index b08cdea1b6..cc7b7f973e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,9 @@ Increment the: * [CODE HEALTH] Move trace and baggage propagation test classes into anonymous namespace [#4199](https://github.com/open-telemetry/opentelemetry-cpp/pull/4199) +* [CODE HEALTH] Move context propagation test classes into anonymous namespace + [#4200](https://github.com/open-telemetry/opentelemetry-cpp/pull/4200) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev 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 From adde231a0304a276d1db42ebbd166c9da9aeceb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:52:00 +0800 Subject: [PATCH 11/61] [CODE HEALTH] Fix clang-tidy bugprone-unused-local-non-trivial-variable warnings (#4202) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ api/test/baggage/baggage_benchmark.cc | 4 ++-- examples/otlp/file_metric_main.cc | 3 --- examples/otlp/http_instrumented_main.cc | 3 --- examples/otlp/http_metric_main.cc | 3 --- exporters/otlp/test/otlp_file_client_test.cc | 4 ---- exporters/prometheus/src/exporter_utils.cc | 2 -- 8 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 1225f05511..4123329d86 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 321 + warning_limit: 309 - cmake_options: all-options-abiv2-preview - warning_limit: 331 + warning_limit: 319 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc7b7f973e..84a0a86a1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,6 +137,9 @@ Increment the: * [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) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev 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/examples/otlp/file_metric_main.cc b/examples/otlp/file_metric_main.cc index 96aef81051..1e484485b1 100644 --- a/examples/otlp/file_metric_main.cc +++ b/examples/otlp/file_metric_main.cc @@ -40,9 +40,6 @@ void InitMetrics() { 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); diff --git a/examples/otlp/http_instrumented_main.cc b/examples/otlp/http_instrumented_main.cc index 7a82abde27..7f45c44ec4 100644 --- a/examples/otlp/http_instrumented_main.cc +++ b/examples/otlp/http_instrumented_main.cc @@ -217,9 +217,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); diff --git a/examples/otlp/http_metric_main.cc b/examples/otlp/http_metric_main.cc index 59527d4514..0e1403debd 100644 --- a/examples/otlp/http_metric_main.cc +++ b/examples/otlp/http_metric_main.cc @@ -43,9 +43,6 @@ void InitMetrics() { 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); diff --git a/exporters/otlp/test/otlp_file_client_test.cc b/exporters/otlp/test/otlp_file_client_test.cc index 91ddcc944c..6260559720 100644 --- a/exporters/otlp/test/otlp_file_client_test.cc +++ b/exporters/otlp/test/otlp_file_client_test.cc @@ -267,8 +267,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 +396,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/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index 55105bace8..694a30a079 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(); From 065c9daa9a9bc63c4242c36960eafd48ea33bce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Fri, 3 Jul 2026 18:34:52 +0800 Subject: [PATCH 12/61] [SDK] LogRecord attribute limits enforcement (#4157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SDK] LogRecord attribute limits enforcement Apply attribute count and value length limits described by the logs SDK spec (https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits) during attribute writes. Limits flow from LoggerProvider through LoggerContext to each LogRecord created by Logger::CreateLogRecord. * Add LogRecordLimits struct with spec defaults: attribute_count_limit = 128 and attribute_value_length_limit = SIZE_MAX (unlimited). * Recordable gains a virtual SetLogRecordLimits with a no-op default so existing implementations need not change. ReadableLogRecord gains a virtual GetDroppedAttributesCount returning zero by default. * ReadWriteLogRecord and OtlpLogRecordable enforce the limits in SetAttribute. An attribute beyond attribute_count_limit is dropped and counted as dropped; string and string-array values whose byte length exceeds attribute_value_length_limit are truncated. Truncation is byte-level, mirroring the existing Span attribute behavior. The OTLP path also populates dropped_attributes_count on the proto LogRecord. * MultiRecordable propagates the limits to every wrapped recordable. * LoggerContext owns a LogRecordLimits value; Logger calls SetLogRecordLimits on the recordable returned by MakeRecordable before any user attribute writes. A new LoggerProviderFactory::Create overload accepts LogRecordLimits. * The declarative configuration path (SdkBuilder) wires LogRecordLimitsConfiguration to the runtime LogRecordLimits. Tests cover ReadWriteLogRecord and OtlpLogRecordable: defaults, count enforcement (including the "replace existing key while at limit must not drop" case), length truncation of strings and string arrays, type selectivity (only string and array-of-string are truncated), the combined count plus length case, and a Logger-level wiring test that verifies the limits configured on LoggerProvider reach the recordable returned by Logger::CreateLogRecord. Fixes #4126 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: address review (store by value, OTLP UTF-8-aware truncate) Address three review comments from @lalitb on PR #4157, plus a follow-up from @owent (r3434178971). 1. Store LogRecordLimits by value inside ReadWriteLogRecord and OtlpLogRecordable instead of a raw pointer to a LoggerContext-owned object. A LogRecord is handed out as a unique_ptr, so a record outliving the context that produced it would otherwise dereference a dangling pointer when the user calls SetAttribute() later. The default-constructed value already carries the spec defaults (count=128, length=unlimited), so a fresh recordable enforces the spec count cap from construction, matching the PR's "enforcement" contract. 2. Drop the now-redundant `limits_ != nullptr` short-circuit at every enforcement site (4 in total). This also closes the Codecov-reported uncovered branch in otlp_log_recordable.cc. 3. Truncate OTLP string attributes at a UTF-8 code-point boundary instead of a raw byte boundary, so the protobuf string_value produced by truncation stays valid UTF-8 when the input was. Malformed UTF-8 and trailing lead bytes degrade to plain byte truncation. Logic adapted from #4132 with attribution. The SDK-side ReadWriteLogRecord truncation stays as plain byte cut. The in-memory `OwnedAttributeValue::std::string` variant may legitimately carry raw bytes when constructed from a non-UTF-8 source, so forcing UTF-8 boundary semantics there would over-truncate that case (per @owent's r3434178971, echoing the same point on #4132 r3409677314). Each recordable's truncation strategy now matches its own consumer's wire-format requirement: SDK in-memory has no wire requirement, OTLP protobuf requires valid UTF-8. While in the same truncation paths, also apply the byte-length cap to raw bytes attributes (`vector` on the SDK side, AnyValue `bytes_value` on the OTLP side). Both were previously passing through any size, even though the spec applies `attribute_value_length_limit` to bytes attributes as well. Test changes: - Rename DefaultsPassThroughWithoutLimitsObject to DefaultRecordEnforcesSpecCountCap (200 attrs in, 128 stored, 72 dropped) to reflect the new spec-correct default behavior. - Add bytes-truncation tests on both SDK and OTLP sides. - Add 3 UTF-8 regression tests on the OTLP side only (split prevention, exact fit at sequence boundary; malformed-fallback omitted since the algorithm's seq=1 fallback for invalid continuations is implementation detail rather than wire contract). - Add a default-cap test on the OTLP side. Refs: open-telemetry/opentelemetry-cpp#4126 Co-authored-by: Hyeonho Kim Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: address dbarker review (extract utilities, dedupe SetAttribute lookup) Address @dbarker's three review comments on PR #4157. * r3442854800: Extract Utf8SafePrefixLength and TruncateProtoAttributeValue from the anonymous namespace in otlp_log_recordable.cc into OtlpPopulateAttributeUtils as static methods, with new direct unit tests in otlp_populate_attribute_utils_test.cc. The upcoming SpanLimits PR will reuse these from the OTLP trace recordable. The OTLP helper that was previously TruncateProtoStringValue is renamed TruncateProtoAttributeValue to reflect that it covers string_value, bytes_value, and array_value branches. * r3443005793: Extract the SDK byte-length truncation helper into sdk::common::TruncateAttributeValueByteLength (inline, declared in the existing sdk/common/attribute_utils.h), with new direct unit tests in attribute_utils_test.cc. The upcoming SpanLimits PR will reuse this from ReadWriteSpanData. The new name reflects that the helper covers string, string-array, AND bytes variants rather than only strings. * r3443034336: Rewrite ReadWriteLogRecord::SetAttribute to use a single unordered_map lookup. The previous code did .find() to gate the count cap, then operator[] to fetch-or-insert; the new code does .find() followed by conditional .emplace(), so existing-key replacement and new-key insertion each cost one hash lookup. Refs: open-telemetry/opentelemetry-cpp#4126 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: fix noexcept correctness + restore Bazel link Two small fixes on top of c4532cd1: * Address @dbarker's r3444034234: rewrite sdk::common::TruncateAttributeValueByteLength to dispatch on the variant via nostd::get_if (returns nullptr if the alternative does not hold) instead of nostd::holds_alternative + nostd::get. The helper is declared noexcept; nostd::get throws when the alternative does not match, which would invoke std::terminate even though the preceding holds_alternative check makes that path unreachable in practice. The get_if rewrite removes the throwing call entirely so the noexcept contract is statically honored. * Restore the Bazel link of the new otlp_populate_attribute_utils_test target by adding //sdk/src/metrics to its deps, matching the existing otlp_log_recordable_test target. The new test target links against :otlp_recordable, which transitively references sdk::metrics::AdaptingCircularBufferCounter symbols; Bazel's strict layering requires the dep to be declared at the cc_test level. CMake did not catch this because its default link aggregates the whole library. Refs: open-telemetry/opentelemetry-cpp#4126 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: fix MSVC variable shadow + drop dead includes + clang-tidy NOLINT Three CI failure classes surfaced after the branch-update merge of main into the PR branch (ccdd5e9286). Address each: * MSVC C2220 (warning-as-error) on attribute_utils.h:93 — the `for (auto &s : *vec)` loop inside TruncateAttributeValueByteLength shadowed the `if (auto *s = get_if(...))` on line 84. GCC/Clang accept if-init scoping for the two `s`, MSVC /W4 /WX treats C4456 (declaration hides previous local) as an error. Rename the loop variable to `element` to remove the shadow. * IWYU drop four includes that the v3+v4 utility-extraction refactor made redundant: - sdk/src/logs/read_write_log_record.cc: - exporters/otlp/src/otlp_log_recordable.cc: - exporters/otlp/test/otlp_populate_attribute_utils_test.cc: and * clang-tidy abiv2-preview misc-no-recursion on OtlpPopulateAttributeUtils::TruncateProtoAttributeValue — the recursive descent into AnyValue::kArrayValue children is intentional and bounded by the SDK-side AttributeValue variant depth. Suppress with a NOLINTBEGIN/END(misc-no-recursion) block, matching the codebase precedent in sdk/src/configuration/configuration_parser.cc. The remaining clang-tidy bugprone-exception-escape warnings in the same config (read_write_log_record.cc:68 SetBody, :158 SetAttribute and otlp_populate_attribute_utils.cc:62/220 PopulateAnyValue) trace through nostd::visit / nostd::get into bad_alloc paths that pre-date this PR; they are out of scope for the LogRecord limits change. Refs: open-telemetry/opentelemetry-cpp#4126 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: address dbarker v3 review (truncate during conversion) Address @dbarker's three review comments from 2026-06-23 (r3461438236, r3461500683, r3461573442) by moving attribute-value-length truncation from a separate post-conversion step into the conversion paths themselves. Architecture change: Before: convert, default-construct map slot, assign converted, post-truncate (helper) After: convert-with-limit, single emplace sdk/include/opentelemetry/sdk/common/attribute_utils.h * AttributeConverter gains an `explicit AttributeConverter(std::size_t max_length)` constructor. String, bytes, and string-array overloads apply the byte-length cap during the OwnedAttributeValue construction. The default-constructed converter is unchanged (`max_length_ = numeric_limits::max()`), so the existing callers in `instrumentation_scope.h` and `read_write_log_record.cc SetBody` keep their current no-truncation behavior. * Delete `TruncateAttributeValueByteLength` (its three branches now live in the converter; the lone production caller was the SDK ReadWriteLogRecord SetAttribute). sdk/src/logs/read_write_log_record.cc * `SetAttribute` rewritten to a single `find` + conditional `emplace`, with the limit-aware converter producing the truncated value in one step. Removes the previous default-construct-then-assign sequence and the now-redundant post-truncate branch. exporters/otlp/include/.../otlp_populate_attribute_utils.h exporters/otlp/src/otlp_populate_attribute_utils.cc * All four `PopulateAttribute` / `PopulateAnyValue` overloads gain a trailing `std::size_t max_length = numeric_limits::max()` parameter (default preserves the existing call-site behavior for the other callers in metrics/recordable/resource paths). * String and bytes branches apply truncation in place during the proto set: `Utf8SafePrefixLength` for `string_value` (preserves protobuf wire-format valid UTF-8), plain byte cut for `bytes_value`. * Delete `TruncateProtoAttributeValue`. The flat-AttributeValue input means the recursive array descent it carried was dead code, as dbarker noted. The `NOLINT(misc-no-recursion)` block goes with it. * `Utf8SafePrefixLength` primary overload now takes `(const char*, std::size_t, std::size_t)` so it can be called without constructing a temporary `std::string` from a `string_view`. A thin inline `(const std::string&, std::size_t)` overload preserves backward compatibility for any existing direct user. exporters/otlp/src/otlp_log_recordable.cc * `SetAttribute` now calls the limit-aware `PopulateAttribute` directly in one step, removing the separate post-truncate branch. Test changes: * Delete the five `TruncateAttributeValueByteLength` unit tests in `sdk/test/common/attribute_utils_test.cc` (helper is gone; end-to-end coverage is preserved by `log_record_limits_test.cc`). * Delete the six `TruncateProtoAttributeValue` unit tests in `exporters/otlp/test/otlp_populate_attribute_utils_test.cc` (helper is gone; end-to-end coverage is preserved by `otlp_log_recordable_test.cc`). * The seven `Utf8SafePrefixLength` unit tests still cover the helper that the new in-place truncation path calls into. Refs: open-telemetry/opentelemetry-cpp#4126 Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: gate SetLogRecordLimits on processor capability CreateLogRecord() called SetLogRecordLimits() on every record unconditionally, paying a virtual dispatch even for recordables that do not enforce limits. Add a RecordableEnforcesLogRecordLimits() capability query on LogRecordExporter and LogRecordProcessor (default false; OTLP and ostream exporters return true; Simple and Batch processors delegate to their exporter; Multi returns true if any child does). LoggerContext caches the result at construction and refreshes it in AddProcessor, so the Logger hot path reads a plain bool and only calls SetLogRecordLimits() when a processor actually enforces limits. The new virtuals are appended at the end of their vtables to keep the change additive. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: UTF-8-safe truncation in AttributeConverter The in-memory AttributeConverter cut string values at a raw byte boundary, which can split a multi-byte UTF-8 sequence and leave an invalid std::string. Move Utf8SafePrefixLength from the OTLP recordable into sdk::common so the converter can share it, and truncate the std::string, string_view, const char*, and string-array alternatives at a UTF-8 code-point boundary. Raw bytes (span) keep the exact byte cut since they carry no encoding. The OTLP helper now forwards to the shared implementation, leaving its public surface and tests unchanged. Also fold in review nits: delegate the const char* overload to the string_view overload to avoid copying an oversized C string before truncating, drop a redundant pointer cast in the bytes overload, and use std::strlen. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: satisfy the clang-tidy warning limit A recent merge of main lowered the abiv1-preview and abiv2-preview clang-tidy ceilings to 331 and 341. Two warnings need addressing to stay within them: - Initialize LoggerContext::recordable_enforces_limits_ in the member initializer list instead of the constructor body (cppcoreguidelines-prefer-member-initializer). - Move the rvalue reference parameter in the test TrackingProcessor OnEmit stub (cppcoreguidelines-rvalue-reference-param-not-moved). Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: default recordable to no limits, inject via provider A default-constructed ReadWriteLogRecord / OtlpLogRecordable previously carried the spec-default LogRecordLimits (count 128), so a recordable used without any LoggerProvider wiring enforced the 128 cap. That changed behavior for code that constructs a recordable directly. Default the recordable to no limits and let the LoggerProvider wiring inject the configured limits through SetLogRecordLimits, which it already does only when a processor enforces them. LoggerContext keeps the spec default, so a record created through the standard provider path still gets the 128 cap (or the configured value), while a bare recordable no longer caps on its own. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: address dbarker review (comments, UTF-8 scan shortcut, test) - Correct the SetLogRecordLimits comments on the base Recordable, ReadWriteLogRecord, and OtlpLogRecordable: the limits are copied, so the caller does not need to keep the supplied object alive (the previous "must outlive" wording was stale). - Drop the vtable-append implementation-detail paragraph from the RecordableEnforcesLogRecordLimits comments on LogRecordExporter and LogRecordProcessor. - Short-circuit Utf8SafePrefixLength when the whole value fits within the budget (max_bytes >= size, including the unbounded default), so the common path skips the per-byte scan. - Add a const char* attribute value truncation test. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> * [SDK] LogRecord limits: drop vtable implementation-detail comment Remove the second paragraph of the Recordable::SetLogRecordLimits doc comment (the vtable-append note) per review. It is an implementation detail and matches the paragraphs already dropped from the exporter and processor headers. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> --------- Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com> Co-authored-by: Hyeonho Kim Co-authored-by: Doug Barker <3782873+dbarker@users.noreply.github.com> Co-authored-by: Tom Tan Co-authored-by: Marc Alff --- CHANGELOG.md | 3 + .../exporters/ostream/log_record_exporter.h | 7 + exporters/otlp/BUILD | 17 + exporters/otlp/CMakeLists.txt | 10 + .../otlp/otlp_file_log_record_exporter.h | 6 + .../otlp/otlp_grpc_log_record_exporter.h | 6 + .../otlp/otlp_http_log_record_exporter.h | 6 + .../exporters/otlp/otlp_log_recordable.h | 15 + .../otlp/otlp_populate_attribute_utils.h | 70 +++- exporters/otlp/src/otlp_log_recordable.cc | 17 +- .../otlp/src/otlp_populate_attribute_utils.cc | 84 +++-- .../otlp/test/otlp_log_recordable_test.cc | 130 +++++++ .../otlp_populate_attribute_utils_test.cc | 76 ++++ .../sdk/common/attribute_utils.h | 128 ++++++- .../sdk/logs/batch_log_record_processor.h | 5 + sdk/include/opentelemetry/sdk/logs/exporter.h | 9 + .../sdk/logs/log_record_limits.h | 48 +++ .../opentelemetry/sdk/logs/logger_context.h | 26 +- .../sdk/logs/logger_provider_factory.h | 10 + .../sdk/logs/multi_log_record_processor.h | 2 + .../opentelemetry/sdk/logs/multi_recordable.h | 7 + .../opentelemetry/sdk/logs/processor.h | 10 + .../sdk/logs/read_write_log_record.h | 21 ++ .../sdk/logs/readable_log_record.h | 8 + .../opentelemetry/sdk/logs/recordable.h | 12 + .../sdk/logs/simple_log_record_processor.h | 5 + sdk/src/configuration/sdk_builder.cc | 30 +- sdk/src/logs/logger.cc | 8 + sdk/src/logs/logger_context.cc | 25 +- sdk/src/logs/logger_provider_factory.cc | 14 + sdk/src/logs/multi_log_record_processor.cc | 12 + sdk/src/logs/multi_recordable.cc | 11 + sdk/src/logs/read_write_log_record.cc | 30 +- sdk/test/common/attribute_utils_test.cc | 87 +++++ sdk/test/logs/BUILD | 16 + sdk/test/logs/CMakeLists.txt | 1 + sdk/test/logs/log_record_limits_test.cc | 348 ++++++++++++++++++ 37 files changed, 1255 insertions(+), 65 deletions(-) create mode 100644 exporters/otlp/test/otlp_populate_attribute_utils_test.cc create mode 100644 sdk/include/opentelemetry/sdk/logs/log_record_limits.h create mode 100644 sdk/test/logs/log_record_limits_test.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a0a86a1a..3d9ab73e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,9 @@ Increment the: * [CI] iwyu and clang-tidy: use install_thirdparty.sh for third-party [#4136](https://github.com/open-telemetry/opentelemetry-cpp/pull/4136) +* [SDK] LogRecord attribute limits enforcement + [#4157](https://github.com/open-telemetry/opentelemetry-cpp/pull/4157) + * [CONFIGURATION] File configuration: declarative resource detection types [#4148](https://github.com/open-telemetry/opentelemetry-cpp/pull/4148) 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/otlp/BUILD b/exporters/otlp/BUILD index e28ba407b2..86b26c57be 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -666,6 +666,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"], diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 7cbe414785..89e229ca4a 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( 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_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_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/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/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc index b722c6c055..d471c86414 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" @@ -419,6 +420,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/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index 3010c6b9bd..c2e75a0f97 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 @@ -68,11 +71,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 +167,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 +200,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 +217,9 @@ struct AttributeConverter { return OwnedAttributeValue(std::vector(vals.begin(), vals.end())); } + +private: + std::size_t max_length_ = (std::numeric_limits::max)(); }; /** 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_context.h b/sdk/include/opentelemetry/sdk/logs/logger_context.h index 038da2d948..65d740f722 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,21 @@ 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; + /** * Force all active LogProcessors to flush any buffered logs * within the given timeout. @@ -93,6 +110,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_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/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index 1574d1cab0..b6641a0dbd 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -64,6 +64,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" @@ -133,6 +134,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" @@ -1915,20 +1917,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..80cd8eddfd 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -162,6 +162,10 @@ opentelemetry::nostd::unique_ptr Logger::CreateL auto recordable = context_->GetProcessor().MakeRecordable(); + if (context_->RecordableEnforcesLogRecordLimits()) + { + recordable->SetLogRecordLimits(context_->GetLogRecordLimits()); + } recordable->SetObservedTimestamp(std::chrono::system_clock::now()); StampSpanContextFromVariant( @@ -184,6 +188,10 @@ opentelemetry::nostd::unique_ptr Logger::CreateL auto recordable = context_->GetProcessor().MakeRecordable(); + if (context_->RecordableEnforcesLogRecordLimits()) + { + recordable->SetLogRecordLimits(context_->GetLogRecordLimits()); + } recordable->SetObservedTimestamp(std::chrono::system_clock::now()); StampSpanContextFromVariant(context_or_span, *recordable); diff --git a/sdk/src/logs/logger_context.cc b/sdk/src/logs/logger_context.cc index f7ad9db0c6..a1e8defbfa 100644 --- a/sdk/src/logs/logger_context.cc +++ b/sdk/src/logs/logger_context.cc @@ -7,6 +7,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/multi_log_record_processor.h" @@ -20,20 +21,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 +57,16 @@ 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_; +} + bool LoggerContext::ForceFlush(std::chrono::microseconds timeout) noexcept { return processor_->ForceFlush(timeout); 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/test/common/attribute_utils_test.cc b/sdk/test/common/attribute_utils_test.cc index 466179594c..c4b6abe378 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,89 @@ TEST(AttributeMapTest, EqualTo) EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_size)); EXPECT_FALSE(attribute_map.EqualTo(kv_iterable_different_all)); } + +// --------------------------------------------------------------------------- +// 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/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/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); +} From 4fa9bfde83e827abbdfa75193f3bc9351dfb0c41 Mon Sep 17 00:00:00 2001 From: Shreyas S Joshi <156504459+BlackPool25@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:31:18 +0530 Subject: [PATCH 13/61] [SDK] Rename ComposableTraceIdRatioBasedSampler to ComposableProbabilitySampler (#4210) --- CHANGELOG.md | 6 ++++- ...ce_id_ratio.h => composable_probability.h} | 6 ++--- sdk/src/trace/CMakeLists.txt | 2 +- ..._id_ratio.cc => composable_probability.cc} | 10 ++++----- sdk/test/trace/composable_sampler_test.cc | 22 +++++++++---------- 5 files changed, 24 insertions(+), 22 deletions(-) rename sdk/include/opentelemetry/sdk/trace/samplers/{composable_trace_id_ratio.h => composable_probability.h} (85%) rename sdk/src/trace/samplers/{composable_trace_id_ratio.cc => composable_probability.cc} (77%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9ab73e97..3dfa92b6f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,13 +115,17 @@ Increment the: [#4144](https://github.com/open-telemetry/opentelemetry-cpp/pull/4144) * [SDK] Add ComposableSampler and CompositeSampler with the consistent - probability sampling variants (AlwaysOn, AlwaysOff, TraceIdRatioBased, + probability sampling variants (AlwaysOn, AlwaysOff, ComposableProbability, ParentThreshold, RuleBased) [#4028](https://github.com/open-telemetry/opentelemetry-cpp/issues/4028) * [SDK] Implement the ProbabilitySampler [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) +* [SDK] Rename ComposableTraceIdRatioBasedSampler to ComposableProbabilitySampler + to align with the OpenTelemetry specification + [#4161](https://github.com/open-telemetry/opentelemetry-cpp/issues/4161) + * [BUILD] Fix protobuf build failure [#4154](https://github.com/open-telemetry/opentelemetry-cpp/pull/4154) 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/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index 9444dc1cd6..b00ccf17dd 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -27,7 +27,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/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/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))); } From 17898c4c7b471c2417e42acbe6d754bdefce3cc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:39:26 +0200 Subject: [PATCH 14/61] Bump docker/build-push-action from 7.2.0 to 7.3.0 (#4204) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.2.0 to 7.3.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 7.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependencies_image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index 40bc4e1e6b..546ff4bd75 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -29,7 +29,7 @@ jobs: uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.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/ From 75316e517d40aaded0cc7435b3d054da296aeeea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:04:30 +0200 Subject: [PATCH 15/61] Bump docker/setup-qemu-action from 4.1.0 to 4.2.0 (#4205) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/06116385d9baf250c9f4dcb4858b16962ea869c3...96fe6ef7f33517b61c61be40b68a1882f3264fb8) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependencies_image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index 546ff4bd75..bf7fd6e9c5 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -22,7 +22,7 @@ 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 From b96abd8207c2b4006269e0000bbd573f07172661 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Sun, 5 Jul 2026 15:25:16 +0530 Subject: [PATCH 16/61] [DOC] add ProbabilitySampler to GettingStarted sampler example --- docs/public/sdk/GettingStarted.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/public/sdk/GettingStarted.rst b/docs/public/sdk/GettingStarted.rst index e250d4b617..f8babe7571 100644 --- a/docs/public/sdk/GettingStarted.rst +++ b/docs/public/sdk/GettingStarted.rst @@ -135,6 +135,10 @@ OpenTelemetry C++ SDK offers five samplers out of the box: auto always_off_sampler = std::unique_ptr (new sdktrace::TraceIdRatioBasedSampler(ratio)); + //ProbabilitySampler - Sample 50% generated spans using consistent probability sampling + auto probability_sampler = std::unique_ptr + (new sdktrace::ProbabilitySampler(ratio)); + TracerContext ^^^^^^^^^^^^^ From 0ab9ec6b943dca865f732e13ab380e170b203aa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:52:38 +0200 Subject: [PATCH 17/61] Bump docker/setup-buildx-action from 4.1.0 to 4.2.0 (#4207) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5...bb05f3f5519dd87d3ba754cc423b652a5edd6d2c) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependencies_image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index bf7fd6e9c5..d7388a24b8 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -26,7 +26,7 @@ jobs: - 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@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 From c2242937218f8311e2ad518ce0b03adaefd1f044 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:52:16 +0200 Subject: [PATCH 18/61] Bump rules_cc from 0.2.20 to 0.2.21 (#4214) Bumps [rules_cc](https://github.com/bazelbuild/rules_cc) from 0.2.20 to 0.2.21. - [Release notes](https://github.com/bazelbuild/rules_cc/releases) - [Commits](https://github.com/bazelbuild/rules_cc/compare/0.2.20...0.2.21) --- updated-dependencies: - dependency-name: rules_cc dependency-version: 0.2.21 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 07e443a3c3..c4d9574873 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,7 +19,7 @@ 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 = "rules_cc", version = "0.2.21") 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") From 4c84d5224a8e8792198b7034281b908875bd438d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:06:43 +0200 Subject: [PATCH 19/61] Bump github/codeql-action/upload-sarif from 4.36.2 to 4.36.3 (#4213) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 4d8cfca765..7bcdce7067 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 with: sarif_file: results.sarif From 5bc67180743f50449cb7459ecfb9c0b0f6ccbcf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:17:41 +0200 Subject: [PATCH 20/61] Bump fossas/fossa-action from 1.9.0 to 2.0.0 (#4212) Bumps [fossas/fossa-action](https://github.com/fossas/fossa-action) from 1.9.0 to 2.0.0. - [Release notes](https://github.com/fossas/fossa-action/releases) - [Commits](https://github.com/fossas/fossa-action/compare/ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3...29693cc50323968e039056be419b32989fc5880c) --- updated-dependencies: - dependency-name: fossas/fossa-action dependency-version: 2.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/fossa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 7fd9e5ef6c..f41b0e1a20 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -19,7 +19,7 @@ jobs: - 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 From 398fb919cdf504cffc45456300b59b94aec94ca5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:51:33 +0200 Subject: [PATCH 21/61] Bump github/codeql-action/init from 4.36.2 to 4.36.3 (#4209) * Bump github/codeql-action/init from 4.36.2 to 4.36.3 Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update .github/workflows/codeql-analysis.yml * Update .github/workflows/codeql-analysis.yml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Alff --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 166f552d43..41622c46df 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 From 6ca9a3ae2823beb64cc36a6414db42565a1c74c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:18:52 +0800 Subject: [PATCH 22/61] [CODE HEALTH] Move api/test and sdk/test classes into anonymous namespace (#4217) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ api/test/nostd/shared_ptr_test.cc | 5 +++++ api/test/nostd/unique_ptr_test.cc | 5 +++++ api/test/nostd/variant_test.cc | 5 +++++ api/test/singleton/singleton_test.cc | 5 +++++ sdk/test/common/global_log_handle_test.cc | 5 +++++ sdk/test/logs/batch_log_record_processor_test.cc | 5 +++++ sdk/test/logs/log_record_test.cc | 5 +++++ sdk/test/logs/logger_config_test.cc | 5 +++++ sdk/test/metrics/attributes_hashmap_test.cc | 5 +++++ sdk/test/metrics/cardinality_limit_test.cc | 5 +++++ sdk/test/metrics/circular_buffer_counter_test.cc | 5 +++++ sdk/test/metrics/meter_config_test.cc | 5 +++++ sdk/test/metrics/metric_test_stress.cc | 5 +++++ sdk/test/metrics/multi_metric_storage_test.cc | 5 +++++ sdk/test/metrics/periodic_exporting_metric_reader_test.cc | 5 +++++ sdk/test/metrics/sum_aggregation_test.cc | 5 +++++ sdk/test/metrics/sync_metric_storage_histogram_test.cc | 5 +++++ sdk/test/resource/resource_test.cc | 5 +++++ sdk/test/trace/simple_processor_test.cc | 5 +++++ sdk/test/trace/tracer_config_test.cc | 5 +++++ 22 files changed, 105 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 4123329d86..e7b4495a44 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 309 + warning_limit: 278 - cmake_options: all-options-abiv2-preview - warning_limit: 319 + warning_limit: 288 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfa92b6f0..1637f4e853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CODE HEALTH] Move api/test and sdk/test classes into anonymous namespace + [#4217](https://github.com/open-telemetry/opentelemetry-cpp/pull/4217) + * [CMAKE] Fix and test WITH_API_ONLY option [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) 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/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/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_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..f6bdedf944 100644 --- a/sdk/test/logs/logger_config_test.cc +++ b/sdk/test/logs/logger_config_test.cc @@ -89,6 +89,9 @@ const std::array instrumentati &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, }; +namespace +{ + // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultLoggerConfiguratorTestFixture : public ::testing::TestWithParam @@ -125,3 +128,5 @@ TEST(LoggerConfig, ScopeConfiguratorPreservesCustomConfig) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultLoggerConfiguratorTestFixture, ::testing::ValuesIn(instrumentation_scopes)); + +} // namespace diff --git a/sdk/test/metrics/attributes_hashmap_test.cc b/sdk/test/metrics/attributes_hashmap_test.cc index bd243fd3f2..04ccd4fe48 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: @@ -223,3 +226,5 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) EXPECT_NE(map_copy.Get(attr), nullptr); } } + +} // namespace diff --git a/sdk/test/metrics/cardinality_limit_test.cc b/sdk/test/metrics/cardinality_limit_test.cc index 01aaf79b0a..4770ad7d74 100644 --- a/sdk/test/metrics/cardinality_limit_test.cc +++ b/sdk/test/metrics/cardinality_limit_test.cc @@ -101,6 +101,9 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) } } +namespace +{ + class WritableMetricStorageCardinalityLimitTestFixture : public ::testing::TestWithParam {}; @@ -183,3 +186,5 @@ TEST(CardinalityLimitOverflowAttribute, MatchesSpecLiteral) 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/meter_config_test.cc b/sdk/test/metrics/meter_config_test.cc index a13db6e3e2..bdc737063d 100644 --- a/sdk/test/metrics/meter_config_test.cc +++ b/sdk/test/metrics/meter_config_test.cc @@ -71,6 +71,9 @@ const std::array instrumentati &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, }; +namespace +{ + // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultMeterConfiguratorTestFixture : public ::testing::TestWithParam @@ -91,3 +94,5 @@ TEST_P(DefaultMeterConfiguratorTestFixture, VerifyDefaultConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultMeterConfiguratorTestFixture, ::testing::ValuesIn(instrumentation_scopes)); + +} // 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..1a4e9c2545 100644 --- a/sdk/test/metrics/sum_aggregation_test.cc +++ b/sdk/test/metrics/sum_aggregation_test.cc @@ -414,6 +414,9 @@ TEST(CounterToSumFilterAttributesWithCardinalityLimit, Double) } } +namespace +{ + class UpDownCounterToSumFixture : public ::testing::TestWithParam {}; @@ -471,3 +474,5 @@ INSTANTIATE_TEST_SUITE_P(UpDownCounterToSum, UpDownCounterToSumFixture, ::testing::Values(true, false)); #endif + +} // namespace diff --git a/sdk/test/metrics/sync_metric_storage_histogram_test.cc b/sdk/test/metrics/sync_metric_storage_histogram_test.cc index 9f608bcd16..ee3af6e8c4 100644 --- a/sdk/test/metrics/sync_metric_storage_histogram_test.cc +++ b/sdk/test/metrics/sync_metric_storage_histogram_test.cc @@ -34,6 +34,9 @@ using namespace opentelemetry::sdk::metrics; using namespace opentelemetry::common; +namespace +{ + class WritableMetricStorageHistogramTestFixture : public ::testing::TestWithParam {}; @@ -538,3 +541,5 @@ INSTANTIATE_TEST_SUITE_P(WritableMetricStorageHistogramTestBase2ExponentialDoubl WritableMetricStorageHistogramTestFixture, ::testing::Values(AggregationTemporality::kCumulative, AggregationTemporality::kDelta)); + +} // namespace 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/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/tracer_config_test.cc b/sdk/test/trace/tracer_config_test.cc index 4f342ba11c..6f849bc94b 100644 --- a/sdk/test/trace/tracer_config_test.cc +++ b/sdk/test/trace/tracer_config_test.cc @@ -71,6 +71,9 @@ const std::array instrumentati &test_scope_1, &test_scope_2, &test_scope_3, &test_scope_4, &test_scope_5, }; +namespace +{ + // Test fixture for VerifyDefaultConfiguratorBehavior class DefaultTracerConfiguratorTestFixture : public ::testing::TestWithParam @@ -91,3 +94,5 @@ TEST_P(DefaultTracerConfiguratorTestFixture, VerifyDefaultConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultTracerConfiguratorTestFixture, ::testing::ValuesIn(instrumentation_scopes)); + +} // namespace From 3ae4629a2826429b8a8f4adee364b3a881b96f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:34:36 +0800 Subject: [PATCH 23/61] [CODE HEALTH] Fix clang-tidy bugprone-unchecked-string-to-number-conversion warnings (#4216) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ examples/grpc/client.cc | 4 ++-- examples/grpc/server.cc | 4 ++-- examples/http/client.cc | 4 ++-- examples/http/server.cc | 4 ++-- .../ext/http/server/http_server.h | 18 +++++++++++++++++- .../ext/http/server/socket_tools.h | 15 ++++++++++++++- .../w3c_tracecontext_http_test_server/main.cc | 4 ++-- sdk/src/configuration/configuration_parser.cc | 1 + sdk/test/metrics/measurements_benchmark.cc | 2 +- 11 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index e7b4495a44..cb0f9a3513 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 278 + warning_limit: 269 - cmake_options: all-options-abiv2-preview - warning_limit: 288 + warning_limit: 279 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1637f4e853..cc15f2cde4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,9 @@ Increment the: * [CODE HEALTH] Fix clang-tidy bugprone-unused-local-non-trivial-variable warnings [#4202](https://github.com/open-telemetry/opentelemetry-cpp/pull/4202) +* [CODE HEALTH] Fix clang-tidy bugprone-unchecked-string-to-number-conversion warnings + [#4216](https://github.com/open-telemetry/opentelemetry-cpp/pull/4216) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev 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/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/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/sdk/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index 7173ce75be..5e00cbc7de 100644 --- a/sdk/src/configuration/configuration_parser.cc +++ b/sdk/src/configuration/configuration_parser.cc @@ -2700,6 +2700,7 @@ std::unique_ptr ConfigurationParser::Parse(std::unique_ptrfile_format.c_str(), "%d.%d", &major, &minor); if (count != 2) { 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); From 6124b92c464d8a20d90989bb9709f5819e068ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=80=E5=90=89?= <84045975+thc1006@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:58:17 +0800 Subject: [PATCH 24/61] [CODE HEALTH] Fix clang-tidy misc-override-with-different-visibility warnings (#4215) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ .../opentelemetry/trace/propagation/http_trace_context.h | 1 + api/test/logs/logger_test.cc | 2 ++ api/test/logs/provider_test.cc | 1 + api/test/trace/provider_test.cc | 2 +- examples/configuration/custom_pull_metric_exporter.h | 2 +- ext/test/http/curl_http_test.cc | 5 +++++ sdk/include/opentelemetry/sdk/logs/logger.h | 4 +++- .../exemplar/aligned_histogram_bucket_exemplar_reservoir.h | 4 +++- sdk/include/opentelemetry/sdk/metrics/view/predicate.h | 2 ++ sdk/test/logs/logger_provider_sdk_test.cc | 1 + sdk/test/logs/logger_provider_set_test.cc | 1 + sdk/test/metrics/common.h | 2 +- sdk/test/trace/tracer_provider_set_test.cc | 2 +- 15 files changed, 28 insertions(+), 8 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index cb0f9a3513..96d4c50cfd 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 269 + warning_limit: 240 - cmake_options: all-options-abiv2-preview - warning_limit: 279 + warning_limit: 250 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc15f2cde4..68f959e268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,6 +153,9 @@ Increment the: * [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) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev diff --git a/api/include/opentelemetry/trace/propagation/http_trace_context.h b/api/include/opentelemetry/trace/propagation/http_trace_context.h index 76b94284ef..f8f349d33e 100644 --- a/api/include/opentelemetry/trace/propagation/http_trace_context.h +++ b/api/include/opentelemetry/trace/propagation/http_trace_context.h @@ -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/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/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/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/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/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h index 8be05720a1..6f72587bc9 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger.h +++ b/sdk/include/opentelemetry/sdk/logs/logger.h @@ -72,7 +72,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 +89,8 @@ 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: // The name of this logger std::string logger_name_; 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/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/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc index e33c9d8116..8fb47d2cfe 100644 --- a/sdk/test/logs/logger_provider_sdk_test.cc +++ b/sdk/test/logs/logger_provider_sdk_test.cc @@ -242,6 +242,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()); 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/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/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 */, From 5926ade2a6bdfd9a7bb22fe18b922c68a97a8053 Mon Sep 17 00:00:00 2001 From: Gerrit Renker Date: Wed, 8 Jul 2026 10:05:12 -0400 Subject: [PATCH 25/61] [BUILD] add missing link dependency for bazel (#4220) --- exporters/otlp/BUILD | 1 + exporters/prometheus/BUILD | 1 + 2 files changed, 2 insertions(+) diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 86b26c57be..b01fac5674 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", 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", ], From 59106118648e90e888610561da1f0a95cd31675d Mon Sep 17 00:00:00 2001 From: Ravi Date: Wed, 8 Jul 2026 13:50:31 -0400 Subject: [PATCH 26/61] [CODE HEALTH] Fix clang-tidy `bugprone-throwing-static-initialization` warnings (#4206) --- CHANGELOG.md | 3 + .../opentelemetry/baggage/baggage_context.h | 2 +- .../common/key_value_iterable_view.h | 3 +- .../trace/span_context_kv_iterable_view.h | 2 +- .../propagation/baggage_propagator_test.cc | 22 +-- examples/configuration/main.cc | 10 +- examples/otlp/file_log_main.cc | 13 +- examples/otlp/file_main.cc | 7 +- examples/otlp/file_metric_main.cc | 7 +- examples/otlp/grpc_log_main.cc | 15 +- examples/otlp/grpc_main.cc | 7 +- examples/otlp/grpc_metric_main.cc | 8 +- examples/otlp/http_instrumented_main.cc | 19 ++- examples/otlp/http_log_main.cc | 14 +- examples/otlp/http_main.cc | 7 +- examples/otlp/http_metric_main.cc | 7 +- examples/zipkin/main.cc | 6 +- exporters/prometheus/src/exporter_utils.cc | 13 +- exporters/zipkin/src/recordable.cc | 28 ++-- .../http/client/curl/http_operation_curl.h | 6 +- functional/otlp/func_grpc_main.cc | 7 +- functional/otlp/func_http_main.cc | 7 +- .../sdk/metrics/state/attributes_hashmap.h | 26 ++-- .../sdk/metrics/state/sync_metric_storage.h | 12 +- sdk/include/opentelemetry/sdk/trace/tracer.h | 1 - .../metrics/instrument_metadata_validator.cc | 8 +- sdk/src/metrics/state/sync_metric_storage.cc | 2 +- sdk/src/trace/tracer.cc | 5 +- sdk/test/common/circular_buffer_test.cc | 11 +- sdk/test/logs/logger_config_test.cc | 94 ++++++++---- sdk/test/logs/logger_sdk_test.cc | 143 ++++++++++++------ sdk/test/metrics/attributes_hashmap_test.cc | 4 +- sdk/test/metrics/cardinality_limit_test.cc | 8 +- sdk/test/metrics/meter_config_test.cc | 90 +++++++---- sdk/test/metrics/sync_instruments_test.cc | 4 - sdk/test/trace/tracer_config_test.cc | 90 +++++++---- 36 files changed, 428 insertions(+), 283 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f959e268..5c7e51e14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,6 +156,9 @@ Increment the: * [CODE HEALTH] Fix clang-tidy misc-override-with-different-visibility warnings [#4215](https://github.com/open-telemetry/opentelemetry-cpp/pull/4215) +* [CODE HEALTH] Fix clang-tidy `bugprone-throwing-static-initialization` warnings + [#4206](https://github.com/open-telemetry/opentelemetry-cpp/pull/4206) + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev 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/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/test/baggage/propagation/baggage_propagator_test.cc b/api/test/baggage/propagation/baggage_propagator_test.cc index fcb3a7f56d..3fc63bc4db 100644 --- a/api/test/baggage/propagation/baggage_propagator_test.cc +++ b/api/test/baggage/propagation/baggage_propagator_test.cc @@ -77,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) { @@ -91,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); } } @@ -106,18 +106,18 @@ 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()); 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/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 1e484485b1..f02373e401 100644 --- a/examples/otlp/file_metric_main.cc +++ b/examples/otlp/file_metric_main.cc @@ -34,9 +34,7 @@ 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); @@ -66,6 +64,7 @@ void CleanupMetrics() int main(int argc, char *argv[]) { + otlp_exporter::OtlpFileMetricExporterOptions exporter_options; std::string example_type; if (argc > 1) { @@ -78,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 7f45c44ec4..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; @@ -254,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; @@ -306,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]; @@ -335,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 0e1403debd..12e1076dc2 100644 --- a/examples/otlp/http_metric_main.cc +++ b/examples/otlp/http_metric_main.cc @@ -37,9 +37,7 @@ 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); @@ -80,6 +78,7 @@ void CleanupMetrics() */ int main(int argc, char *argv[]) { + otlp_exporter::OtlpHttpMetricExporterOptions exporter_options; std::string example_type; if (argc > 1) { @@ -112,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/prometheus/src/exporter_utils.cc b/exporters/prometheus/src/exporter_utils.cc index 694a30a079..bcca6bb73c 100644 --- a/exporters/prometheus/src/exporter_utils.cc +++ b/exporters/prometheus/src/exporter_utils.cc @@ -288,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) { @@ -371,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; @@ -425,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/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/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/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/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h b/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h index 19e10343ba..7812cdc38e 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,12 +211,12 @@ 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()); + bool has_overflow = (hash_map_.find(GetOverflowAttributes()) != hash_map_.end()); // If overflow already present, total size already includes it; trigger overflow // when current size (including overflow) is >= limit. if (has_overflow) 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..c64b310cc3 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h @@ -264,17 +264,17 @@ 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(); + const bool has_overflow = active_keys_.find(GetOverflowAttributes()) != 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 @@ -283,8 +283,8 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage has_overflow ? (active_keys_.size() >= limit) : (active_keys_.size() + 1 >= limit); if (would_overflow) { - active_keys_.insert(kOverflowAttributes); - return kOverflowAttributes; + active_keys_.insert(GetOverflowAttributes()); + return GetOverflowAttributes(); } active_keys_.insert(filtered); return filtered; diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 4c9969a89f..dcaf683395 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -137,7 +137,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/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/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/tracer.cc b/sdk/src/trace/tracer.cc index 298873db31..b0f0e593e8 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); } 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/logs/logger_config_test.cc b/sdk/test/logs/logger_config_test.cc index f6bdedf944..b2d2925f61 100644 --- a/sdk/test/logs/logger_config_test.cc +++ b/sdk/test/logs/logger_config_test.cc @@ -54,40 +54,70 @@ 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 { @@ -121,12 +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_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/attributes_hashmap_test.cc b/sdk/test/metrics/attributes_hashmap_test.cc index 04ccd4fe48..157f3f814b 100644 --- a/sdk/test/metrics/attributes_hashmap_test.cc +++ b/sdk/test/metrics/attributes_hashmap_test.cc @@ -203,7 +203,7 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) EXPECT_EQ(map.Size(), limit); // 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) @@ -219,7 +219,7 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) return true; }); EXPECT_EQ(map_copy.Size(), map.Size()); - EXPECT_NE(map_copy.Get(kOverflowAttributes), nullptr); + EXPECT_NE(map_copy.Get(GetOverflowAttributes()), nullptr); for (size_t i = 0; i < limit - 1; ++i) { MetricAttributes attr = {{"k", std::to_string(i)}}; diff --git a/sdk/test/metrics/cardinality_limit_test.cc b/sdk/test/metrics/cardinality_limit_test.cc index 4770ad7d74..981b4adbc8 100644 --- a/sdk/test/metrics/cardinality_limit_test.cc +++ b/sdk/test/metrics/cardinality_limit_test.cc @@ -76,7 +76,7 @@ TEST(CardinalityLimit, AttributesHashMapBasicTests) EXPECT_EQ(hash_map.Size(), 10); // 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_), @@ -177,12 +177,12 @@ 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); } diff --git a/sdk/test/metrics/meter_config_test.cc b/sdk/test/metrics/meter_config_test.cc index bdc737063d..cd41277a88 100644 --- a/sdk/test/metrics/meter_config_test.cc +++ b/sdk/test/metrics/meter_config_test.cc @@ -36,40 +36,70 @@ 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 { @@ -93,6 +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/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/trace/tracer_config_test.cc b/sdk/test/trace/tracer_config_test.cc index 6f849bc94b..f890d8ecb6 100644 --- a/sdk/test/trace/tracer_config_test.cc +++ b/sdk/test/trace/tracer_config_test.cc @@ -36,40 +36,70 @@ 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 { @@ -93,6 +123,6 @@ TEST_P(DefaultTracerConfiguratorTestFixture, VerifyDefaultConfiguratorBehavior) INSTANTIATE_TEST_SUITE_P(InstrumentationScopes, DefaultTracerConfiguratorTestFixture, - ::testing::ValuesIn(instrumentation_scopes)); + ::testing::ValuesIn(GetInstrumentationScopes())); } // namespace From 6acd4bbae0cb5efc801e144a14d29681fa1a0bff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:34:19 +0200 Subject: [PATCH 27/61] Bump rules_cc from 0.2.21 to 0.2.22 (#4223) Bumps [rules_cc](https://github.com/bazelbuild/rules_cc) from 0.2.21 to 0.2.22. - [Release notes](https://github.com/bazelbuild/rules_cc/releases) - [Commits](https://github.com/bazelbuild/rules_cc/compare/0.2.21...0.2.22) --- updated-dependencies: - dependency-name: rules_cc dependency-version: 0.2.22 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index c4d9574873..31994c3e00 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,7 +19,7 @@ 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.21") +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") From 21664a1d35304157b41fb9d5abdca0e09caa208f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 02:08:14 +0200 Subject: [PATCH 28/61] Bump step-security/harden-runner from 2.19.4 to 2.20.0 (#4222) Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.19.4 to 2.20.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/9af89fc71515a100421586dfdb3dc9c984fbf411...bf7454d06d71f1098171f2acdf0cd4708d7b5920) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.20.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 4 +- .github/workflows/ci.yml | 102 +++++++++--------- .github/workflows/clang-tidy.yaml | 2 +- .github/workflows/cmake_install.yml | 20 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/cppcheck.yml | 2 +- .github/workflows/dependencies_image.yml | 2 +- .github/workflows/fossa.yml | 2 +- .github/workflows/iwyu.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .../workflows/project_management_comment.yml | 2 +- .../project_management_issue_open.yml | 2 +- 12 files changed, 72 insertions(+), 72 deletions(-) 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 9098076489..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 @@ -60,7 +60,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 @@ -84,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 @@ -114,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 @@ -152,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 @@ -190,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 @@ -228,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 @@ -259,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 @@ -290,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 @@ -326,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 @@ -357,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 @@ -384,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 @@ -402,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 @@ -422,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 @@ -442,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 @@ -468,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 @@ -486,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 @@ -513,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 @@ -537,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 @@ -561,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 @@ -595,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 @@ -619,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 @@ -653,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 @@ -683,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 @@ -704,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 @@ -726,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 @@ -750,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 @@ -774,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 @@ -800,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 @@ -826,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 @@ -852,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 @@ -878,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 @@ -904,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 @@ -930,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 @@ -956,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 @@ -982,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 @@ -1009,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 @@ -1042,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 @@ -1057,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 @@ -1070,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 @@ -1090,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 @@ -1112,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 @@ -1132,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 @@ -1152,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 @@ -1170,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 @@ -1199,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 @@ -1216,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 @@ -1231,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 @@ -1249,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 @@ -1265,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 @@ -1309,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 96d4c50cfd..738452084c 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -25,7 +25,7 @@ jobs: 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 41622c46df..517a7a20cc 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 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 d7388a24b8..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 diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index f41b0e1a20..870f1e0f1e 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.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 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 7bcdce7067..add463e933 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 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 From 7b918eacce099bc2f894f433071731d82b0289ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:16:42 +0200 Subject: [PATCH 29/61] Bump bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml (#4221) Bumps [bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml](https://github.com/bazel-contrib/publish-to-bcr) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/bazel-contrib/publish-to-bcr/releases) - [Commits](https://github.com/bazel-contrib/publish-to-bcr/compare/c316f1611511a40423572303f66c80bb30bfe2f8...ad6879fd015a01c3e3b1b318fe3c061f928ac2fb) --- updated-dependencies: - dependency-name: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml dependency-version: 1.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-bcr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' }} From a31311118b0070d6daeefbc156673fa10b959fe7 Mon Sep 17 00:00:00 2001 From: Mateen Anjum Date: Thu, 9 Jul 2026 06:05:45 -0400 Subject: [PATCH 30/61] [CODE HEALTH] Move ext and exporters test classes into anonymous namespace (#4225) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ exporters/memory/test/in_memory_metric_exporter_test.cc | 5 +++++ exporters/ostream/test/ostream_span_test.cc | 5 +++++ ext/test/http/url_parser_test.cc | 5 +++++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 738452084c..03dd195aac 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 240 + warning_limit: 235 - cmake_options: all-options-abiv2-preview - warning_limit: 250 + warning_limit: 245 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c7e51e14d..c5479deae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CODE HEALTH] Move ext and exporters test classes into anonymous namespace + [#4225](https://github.com/open-telemetry/opentelemetry-cpp/pull/4225) + * [CODE HEALTH] Move api/test and sdk/test classes into anonymous namespace [#4217](https://github.com/open-telemetry/opentelemetry-cpp/pull/4217) 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/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/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 From e9ab790543a38423743e8801edac77c9d111d529 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:23:53 +0200 Subject: [PATCH 31/61] Bump github/codeql-action/upload-sarif from 4.36.3 to 4.37.0 (#4227) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/54f647b7e1bb85c95cddabcd46b0c578ec92bc1a...99df26d4f13ea111d4ec1a7dddef6063f76b97e9) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index add463e933..dd42cd542e 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 with: sarif_file: results.sarif From cd51655a7727cd722871a1357ccd87a84ef508a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:19:20 +0200 Subject: [PATCH 32/61] Bump github/codeql-action/init from 4.36.3 to 4.37.0 (#4229) * Bump github/codeql-action/init from 4.36.3 to 4.37.0 Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/54f647b7e1bb85c95cddabcd46b0c578ec92bc1a...99df26d4f13ea111d4ec1a7dddef6063f76b97e9) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update .github/workflows/codeql-analysis.yml --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Alff --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 517a7a20cc..c7bae0d749 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 with: languages: cpp config: | @@ -71,4 +71,4 @@ jobs: "${GITHUB_WORKSPACE}" cmake --build . --parallel - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 + uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 From 7b1ec0fbd9701ccb6b4b91a06163b6bf8c8c041c Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Thu, 9 Jul 2026 23:38:42 +0200 Subject: [PATCH 33/61] [SEMANTIC CONVENTIONS] Upgrade to semconv 1.43.0 (#4230) --- .../opentelemetry/semconv/db_attributes.h | 27 ++++++- .../semconv/incubating/azure_attributes.h | 7 ++ .../semconv/incubating/cicd_metrics.h | 2 +- .../semconv/incubating/cloud_attributes.h | 2 + .../semconv/incubating/db_attributes.h | 27 ++++++- .../semconv/incubating/k8s_attributes.h | 70 +++++++++---------- .../semconv/incubating/network_attributes.h | 4 +- .../semconv/incubating/otel_metrics.h | 4 +- .../semconv/incubating/telemetry_attributes.h | 2 + .../opentelemetry/semconv/k8s_attributes.h | 70 +++++++++---------- .../opentelemetry/semconv/schema_url.h | 2 +- .../semconv/telemetry_attributes.h | 2 + buildscripts/semantic-convention/generate.sh | 4 +- 13 files changed, 141 insertions(+), 82 deletions(-) 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/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 From 7d046559fa51bdc4403cdda0a26122a13c90b16d Mon Sep 17 00:00:00 2001 From: Mateen Anjum Date: Thu, 9 Jul 2026 20:13:33 -0400 Subject: [PATCH 34/61] [CODE HEALTH] Move otlp, zipkin and prometheus test classes into anonymous namespace (#4231) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ exporters/otlp/test/otlp_file_client_test.cc | 3 +++ exporters/otlp/test/otlp_file_exporter_test.cc | 6 ++++++ exporters/otlp/test/otlp_file_log_record_exporter_test.cc | 6 ++++++ exporters/otlp/test/otlp_file_metric_exporter_test.cc | 3 +++ exporters/otlp/test/otlp_grpc_exporter_test.cc | 6 ++++++ exporters/otlp/test/otlp_http_exporter_test.cc | 3 +++ exporters/otlp/test/otlp_log_recordable_test.cc | 3 +++ exporters/otlp/test/otlp_recordable_test.cc | 6 ++++++ exporters/prometheus/test/collector_test.cc | 6 ++++++ exporters/prometheus/test/exporter_utils_test.cc | 6 ++++++ exporters/zipkin/test/zipkin_exporter_test.cc | 6 ++++++ exporters/zipkin/test/zipkin_recordable_test.cc | 3 +++ 14 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 03dd195aac..84300776f8 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 235 + warning_limit: 137 - cmake_options: all-options-abiv2-preview - warning_limit: 245 + warning_limit: 145 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index c5479deae7..870444cf52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CODE HEALTH] Move otlp, zipkin and prometheus test classes into anonymous namespace + [#4231](https://github.com/open-telemetry/opentelemetry-cpp/pull/4231) + * [CODE HEALTH] Move ext and exporters test classes into anonymous namespace [#4225](https://github.com/open-telemetry/opentelemetry-cpp/pull/4225) diff --git a/exporters/otlp/test/otlp_file_client_test.cc b/exporters/otlp/test/otlp_file_client_test.cc index 6260559720..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) { 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..a77a5fe590 100644 --- a/exporters/otlp/test/otlp_http_exporter_test.cc +++ b/exporters/otlp/test/otlp_http_exporter_test.cc @@ -777,9 +777,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_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc index d471c86414..3801afe043 100644 --- a/exporters/otlp/test/otlp_log_recordable_test.cc +++ b/exporters/otlp/test/otlp_log_recordable_test.cc @@ -254,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); diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 56c4015fe2..1645b61b5f 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -548,11 +548,14 @@ TEST(OtlpRecordable, PopulateRequestMissing) } } +namespace +{ template struct EmptyArrayAttributeTest : public testing::Test { using ElementType = T; }; +} // namespace using ArrayElementTypes = testing::Types; @@ -576,11 +579,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); 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/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); From 1d7cd745f96e5ef5b6a4300d86a7b02aeca80cf5 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Fri, 10 Jul 2026 05:57:39 +0200 Subject: [PATCH 35/61] [BAZEL] Upgrade to rapidyaml 1.15.2 (#4234) --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 31994c3e00..f02a516052 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -18,7 +18,7 @@ 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 = "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") From f1e827a88b1bef283405c71e605c00fc1c419862 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:34:34 -0400 Subject: [PATCH 36/61] [BENCHMARK] add SpanData recordable benchmark and unify common utils (#4203) --- exporters/otlp/BUILD | 1 + exporters/otlp/CMakeLists.txt | 8 +- .../otlp/test/otlp_recordable_benchmark.cc | 323 +++++++++--------- sdk/src/trace/span_data.cc | 6 +- sdk/test/trace/BUILD | 16 + sdk/test/trace/CMakeLists.txt | 10 + sdk/test/trace/span_data_benchmark.cc | 213 ++++++++++++ .../test_common/sdk/trace/test_utils.h | 228 +++++++++++++ 8 files changed, 630 insertions(+), 175 deletions(-) create mode 100644 sdk/test/trace/span_data_benchmark.cc create mode 100644 test_common/include/opentelemetry/test_common/sdk/trace/test_utils.h diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index b01fac5674..f756afa698 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -1041,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 89e229ca4a..eef251a1de 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -1225,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/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/sdk/src/trace/span_data.cc b/sdk/src/trace/span_data.cc index 9969b44c30..8b1503ee5d 100644 --- a/sdk/src/trace/span_data.cc +++ b/sdk/src/trace/span_data.cc @@ -100,15 +100,13 @@ 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); + events_.emplace_back(std::string(name), timestamp, attributes); } void SpanData::AddLink(const opentelemetry::trace::SpanContext &span_context, const opentelemetry::common::KeyValueIterable &attributes) noexcept { - SpanDataLink link(span_context, attributes); - links_.push_back(link); + links_.emplace_back(span_context, attributes); } void SpanData::SetStatus(opentelemetry::trace::StatusCode code, diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 6f2230e6fb..1a754e68e8 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -221,3 +221,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 566422a928..671a6666d8 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -37,4 +37,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/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/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 From da1f89b888dff853988905df950ae3fbd6c1a00a Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Sat, 11 Jul 2026 03:36:26 -0400 Subject: [PATCH 37/61] [CI] Add `modernize-` and `cppcoreguidelines-pro-` checks to clang-tidy (#4238) --- .clang-tidy | 29 ++++++++++++++++++++++++++++- .github/workflows/clang-tidy.yaml | 4 ++-- 2 files changed, 30 insertions(+), 3 deletions(-) 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/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 84300776f8..08b66a0699 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 137 + warning_limit: 216 - cmake_options: all-options-abiv2-preview - warning_limit: 145 + warning_limit: 226 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 From 3daedc18aaedeaf6afd961b2930bca70f88fb70a Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Sun, 12 Jul 2026 02:39:08 -0700 Subject: [PATCH 38/61] [METRICS SDK] Apply cardinality limits only to non-overflow attributesets (#4236) --- CHANGELOG.md | 4 ++ .../sdk/metrics/state/attributes_hashmap.h | 17 ++--- .../sdk/metrics/state/sync_metric_storage.h | 10 ++- sdk/test/metrics/attributes_hashmap_test.cc | 11 ++-- .../metrics/bound_sync_instruments_test.cc | 64 +++++++++---------- sdk/test/metrics/cardinality_limit_test.cc | 19 +++--- sdk/test/metrics/sum_aggregation_test.cc | 9 +-- 7 files changed, 62 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 870444cf52..7616b2a719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Increment the: ## [Unreleased] +* [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) + * [CODE HEALTH] Move otlp, zipkin and prometheus test classes into anonymous namespace [#4231](https://github.com/open-telemetry/opentelemetry-cpp/pull/4231) diff --git a/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h b/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h index 7812cdc38e..38f62005e1 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/attributes_hashmap.h @@ -215,18 +215,11 @@ class AttributesHashMapWithCustomHash { return true; } - // Determine if overflow entry already exists. - bool has_overflow = (hash_map_.find(GetOverflowAttributes()) != 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 c64b310cc3..e9aaebe6f5 100644 --- a/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h +++ b/sdk/include/opentelemetry/sdk/metrics/state/sync_metric_storage.h @@ -275,12 +275,10 @@ class SyncMetricStorage : public MetricStorage, public SyncWritableMetricStorage } const size_t limit = aggregation_config_->cardinality_limit_; const bool has_overflow = active_keys_.find(GetOverflowAttributes()) != 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); + // 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(GetOverflowAttributes()); diff --git a/sdk/test/metrics/attributes_hashmap_test.cc b/sdk/test/metrics/attributes_hashmap_test.cc index 157f3f814b..9aedf0aef2 100644 --- a/sdk/test/metrics/attributes_hashmap_test.cc +++ b/sdk/test/metrics/attributes_hashmap_test.cc @@ -183,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"}}; @@ -190,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)}}; @@ -200,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(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); @@ -220,7 +221,7 @@ TEST(AttributesHashMap, OverflowCardinalityLimitBehavior) }); EXPECT_EQ(map_copy.Size(), map.Size()); EXPECT_NE(map_copy.Get(GetOverflowAttributes()), nullptr); - 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_copy.Get(attr), nullptr); diff --git a/sdk/test/metrics/bound_sync_instruments_test.cc b/sdk/test/metrics/bound_sync_instruments_test.cc index 9c2f233691..756030437e 100644 --- a/sdk/test/metrics/bound_sync_instruments_test.cc +++ b/sdk/test/metrics/bound_sync_instruments_test.cc @@ -638,8 +638,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 +657,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 +677,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 +760,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 +768,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 +860,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 +883,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 +914,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 +950,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 +1038,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 +1072,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 +1114,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 981b4adbc8..cb7dc9b8a0 100644 --- a/sdk/test/metrics/cardinality_limit_test.cc +++ b/sdk/test/metrics/cardinality_limit_test.cc @@ -47,7 +47,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 +56,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 +74,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(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); @@ -125,7 +126,7 @@ TEST_P(WritableMetricStorageCardinalityLimitTestFixture, LongCounterSumAggregati &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)}}; @@ -154,13 +155,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, diff --git a/sdk/test/metrics/sum_aggregation_test.cc b/sdk/test/metrics/sum_aggregation_test.cc index 1a4e9c2545..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()); From 5cd8dea9a4259dc67d98cbbcdfc65f2d4ea29202 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:18:22 -0400 Subject: [PATCH 39/61] [CONFIGURATION] fix yaml configuration of the logger configurator (#4241) --- sdk/src/configuration/sdk_builder.cc | 71 ++++++++++++++++++++- sdk/test/configuration/BUILD | 20 ++++++ sdk/test/configuration/CMakeLists.txt | 1 + sdk/test/configuration/sdk_builder_test.cc | 74 ++++++++++++++++++++++ 4 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 sdk/test/configuration/sdk_builder_test.cc diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index b6641a0dbd..5b6782313a 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -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" @@ -194,6 +195,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 { @@ -1884,15 +1947,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) { 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..cccb6d37ee --- /dev/null +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -0,0 +1,74 @@ +// 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/instrumentationscope/instrumentation_scope.h" +#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" +#include "opentelemetry/sdk/logs/logger_config.h" + +namespace config_sdk = opentelemetry::sdk::configuration; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace logs_sdk = opentelemetry::sdk::logs; + +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()); +} From 1e39c1b2cf742d072d15ea0b5c8a15aeeb16933c Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:24:05 -0400 Subject: [PATCH 40/61] [SDK] Support LoggerConfigurator updates and example (#4224) --- examples/CMakeLists.txt | 1 + examples/logger_configurator/CMakeLists.txt | 13 + examples/logger_configurator/README.md | 420 ++++++++++++++++++ examples/logger_configurator/main.cc | 294 ++++++++++++ sdk/include/opentelemetry/sdk/logs/logger.h | 17 +- .../opentelemetry/sdk/logs/logger_context.h | 7 + .../opentelemetry/sdk/logs/logger_provider.h | 16 + sdk/src/logs/logger.cc | 43 +- sdk/src/logs/logger_context.cc | 15 + sdk/src/logs/logger_provider.cc | 30 ++ sdk/test/logs/logger_provider_sdk_test.cc | 355 ++++++++++++++- 11 files changed, 1190 insertions(+), 21 deletions(-) create mode 100644 examples/logger_configurator/CMakeLists.txt create mode 100644 examples/logger_configurator/README.md create mode 100644 examples/logger_configurator/main.cc 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/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/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h index 6f72587bc9..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 @@ -91,6 +92,17 @@ class Logger final : public opentelemetry::logs::Logger #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_; @@ -98,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 65d740f722..bf19fa879c 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger_context.h +++ b/sdk/include/opentelemetry/sdk/logs/logger_context.h @@ -93,6 +93,13 @@ class LoggerContext */ 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. 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/src/logs/logger.cc b/sdk/src/logs/logger.cc index 80cd8eddfd..cde8a3503c 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include @@ -92,9 +93,9 @@ trace_api::SpanContext ExtractSpanContext( 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 +132,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)) { - SetMinimumSeverity(logger_config_.IsEnabled() - ? static_cast(logger_config_.GetMinimumSeverity()) - : opentelemetry::logs::kMaxSeverity); + LoggerConfig config = context_->GetLoggerConfigurator().ComputeConfig(*instrumentation_scope_); + UpdateLoggerConfig(config); +} + +void Logger::UpdateLoggerConfig(LoggerConfig config) noexcept +{ + 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,7 +162,7 @@ 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(); } @@ -181,7 +188,7 @@ 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(); } @@ -203,7 +210,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)); } @@ -229,7 +236,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; } @@ -243,7 +250,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; } @@ -256,7 +263,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; } @@ -269,7 +277,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 a1e8defbfa..7f6d5315ec 100644 --- a/sdk/src/logs/logger_context.cc +++ b/sdk/src/logs/logger_context.cc @@ -6,6 +6,8 @@ #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" @@ -67,6 +69,19 @@ 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/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc index 8fb47d2cfe..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: @@ -255,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) { @@ -292,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(); +} From 33206642abf1ed45ff9bc7c35e12ba2910af0496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 09:53:31 +0200 Subject: [PATCH 41/61] Bump actions/stale from 10.3.0 to 10.4.0 (#4242) Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to 10.4.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899...1e223db275d687790206a7acac4d1a11bd6fe629) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b2cb13c6a44d9f61b5fe0f52df48ef2865116aca Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:57:38 -0400 Subject: [PATCH 42/61] [CMAKE] Remove environment variable support from the add external components script (#4211) * remove feature to add external components to the opentelemetry-cpp CMake build using environment variables in favor or users building external components with opentelemetry-cpp as a dependency imported through find_package or FetchContent * Adds a message if the external component is added. Clean up comments to remove env var support * Update cmake/opentelemetry-build-external-component.cmake Co-authored-by: Tom Tan --------- Co-authored-by: Marc Alff Co-authored-by: Tom Tan --- ...entelemetry-build-external-component.cmake | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) 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() From e7e4b046c97f4d4a648ad8b933a03bec50b3b72c Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Wed, 15 Jul 2026 10:48:16 -0700 Subject: [PATCH 43/61] [ETW] Make spans own their names (#4247) --- CHANGELOG.md | 3 +++ .../opentelemetry/exporters/etw/etw_tracer.h | 6 +++--- exporters/etw/test/etw_tracer_test.cc | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7616b2a719..189260e7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [ETW] Ensure spans own their names until they are ended + [#4247](https://github.com/open-telemetry/opentelemetry-cpp/pull/4247) + * [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) 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 From 24c04aacb8557241812c665a61effe5732acfd30 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:40:27 -0400 Subject: [PATCH 44/61] [SDK] Span limits configuration (#4232) --- CHANGELOG.md | 5 + .../otlp/otlp_grpc_exporter_options.h | 6 +- .../otlp/otlp_http_exporter_options.h | 6 +- .../exporters/otlp/otlp_recordable.h | 37 ++- exporters/otlp/src/otlp_recordable.cc | 40 ++- exporters/otlp/test/otlp_recordable_test.cc | 272 ++++++++++++++++++ .../sdk/common/attribute_utils.h | 84 +++++- .../configuration/span_limits_configuration.h | 10 +- .../sdk/trace/multi_recordable.h | 2 + .../opentelemetry/sdk/trace/recordable.h | 7 + .../opentelemetry/sdk/trace/span_data.h | 45 ++- .../opentelemetry/sdk/trace/span_limits.h | 68 +++++ sdk/include/opentelemetry/sdk/trace/tracer.h | 4 + .../opentelemetry/sdk/trace/tracer_context.h | 11 +- .../opentelemetry/sdk/trace/tracer_provider.h | 14 +- .../sdk/trace/tracer_provider_factory.h | 17 ++ sdk/src/configuration/configuration_parser.cc | 24 +- sdk/src/configuration/sdk_builder.cc | 34 ++- sdk/src/trace/multi_recordable.cc | 8 + sdk/src/trace/span.cc | 1 + sdk/src/trace/span_data.cc | 113 +++++++- sdk/src/trace/tracer_context.cc | 23 +- sdk/src/trace/tracer_provider.cc | 23 +- sdk/src/trace/tracer_provider_factory.cc | 29 +- sdk/test/common/attribute_utils_test.cc | 20 ++ sdk/test/configuration/sdk_builder_test.cc | 55 ++++ sdk/test/configuration/yaml_trace_test.cc | 73 +++++ sdk/test/trace/span_data_test.cc | 143 +++++++++ sdk/test/trace/tracer_provider_test.cc | 75 ++++- sdk/test/trace/tracer_test.cc | 54 +++- 30 files changed, 1216 insertions(+), 87 deletions(-) create mode 100644 sdk/include/opentelemetry/sdk/trace/span_limits.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 189260e7c7..79e8e1ffec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,11 @@ Increment the: * [CMAKE] Fix and test WITH_API_ONLY option [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) +* [SDK] Span limits can now be configured through the TracerProvider interface + and declaritive configuration. When set, limits are enforced by the + SpanData and OtlpRecordables recordables used by the standard exporters + [#4232](https://github.com/open-telemetry/opentelemetry-cpp/pull/4232) + * [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. 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_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_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_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_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 1645b61b5f..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" @@ -663,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: @@ -704,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 @@ -785,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/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index c2e75a0f97..8b83b363b0 100644 --- a/sdk/include/opentelemetry/sdk/common/attribute_utils.h +++ b/sdk/include/opentelemetry/sdk/common/attribute_utils.h @@ -21,6 +21,10 @@ #include "opentelemetry/nostd/variant.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_HAVE_EXCEPTIONS +# include +#endif + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -276,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. */ @@ -328,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 @@ -403,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/span_limits_configuration.h b/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h index a4624bb1a5..25e417113d 100644 --- a/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/span_limits_configuration.h @@ -17,11 +17,11 @@ 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; + std::uint32_t attribute_count_limit; + std::uint32_t event_count_limit; + std::uint32_t link_count_limit; + std::uint32_t event_attribute_count_limit; + std::uint32_t link_attribute_count_limit; }; } // namespace configuration 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/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 dcaf683395..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(); } 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/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index 5e00cbc7de..c83f329d0f 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 @@ -1596,12 +1596,22 @@ std::unique_ptr ConfigurationParser::ParseSpanLimitsCon { 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_count_limit = get_valid_uint32("attribute_count_limit", 128); + model->event_count_limit = get_valid_uint32("event_count_limit", 128); + model->link_count_limit = get_valid_uint32("link_count_limit", 128); + model->event_attribute_count_limit = get_valid_uint32("event_attribute_count_limit", 128); + model->link_attribute_count_limit = get_valid_uint32("link_attribute_count_limit", 128); return model; } diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index 5b6782313a..fbea5dce01 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 @@ -116,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" @@ -174,6 +175,7 @@ #include "opentelemetry/sdk/trace/samplers/probability_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" @@ -1175,20 +1177,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; 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/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 8b1503ee5d..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,20 +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 { - events_.emplace_back(std::string(name), timestamp, attributes); + 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 { - links_.emplace_back(span_context, attributes); + 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_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/test/common/attribute_utils_test.cc b/sdk/test/common/attribute_utils_test.cc index c4b6abe378..848ce86bc4 100644 --- a/sdk/test/common/attribute_utils_test.cc +++ b/sdk/test/common/attribute_utils_test.cc @@ -195,6 +195,26 @@ TEST(AttributeMapTest, EqualTo) 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 // --------------------------------------------------------------------------- diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc index cccb6d37ee..22d07a8fc0 100644 --- a/sdk/test/configuration/sdk_builder_test.cc +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -13,14 +13,69 @@ #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; 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/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_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()); +} From 3d47255c1722fcf4be3803c66c4fa37a8cdc57f1 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:27:13 -0400 Subject: [PATCH 45/61] [CONFIGURATION] Define and set spec default values in configuration objects (#4244) --- .../attribute_limits_configuration.h | 8 +- ...cket_histogram_aggregation_configuration.h | 15 +- ...batch_log_record_processor_configuration.h | 15 +- .../batch_span_processor_configuration.h | 14 +- .../boolean_attribute_value_configuration.h | 2 +- .../cardinality_limits_configuration.h | 22 ++- ...osable_probability_sampler_configuration.h | 4 +- .../sdk/configuration/configuration.h | 3 +- .../sdk/configuration/configuration_parser.h | 4 +- ...nsole_push_metric_exporter_configuration.h | 10 +- .../sdk/configuration/document_node.h | 6 +- .../double_attribute_value_configuration.h | 2 +- ...cket_histogram_aggregation_configuration.h | 5 +- .../configuration/grpc_tls_configuration.h | 4 +- .../integer_attribute_value_configuration.h | 2 +- .../jaeger_remote_sampler_configuration.h | 5 +- .../log_record_limits_configuration.h | 10 +- .../logger_config_configuration.h | 10 +- .../meter_provider_configuration.h | 2 +- ..._file_push_metric_exporter_configuration.h | 10 +- ...p_grpc_log_record_exporter_configuration.h | 5 +- ..._grpc_push_metric_exporter_configuration.h | 14 +- .../otlp_grpc_span_exporter_configuration.h | 5 +- ...p_http_log_record_exporter_configuration.h | 5 +- ..._http_push_metric_exporter_configuration.h | 14 +- .../otlp_http_span_exporter_configuration.h | 5 +- .../periodic_metric_reader_configuration.h | 9 +- ...theus_pull_metric_exporter_configuration.h | 18 +- .../sdk/configuration/ryml_document_node.h | 2 +- .../configuration/span_limits_configuration.h | 22 ++- ...ace_id_ratio_based_sampler_configuration.h | 5 +- .../configuration/view_stream_configuration.h | 6 +- sdk/src/configuration/configuration_parser.cc | 179 +++++++++++------- 33 files changed, 296 insertions(+), 146 deletions(-) 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 25e417113d..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::uint32_t attribute_count_limit; - std::uint32_t event_count_limit; - std::uint32_t link_count_limit; - std::uint32_t event_attribute_count_limit; - std::uint32_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/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index c83f329d0f..cc83201a97 100644 --- a/sdk/src/configuration/configuration_parser.cc +++ b/sdk/src/configuration/configuration_parser.cc @@ -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,7 +1618,8 @@ 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); @@ -1606,12 +1631,16 @@ std::unique_ptr ConfigurationParser::ParseSpanLimitsCon return static_cast(value); }; - model->attribute_value_length_limit = node->GetInteger("attribute_value_length_limit", 4096); - model->attribute_count_limit = get_valid_uint32("attribute_count_limit", 128); - model->event_count_limit = get_valid_uint32("event_count_limit", 128); - model->link_count_limit = get_valid_uint32("link_count_limit", 128); - model->event_attribute_count_limit = get_valid_uint32("event_attribute_count_limit", 128); - model->link_attribute_count_limit = get_valid_uint32("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; } @@ -1642,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; @@ -1650,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) @@ -1713,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; } @@ -1742,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; } @@ -2019,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"); @@ -2038,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); @@ -2050,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"); @@ -2069,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; } @@ -2159,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); From 0fe0267dca441757caedacc6eabe11ca4ce40eab Mon Sep 17 00:00:00 2001 From: ayush-that Date: Fri, 17 Jul 2026 00:34:31 +0530 Subject: [PATCH 46/61] remove configuration changes, keep SDK sampler only --- sdk/src/configuration/sdk_builder.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index fbea5dce01..897413643d 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -172,7 +172,6 @@ #include "opentelemetry/sdk/trace/samplers/always_off_factory.h" #include "opentelemetry/sdk/trace/samplers/always_on_factory.h" #include "opentelemetry/sdk/trace/samplers/parent_factory.h" -#include "opentelemetry/sdk/trace/samplers/probability_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" @@ -456,14 +455,7 @@ class SamplerBuilder : public opentelemetry::sdk::configuration::SamplerConfigur const opentelemetry::sdk::configuration::ComposableProbabilitySamplerConfiguration *model) override { - if (model->ratio > 0.0) - { - sampler = opentelemetry::sdk::trace::ProbabilitySamplerFactory::Create(model->ratio); - } - else - { - sampler = opentelemetry::sdk::trace::AlwaysOffSamplerFactory::Create(); - } + sampler = opentelemetry::sdk::trace::TraceIdRatioBasedSamplerFactory::Create(model->ratio); } void VisitComposableParentThreshold( From 8d0f88aef9c1d71ab04942898df16ac885fb5456 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:47:28 -0400 Subject: [PATCH 47/61] [CMAKE] Upgrade to protobuf 35.1 and grpc 1.82.1 (#4255) --- cmake/grpc.cmake | 2 +- .../otlp/test/otlp_http_exporter_test.cc | 14 +++++++-- .../otlp_http_log_record_exporter_test.cc | 14 +++++++-- .../test/otlp_http_metric_exporter_test.cc | 30 +++++++++++++++---- install/cmake/CMakeLists.txt | 2 +- install/cmake/third_party_latest | 4 +-- third_party_release | 4 +-- 7 files changed, 54 insertions(+), 16 deletions(-) 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/exporters/otlp/test/otlp_http_exporter_test.cc b/exporters/otlp/test/otlp_http_exporter_test.cc index a77a5fe590..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) 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/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/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 From 6ad61079a3b1bcfb8e71a938a351e7e0fdbd0989 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Thu, 16 Jul 2026 22:46:07 +0200 Subject: [PATCH 48/61] [RELEASE] Release opentelemetry-cpp 1.28.0 (#4233) --- CHANGELOG.md | 386 ++++++++++++++---- CMakeLists.txt | 2 +- MODULE.bazel | 2 +- api/include/opentelemetry/version.h | 2 +- .../opentelemetry/sdk/version/version.h | 2 +- sdk/src/version/version.cc | 6 +- tbump.toml | 2 +- 7 files changed, 314 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e8e1ffec..c7f827f592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,58 +15,81 @@ Increment the: ## [Unreleased] -* [ETW] Ensure spans own their names until they are ended - [#4247](https://github.com/open-telemetry/opentelemetry-cpp/pull/4247) - -* [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) - -* [CODE HEALTH] Move otlp, zipkin and prometheus test classes into anonymous namespace - [#4231](https://github.com/open-telemetry/opentelemetry-cpp/pull/4231) - -* [CODE HEALTH] Move ext and exporters test classes into anonymous namespace - [#4225](https://github.com/open-telemetry/opentelemetry-cpp/pull/4225) - -* [CODE HEALTH] Move api/test and sdk/test classes into anonymous namespace - [#4217](https://github.com/open-telemetry/opentelemetry-cpp/pull/4217) - -* [CMAKE] Fix and test WITH_API_ONLY option - [#4201](https://github.com/open-telemetry/opentelemetry-cpp/pull/4201) - -* [SDK] Span limits can now be configured through the TracerProvider interface - and declaritive configuration. When set, limits are enforced by the - SpanData and OtlpRecordables recordables used by the standard exporters - [#4232](https://github.com/open-telemetry/opentelemetry-cpp/pull/4232) +* [SDK] Implement the ProbabilitySampler + [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) -* [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`). - [#4194](https://github.com/open-telemetry/opentelemetry-cpp/pull/4194) +* [SDK] Rename ComposableTraceIdRatioBasedSampler to ComposableProbabilitySampler + to align with the OpenTelemetry specification + [#4161](https://github.com/open-telemetry/opentelemetry-cpp/issues/4161) -* [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example - [#4065](https://github.com/open-telemetry/opentelemetry-cpp/pull/4065) +## [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 @@ -75,12 +98,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) @@ -90,93 +125,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) -* [SDK] LogRecord attribute limits enforcement - [#4157](https://github.com/open-telemetry/opentelemetry-cpp/pull/4157) +* [CONTEXT] Fix env carrier spec compliance + [#4141](https://github.com/open-telemetry/opentelemetry-cpp/pull/4141) -* [CONFIGURATION] File configuration: declarative resource detection types - [#4148](https://github.com/open-telemetry/opentelemetry-cpp/pull/4148) - -* [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, ComposableProbability, - 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) -* [SDK] Implement the ProbabilitySampler - [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) +* [EXPORTER] Add custom HTTP client + [#4071](https://github.com/open-telemetry/opentelemetry-cpp/pull/4071) -* [SDK] Rename ComposableTraceIdRatioBasedSampler to ComposableProbabilitySampler - to align with the OpenTelemetry specification - [#4161](https://github.com/open-telemetry/opentelemetry-cpp/issues/4161) +* 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 e44aa1544f..3380189ff4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20.0") endif() set(OPENTELEMETRY_VERSION_NUMBER "1.28.0") -set(OPENTELEMETRY_VERSION_SUFFIX "-dev") +set(OPENTELEMETRY_VERSION_SUFFIX "") set(OPENTELEMETRY_VERSION "${OPENTELEMETRY_VERSION_NUMBER}${OPENTELEMETRY_VERSION_SUFFIX}") diff --git a/MODULE.bazel b/MODULE.bazel index f02a516052..c8d409be41 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,7 +3,7 @@ module( name = "opentelemetry-cpp", - version = "1.28.0-dev", + version = "1.28.0", compatibility_level = 0, repo_name = "io_opentelemetry_cpp", ) diff --git a/api/include/opentelemetry/version.h b/api/include/opentelemetry/version.h index 574d1df49f..48908afe88 100644 --- a/api/include/opentelemetry/version.h +++ b/api/include/opentelemetry/version.h @@ -12,7 +12,7 @@ #endif // NOLINTBEGIN(cppcoreguidelines-macro-to-enum) -#define OPENTELEMETRY_VERSION "1.28.0-dev" +#define OPENTELEMETRY_VERSION "1.28.0" #define OPENTELEMETRY_VERSION_MAJOR 1 #define OPENTELEMETRY_VERSION_MINOR 28 #define OPENTELEMETRY_VERSION_PATCH 0 diff --git a/sdk/include/opentelemetry/sdk/version/version.h b/sdk/include/opentelemetry/sdk/version/version.h index 487797e603..5924fa4b74 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.28.0" #include "opentelemetry/version.h" diff --git a/sdk/src/version/version.cc b/sdk/src/version/version.cc index f7c6730f4f..7504c6c2f2 100644 --- a/sdk/src/version/version.cc +++ b/sdk/src/version/version.cc @@ -14,16 +14,16 @@ namespace version const int major_version = 1; const int minor_version = 28; const int patch_version = 0; -const char *pre_release = "dev"; +const char *pre_release = ""; const char *build_metadata = "none"; const char *short_version = "1.28.0"; -const char *full_version = "1.28.0-dev"; +const char *full_version = "1.28.0"; /** * Release date. * For published releases: YYYY-MM-DD * For -dev releases: empty string */ -const char *build_date = ""; +const char *build_date = "2026-07-16"; } // namespace version } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/tbump.toml b/tbump.toml index dc0218ae06..e1a3bdd310 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.28.0" # Example of a semver regexp. # Make sure this matches current_version before From 6681466e6cb91aa14372a4ef60e6de1b0c4b2f23 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Thu, 16 Jul 2026 23:42:58 +0200 Subject: [PATCH 49/61] [RELEASE] Bump main branch to 1.29.0-dev (#4259) --- CHANGELOG.md | 3 +++ CMakeLists.txt | 4 ++-- MODULE.bazel | 2 +- api/include/opentelemetry/version.h | 4 ++-- sdk/include/opentelemetry/sdk/version/version.h | 2 +- sdk/src/version/version.cc | 10 +++++----- tbump.toml | 2 +- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f827f592..3ca544a683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [RELEASE] Bump main branch to 1.29.0-dev + [#4259](https://github.com/open-telemetry/opentelemetry-cpp/pull/4259) + * [SDK] Implement the ProbabilitySampler [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3380189ff4..c6f255c49c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ 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_SUFFIX "") +set(OPENTELEMETRY_VERSION_NUMBER "1.29.0") +set(OPENTELEMETRY_VERSION_SUFFIX "-dev") set(OPENTELEMETRY_VERSION "${OPENTELEMETRY_VERSION_NUMBER}${OPENTELEMETRY_VERSION_SUFFIX}") diff --git a/MODULE.bazel b/MODULE.bazel index c8d409be41..e46bf14c66 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,7 +3,7 @@ module( name = "opentelemetry-cpp", - version = "1.28.0", + version = "1.29.0-dev", compatibility_level = 0, repo_name = "io_opentelemetry_cpp", ) diff --git a/api/include/opentelemetry/version.h b/api/include/opentelemetry/version.h index 48908afe88..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" +#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/sdk/include/opentelemetry/sdk/version/version.h b/sdk/include/opentelemetry/sdk/version/version.h index 5924fa4b74..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" +#define OPENTELEMETRY_SDK_VERSION "1.29.0-dev" #include "opentelemetry/version.h" diff --git a/sdk/src/version/version.cc b/sdk/src/version/version.cc index 7504c6c2f2..5230bbdd6b 100644 --- a/sdk/src/version/version.cc +++ b/sdk/src/version/version.cc @@ -12,18 +12,18 @@ 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 = ""; +const char *pre_release = "dev"; const char *build_metadata = "none"; -const char *short_version = "1.28.0"; -const char *full_version = "1.28.0"; +const char *short_version = "1.29.0"; +const char *full_version = "1.29.0-dev"; /** * Release date. * For published releases: YYYY-MM-DD * For -dev releases: empty string */ -const char *build_date = "2026-07-16"; +const char *build_date = ""; } // namespace version } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/tbump.toml b/tbump.toml index e1a3bdd310..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" +current = "1.29.0-dev" # Example of a semver regexp. # Make sure this matches current_version before From ed69e1f397ae21095d90102f0649948cd60970e5 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Thu, 16 Jul 2026 17:20:51 -0700 Subject: [PATCH 50/61] docs: update supported development platforms (#4260) --- CHANGELOG.md | 3 +++ README.md | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca544a683..7caa734535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* 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) 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). From e6dc7d08e920e133ee055c26031f05fccc035e0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:48:52 +0200 Subject: [PATCH 51/61] Bump github/codeql-action/upload-sarif from 4.37.0 to 4.37.1 (#4262) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...7188fc363630916deb702c7fdcf4e481b751f97a) --- updated-dependencies: - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index dd42cd542e..319fdcfea1 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 with: sarif_file: results.sarif From 8e30c1f9ad16887f2ca6592fcf0e21a2c67de161 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:15:11 +0200 Subject: [PATCH 52/61] Bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#4263) * Bump github/codeql-action/analyze from 4.37.0 to 4.37.1 Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...7188fc363630916deb702c7fdcf4e481b751f97a) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Apply suggestion from @marcalff --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Alff --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c7bae0d749..739c723e8f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + 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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 From 6ac03c8af25becdfb5fb18039bf891ba036c1875 Mon Sep 17 00:00:00 2001 From: Hyeonho Kim Date: Fri, 17 Jul 2026 22:59:05 +0900 Subject: [PATCH 53/61] [API] reduce allocation in no span case (#4254) --- CHANGELOG.md | 5 +++ api/include/opentelemetry/trace/context.h | 27 ++++++++++++ .../trace/propagation/b3_propagator.h | 4 +- .../trace/propagation/http_trace_context.h | 2 +- .../opentelemetry/trace/propagation/jaeger.h | 2 +- api/test/trace/context_test.cc | 42 +++++++++++++++++++ opentracing-shim/src/tracer_shim.cc | 2 +- .../sdk/metrics/exemplar/reservoir_cell.h | 11 ++--- sdk/src/logs/logger.cc | 34 +-------------- sdk/src/trace/tracer.cc | 2 +- 10 files changed, 85 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7caa734535..67596768ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,11 @@ Increment the: * [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) + * [SDK] Implement the ProbabilitySampler [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) 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 f8f349d33e..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; 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/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/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/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/src/logs/logger.cc b/sdk/src/logs/logger.cc index cde8a3503c..20bc6e4562 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -9,13 +9,11 @@ #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" @@ -26,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" @@ -50,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 { @@ -86,7 +56,7 @@ 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(); } diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index b0f0e593e8..96b2d81fc9 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -75,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; From 45c464db6ced99a8792d2baaf108b811c64dd552 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Sat, 18 Jul 2026 04:00:35 -0400 Subject: [PATCH 54/61] [CONFIGURATION] Programmatic configuration use case tests and fixes (#4243) --- CHANGELOG.md | 3 + .../sdk/configuration/configured_sdk.h | 3 +- sdk/src/configuration/sdk_builder.cc | 35 +- sdk/test/configuration/BUILD | 42 +- sdk/test/configuration/CMakeLists.txt | 20 +- sdk/test/configuration/configured_sdk_test.cc | 313 ++++++ .../programmatic_configuration_test.cc | 950 ++++++++++++++++++ sdk/test/configuration/sdk_builder_test.cc | 107 +- 8 files changed, 1451 insertions(+), 22 deletions(-) create mode 100644 sdk/test/configuration/configured_sdk_test.cc create mode 100644 sdk/test/configuration/programmatic_configuration_test.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index 67596768ff..0cc6be2fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Increment the: * [RELEASE] Bump main branch to 1.29.0-dev [#4259](https://github.com/open-telemetry/opentelemetry-cpp/pull/4259) +* [CONFIGURATION] Programmatic configuration use case tests and fixes + [#4243](https://github.com/open-telemetry/opentelemetry-cpp/pull/4243) + * [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. diff --git a/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h b/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h index 64d2be0b0b..6362324d4b 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h +++ b/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h @@ -48,7 +48,8 @@ class ConfiguredSdk */ void UnInstall(); - opentelemetry::sdk::common::internal_log::LogLevel log_level; + opentelemetry::sdk::common::internal_log::LogLevel log_level{ + opentelemetry::sdk::common::internal_log::LogLevel::Info}; opentelemetry::sdk::resource::Resource resource; std::shared_ptr tracer_provider; std::shared_ptr propagator; diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index 897413643d..7e3a9600fc 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -43,6 +43,7 @@ #include "opentelemetry/sdk/configuration/console_span_exporter_configuration.h" #include "opentelemetry/sdk/configuration/double_array_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/double_attribute_value_configuration.h" +#include "opentelemetry/sdk/configuration/exemplar_filter.h" #include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" @@ -143,7 +144,6 @@ #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" #include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" -#include "opentelemetry/sdk/metrics/exemplar/filter_type.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h" @@ -182,7 +182,7 @@ #include "src/common/wildcard_match.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/configuration/exemplar_filter.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" #endif OPENTELEMETRY_BEGIN_NAMESPACE @@ -726,7 +726,8 @@ class AggregationConfigBuilder aggregation_type = opentelemetry::sdk::metrics::AggregationType::kSum; } - opentelemetry::sdk::metrics::AggregationType aggregation_type; + opentelemetry::sdk::metrics::AggregationType aggregation_type{ + opentelemetry::sdk::metrics::AggregationType::kDefault}; std::unique_ptr aggregation_config; private: @@ -855,7 +856,15 @@ std::unique_ptr SdkBuilder::CreateParentBase std::unique_ptr local_parent_sampled_sdk; std::unique_ptr local_parent_not_sampled_sdk; - auto root_sdk = SdkBuilder::CreateSampler(model->root); + std::unique_ptr root_sdk; + if (model->root) + { + root_sdk = SdkBuilder::CreateSampler(model->root); + } + else + { + root_sdk = opentelemetry::sdk::trace::AlwaysOnSamplerFactory::Create(); + } if (model->remote_parent_sampled != nullptr) { @@ -1057,11 +1066,7 @@ std::unique_ptr SdkBuilder::CreateBatc opentelemetry::sdk::trace::BatchSpanProcessorOptions options; options.schedule_delay_millis = std::chrono::milliseconds(model->schedule_delay); - -#ifdef LATER - options.xxx = model->export_timeout; -#endif - + options.export_timeout = std::chrono::milliseconds(model->export_timeout); options.max_queue_size = model->max_queue_size; options.max_export_batch_size = model->max_export_batch_size; @@ -1161,6 +1166,12 @@ std::unique_ptr SdkBuilder::CreateTra { sampler = CreateSampler(model->sampler); } + else + { + // Spec default: parentbased_always_on + sampler = opentelemetry::sdk::trace::ParentBasedSamplerFactory::Create( + opentelemetry::sdk::trace::AlwaysOnSamplerFactory::Create()); + } std::vector> sdk_processors; @@ -1683,7 +1694,10 @@ void SdkBuilder::AddView( std::shared_ptr sdk_aggregation_config; - sdk_aggregation_config = CreateAggregationConfig(stream->aggregation, sdk_aggregation_type); + if (stream->aggregation) + { + sdk_aggregation_config = CreateAggregationConfig(stream->aggregation, sdk_aggregation_type); + } std::unique_ptr sdk_attribute_processor; @@ -1890,6 +1904,7 @@ SdkBuilder::CreateBatchLogRecordProcessor( opentelemetry::sdk::logs::BatchLogRecordProcessorOptions options; options.schedule_delay_millis = std::chrono::milliseconds(model->schedule_delay); + options.export_timeout_millis = std::chrono::milliseconds(model->export_timeout); options.max_queue_size = model->max_queue_size; options.max_export_batch_size = model->max_export_batch_size; diff --git a/sdk/test/configuration/BUILD b/sdk/test/configuration/BUILD index f16cc267cc..7b3f6ddd6d 100644 --- a/sdk/test/configuration/BUILD +++ b/sdk/test/configuration/BUILD @@ -9,8 +9,48 @@ cc_test( "sdk_builder_test.cc", ], tags = [ + "config", + "test", + ], + 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 = "configured_sdk_test", + srcs = [ + "configured_sdk_test.cc", + ], + tags = [ + "config", + "test", + ], + 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 = "programmatic_configuration_test", + srcs = [ + "programmatic_configuration_test.cc", + ], + tags = [ + "config", "test", - "yaml", ], deps = [ "//sdk:headers", diff --git a/sdk/test/configuration/CMakeLists.txt b/sdk/test/configuration/CMakeLists.txt index 5385ddf0de..724fef6aeb 100644 --- a/sdk/test/configuration/CMakeLists.txt +++ b/sdk/test/configuration/CMakeLists.txt @@ -1,9 +1,20 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 +foreach(testname sdk_builder_test configured_sdk_test + programmatic_configuration_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} PRIVATE ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_configuration) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX config. + TEST_LIST ${testname}) +endforeach() + foreach( testname - sdk_builder_test yaml_logs_test yaml_metrics_test yaml_propagator_test @@ -12,10 +23,11 @@ foreach( yaml_trace_test yaml_distribution_test) add_executable(${testname} "${testname}.cc") - target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} opentelemetry_configuration) + target_link_libraries( + ${testname} PRIVATE ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_configuration) gtest_add_tests( TARGET ${testname} - TEST_PREFIX trace. + TEST_PREFIX yaml. TEST_LIST ${testname}) endforeach() diff --git a/sdk/test/configuration/configured_sdk_test.cc b/sdk/test/configuration/configured_sdk_test.cc new file mode 100644 index 0000000000..449f4e5e65 --- /dev/null +++ b/sdk/test/configuration/configured_sdk_test.cc @@ -0,0 +1,313 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include + +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/propagation/noop_propagator.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/metrics/noop.h" +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/provider.h" + +#include "opentelemetry/sdk/common/exporter_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/configured_sdk.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/logger_provider_configuration.h" +#include "opentelemetry/sdk/configuration/meter_provider_configuration.h" +#include "opentelemetry/sdk/configuration/metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/propagator_configuration.h" +#include "opentelemetry/sdk/configuration/push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/resource_configuration.h" +#include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/simple_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" + +namespace nostd = opentelemetry::nostd; +namespace trace = opentelemetry::trace; +namespace logs = opentelemetry::logs; +namespace metrics = opentelemetry::metrics; +namespace propagation = opentelemetry::context::propagation; + +namespace common_sdk = opentelemetry::sdk::common; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace config_sdk = opentelemetry::sdk::configuration; +namespace internal_log = opentelemetry::sdk::common::internal_log; + +namespace +{ +//------------------------------------------------------------------------------ +// Noop Exporters + +class NoopSpanExporter : public trace_sdk::SpanExporter +{ +public: + NoopSpanExporter() = default; + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + common_sdk::ExportResult Export( + const nostd::span> &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +class NoopLogRecordExporter : public logs_sdk::LogRecordExporter +{ +public: + NoopLogRecordExporter() = default; + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + common_sdk::ExportResult Export( + const nostd::span> &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +class NoopPushMetricExporter : public metrics_sdk::PushMetricExporter +{ +public: + NoopPushMetricExporter() = default; + common_sdk::ExportResult Export(const metrics_sdk::ResourceMetrics &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + metrics_sdk::AggregationTemporality GetAggregationTemporality( + metrics_sdk::InstrumentType) const noexcept override + { + return metrics_sdk::AggregationTemporality::kCumulative; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +//------------------------------------------------------------------------------ +// Configuration Builders + +class NoopSpanExporterBuilder : public config_sdk::ExtensionSpanExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionSpanExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +class NoopLogRecordExporterBuilder : public config_sdk::ExtensionLogRecordExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionLogRecordExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +class NoopPushMetricExporterBuilder : public config_sdk::ExtensionPushMetricExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionPushMetricExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +//------------------------------------------------------------------------------ +// ConfiguredSdkTest fixture + +class ConfiguredSdkTest : public ::testing::Test +{ +protected: + void SetUp() override + { + MakeRegistry(); + SetNullProviders(); + } + + void TearDown() override + { + if (sdk_) + { + sdk_->UnInstall(); + } + SetNoopProviders(); + } + + void SetNullProviders() + { + propagation::GlobalTextMapPropagator::SetGlobalPropagator({}); + trace::Provider::SetTracerProvider({}); + logs::Provider::SetLoggerProvider({}); + metrics::Provider::SetMeterProvider({}); + + ASSERT_EQ(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + ASSERT_EQ(trace::Provider::GetTracerProvider(), nullptr); + ASSERT_EQ(logs::Provider::GetLoggerProvider(), nullptr); + ASSERT_EQ(metrics::Provider::GetMeterProvider(), nullptr); + } + + void SetNoopProviders() + { + propagation::GlobalTextMapPropagator::SetGlobalPropagator( + {std::make_shared()}); + trace::Provider::SetTracerProvider({std::make_shared()}); + logs::Provider::SetLoggerProvider({std::make_shared()}); + metrics::Provider::SetMeterProvider({std::make_shared()}); + + ASSERT_NE(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + ASSERT_NE(trace::Provider::GetTracerProvider(), nullptr); + ASSERT_NE(logs::Provider::GetLoggerProvider(), nullptr); + ASSERT_NE(metrics::Provider::GetMeterProvider(), nullptr); + } + + void CreateSdk(const std::unique_ptr &model) + { + ASSERT_TRUE(sdk_ == nullptr); + ASSERT_NO_THROW(sdk_ = config_sdk::ConfiguredSdk::Create(registry_, model)); + ASSERT_FALSE(sdk_ == nullptr); + } + + void MakeRegistry() + { + registry_ = std::make_shared(); + registry_->SetExtensionSpanExporterBuilder("noop", std::make_unique()); + registry_->SetExtensionLogRecordExporterBuilder( + "noop", std::make_unique()); + registry_->SetExtensionPushMetricExporterBuilder( + "noop", std::make_unique()); + } + + static std::unique_ptr MakeTracerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeLoggerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeMeterProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto reader = std::make_unique(); + reader->exporter = std::move(exporter); + reader->interval = 3'600'000; // milliseconds. Set to a large value and rely on ForceFlush to + // trigger collection. + reader->timeout = 60'000; // milliseconds + auto config = std::make_unique(); + config->readers.emplace_back(std::move(reader)); + return config; + } + + std::shared_ptr registry_; + std::unique_ptr sdk_; +}; + +} // namespace + +//------------------------------------------------------------------------------ +// ConfiguredSdk Tests. These are intended to cover just the API and implementation of the +// ConfiguredSdk class. +// For integration testing to determine if the SDK is configured correctly, see +// the programmatic_configuration_test.cc file + +TEST_F(ConfiguredSdkTest, ConfiguredSdkDefaultLogLevel) +{ + // default log level is info + auto model = std::make_unique(); + model->resource = std::make_unique(); + CreateSdk(model); + sdk_->Install(); + EXPECT_EQ(sdk_->log_level, internal_log::LogLevel::Info); + EXPECT_EQ(internal_log::GlobalLogHandler::GetLogLevel(), internal_log::LogLevel::Info); +} + +TEST_F(ConfiguredSdkTest, ConfiguredSdkSetLogLevel) +{ + auto model = std::make_unique(); + model->resource = std::make_unique(); + model->log_level = config_sdk::SeverityNumber::debug; + CreateSdk(model); + sdk_->Install(); + EXPECT_EQ(sdk_->log_level, internal_log::LogLevel::Debug); + EXPECT_EQ(internal_log::GlobalLogHandler::GetLogLevel(), internal_log::LogLevel::Debug); +} + +TEST_F(ConfiguredSdkTest, ConfiguredSdkInstallUninstall) +{ + auto model = std::make_unique(); + model->resource = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + model->propagator = std::make_unique(); + model->propagator->composite_list = "tracecontext,baggage,b3,b3multi,jaeger"; + + CreateSdk(model); + sdk_->Install(); + EXPECT_NE(trace::Provider::GetTracerProvider(), nullptr); + EXPECT_NE(logs::Provider::GetLoggerProvider(), nullptr); + EXPECT_NE(metrics::Provider::GetMeterProvider(), nullptr); + EXPECT_NE(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + + sdk_->UnInstall(); + EXPECT_EQ(trace::Provider::GetTracerProvider(), nullptr); + EXPECT_EQ(logs::Provider::GetLoggerProvider(), nullptr); + EXPECT_EQ(metrics::Provider::GetMeterProvider(), nullptr); + EXPECT_EQ(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); +} diff --git a/sdk/test/configuration/programmatic_configuration_test.cc b/sdk/test/configuration/programmatic_configuration_test.cc new file mode 100644 index 0000000000..6e00c6c526 --- /dev/null +++ b/sdk/test/configuration/programmatic_configuration_test.cc @@ -0,0 +1,950 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/baggage/baggage_context.h" +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/context/context.h" +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/propagation/noop_propagator.h" +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/logs/severity.h" +#include "opentelemetry/metrics/meter.h" +#include "opentelemetry/metrics/meter_provider.h" +#include "opentelemetry/metrics/noop.h" +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/metrics/sync_instruments.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/context.h" +#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/configuration/aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/always_off_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/batch_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/configured_sdk.h" +#include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/instrument_type.h" +#include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_limits_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_processor_configuration.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/logger_provider_configuration.h" +#include "opentelemetry/sdk/configuration/meter_config_configuration.h" +#include "opentelemetry/sdk/configuration/meter_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/meter_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/meter_provider_configuration.h" +#include "opentelemetry/sdk/configuration/metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/parent_based_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/propagator_configuration.h" +#include "opentelemetry/sdk/configuration/push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/resource_configuration.h" +#include "opentelemetry/sdk/configuration/sampler_configuration.h" +#include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/simple_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_config_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/configuration/view_configuration.h" +#include "opentelemetry/sdk/configuration/view_selector_configuration.h" +#include "opentelemetry/sdk/configuration/view_stream_configuration.h" + +#include "opentelemetry/sdk/common/exporter_utils.h" +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/metrics/data/metric_data.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" + +namespace nostd = opentelemetry::nostd; +namespace trace = opentelemetry::trace; +namespace logs = opentelemetry::logs; +namespace metrics = opentelemetry::metrics; +namespace common = opentelemetry::common; +namespace baggage = opentelemetry::baggage; +namespace propagation = opentelemetry::context::propagation; +namespace context = opentelemetry::context; +namespace common_sdk = opentelemetry::sdk::common; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace config_sdk = opentelemetry::sdk::configuration; + +namespace +{ +// --------------------------------------------------------------------------- +// Shared export buffers + +using LogRecordBuffer = std::vector>; +using SpanBuffer = std::vector>; +using MetricBuffer = std::vector; + +// --------------------------------------------------------------------------- +// Recording exporters to support integration testing of the configured SDK. +// These exporters record the data they receive into a buffer for later inspection. + +class RecordingSpanExporter : public trace_sdk::SpanExporter +{ +public: + explicit RecordingSpanExporter(std::shared_ptr buffer) : buffer_(std::move(buffer)) {} + + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + + common_sdk::ExportResult Export( + const nostd::span> &spans) noexcept override + { + for (auto &span : spans) + { + buffer_->emplace_back(static_cast(span.release())); + } + return common_sdk::ExportResult::kSuccess; + } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +class RecordingLogRecordExporter : public logs_sdk::LogRecordExporter +{ +public: + explicit RecordingLogRecordExporter(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + + common_sdk::ExportResult Export( + const nostd::span> &records) noexcept override + { + for (auto &rec : records) + { + buffer_->emplace_back(static_cast(rec.release())); + } + return common_sdk::ExportResult::kSuccess; + } + + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +class RecordingPushMetricExporter : public metrics_sdk::PushMetricExporter +{ +public: + explicit RecordingPushMetricExporter(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + + common_sdk::ExportResult Export( + const metrics_sdk::ResourceMetrics &resource_metrics) noexcept override + { + for (const auto &scope : resource_metrics.scope_metric_data_) + { + for (const auto &metric : scope.metric_data_) + { + buffer_->emplace_back(metric); + } + } + return common_sdk::ExportResult::kSuccess; + } + + metrics_sdk::AggregationTemporality GetAggregationTemporality( + metrics_sdk::InstrumentType) const noexcept override + { + return metrics_sdk::AggregationTemporality::kCumulative; + } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +// --------------------------------------------------------------------------- +// Configuration Builders for recording exporters + +class RecordingSpanExporterBuilder : public config_sdk::ExtensionSpanExporterBuilder +{ +public: + explicit RecordingSpanExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionSpanExporterConfiguration *) const override + { + return std::make_unique(buffer_); + } + +private: + std::shared_ptr buffer_; +}; + +class RecordingLogRecordExporterBuilder : public config_sdk::ExtensionLogRecordExporterBuilder +{ +public: + explicit RecordingLogRecordExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionLogRecordExporterConfiguration *) const override + { + return std::make_unique(buffer_); + } + +private: + std::shared_ptr buffer_; +}; + +class RecordingPushMetricExporterBuilder : public config_sdk::ExtensionPushMetricExporterBuilder +{ +public: + explicit RecordingPushMetricExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionPushMetricExporterConfiguration *) const override + { + auto exporter = std::make_unique(buffer_); + return exporter; + } + +private: + std::shared_ptr buffer_; +}; + +// --------------------------------------------------------------------------- +// TextMapCarrier for propagator tests. + +class MapCarrier : public propagation::TextMapCarrier +{ +public: + nostd::string_view Get(nostd::string_view key) const noexcept override + { + auto it = map_.find(std::string(key)); + return it != map_.end() ? nostd::string_view(it->second) : ""; + } + void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + map_[std::string(key)] = std::string(value); + } + + const std::map &map() const { return map_; } + +private: + std::map map_; +}; + +//--------------------------------------------------------------------------- +// ProgrammaticConfigTest fixture: This supports integration testing of the configured SDK. +// It registers the recording exporters and maintains buffers for inspection of exported signal +// data. + +class ProgrammaticConfigTest : public ::testing::Test +{ +protected: + void SetUp() override + { + MakeRegistry(); + + propagation::GlobalTextMapPropagator::SetGlobalPropagator( + {std::make_shared()}); + trace::Provider::SetTracerProvider({std::make_shared()}); + logs::Provider::SetLoggerProvider({std::make_shared()}); + metrics::Provider::SetMeterProvider({std::make_shared()}); + } + + void TearDown() override + { + if (sdk_) + { + sdk_->UnInstall(); + } + } + + void CreateAndInstallSdk(const std::unique_ptr &model) + { + ASSERT_TRUE(sdk_ == nullptr); + ASSERT_NO_THROW(sdk_ = config_sdk::ConfiguredSdk::Create(registry_, model)); + ASSERT_FALSE(sdk_ == nullptr); + sdk_->Install(); + } + + void MakeRegistry() + { + registry_ = std::make_shared(); + registry_->SetExtensionSpanExporterBuilder( + "recording", std::make_unique(span_buffer_)); + registry_->SetExtensionLogRecordExporterBuilder( + "recording", std::make_unique(log_buffer_)); + registry_->SetExtensionPushMetricExporterBuilder( + "recording", std::make_unique(metric_buffer_)); + } + + static std::unique_ptr MakeTracerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeLoggerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeMeterProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto reader = std::make_unique(); + reader->exporter = std::move(exporter); + reader->interval = 3'600'000; // milliseconds. Set to a large value and rely on ForceFlush to + // trigger collection. + reader->timeout = 60'000; // milliseconds + auto config = std::make_unique(); + config->readers.emplace_back(std::move(reader)); + return config; + } + + std::shared_ptr span_buffer_{std::make_shared()}; + std::shared_ptr log_buffer_{std::make_shared()}; + std::shared_ptr metric_buffer_{std::make_shared()}; + + std::shared_ptr registry_; + std::unique_ptr sdk_; +}; + +} // namespace + +//--------------------------------------------------------------------------- +// Resource configuration tests + +TEST_F(ProgrammaticConfigTest, ResourceAttributesFromList) +{ + auto resource_config = std::make_unique(); + resource_config->attributes_list = "service.name=test-service,service.version=1.0"; + auto model = std::make_unique(); + model->resource = std::move(resource_config); + + CreateAndInstallSdk(model); + + const auto &attributes = sdk_->resource.GetAttributes(); + ASSERT_NE(attributes.find("service.name"), attributes.end()); + EXPECT_EQ(nostd::get(attributes.at("service.name")), "test-service"); + ASSERT_NE(attributes.find("service.version"), attributes.end()); + EXPECT_EQ(nostd::get(attributes.at("service.version")), "1.0"); +} + +//-------------------------------------------------------------------------- +// Disabled SDK configuration tests + +TEST_F(ProgrammaticConfigTest, DisabledConfigProducesNullProviders) +{ + auto model = std::make_unique(); + model->disabled = true; + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + + CreateAndInstallSdk(model); + + EXPECT_EQ(sdk_->tracer_provider, nullptr); + EXPECT_EQ(sdk_->logger_provider, nullptr); + EXPECT_EQ(sdk_->meter_provider, nullptr); + EXPECT_EQ(sdk_->propagator, nullptr); +} + +TEST_F(ProgrammaticConfigTest, EnabledConfigProducesProviders) +{ + auto model = std::make_unique(); + model->disabled = false; + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + model->propagator = std::make_unique(); + model->propagator->composite_list = "tracecontext"; + + CreateAndInstallSdk(model); + + EXPECT_NE(sdk_->tracer_provider, nullptr); + EXPECT_NE(sdk_->logger_provider, nullptr); + EXPECT_NE(sdk_->meter_provider, nullptr); + EXPECT_NE(sdk_->propagator, nullptr); +} + +//--------------------------------------------------------------------------- +// LoggerProvider tests + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithDefaults) +{ + auto logger_provider_config = MakeLoggerProviderConfig(); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + + ASSERT_NE(sdk_->logger_provider, nullptr); + + auto logger = logs::Provider::GetLoggerProvider()->GetLogger("test"); + logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithLogRecordLimits) +{ + config_sdk::LogRecordLimitsConfiguration limits{ + 0, 0}; // TODO: Remove the default initialization once the limit members are initialized. + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 5; + + auto logger_provider_config = MakeLoggerProviderConfig(); + logger_provider_config->limits = + std::make_unique(limits); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + auto logger = logs::Provider::GetLoggerProvider()->GetLogger("test"); + logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); + auto *record = log_buffer_->front().get(); + EXPECT_EQ(nostd::get(record->GetBody()), "test-message"); + const auto &attributes = record->GetAttributes(); + EXPECT_EQ(attributes.size(), limits.attribute_count_limit); + for (const auto &attr : attributes) + { + EXPECT_EQ(nostd::get(attr.second).size(), limits.attribute_value_length_limit); + } +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithLoggerConfigurator) +{ + auto error_logger_matcher = config_sdk::LoggerMatcherAndConfigConfiguration(); + error_logger_matcher.name = "error_logger"; + error_logger_matcher.config.enabled = true; + error_logger_matcher.config.minimum_severity = config_sdk::SeverityNumber::error; + + auto logger_configurator = std::make_unique(); + logger_configurator->default_config.enabled = true; + logger_configurator->default_config.minimum_severity = config_sdk::SeverityNumber::info; + logger_configurator->loggers.emplace_back(std::move(error_logger_matcher)); + + auto logger_provider_config = MakeLoggerProviderConfig(); + logger_provider_config->logger_configurator = std::move(logger_configurator); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + // The default logger should be enabled and have a minimum severity of info + auto default_logger = logs::Provider::GetLoggerProvider()->GetLogger("default_logger"); + default_logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + EXPECT_TRUE(default_logger->Enabled(logs::Severity::kInfo)); + + // The error_logger should be enabled and have a minimum severity of error + auto error_logger = logs::Provider::GetLoggerProvider()->GetLogger("error_logger"); + error_logger->EmitLogRecord(logs::Severity::kError, nostd::string_view("test-message")); + + EXPECT_FALSE(error_logger->Enabled(logs::Severity::kInfo)); + EXPECT_TRUE(error_logger->Enabled(logs::Severity::kError)); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 2); +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithBatchProcessorDefaults) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto logger_provider_config = std::make_unique(); + logger_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + logs::Provider::GetLoggerProvider()->GetLogger("test")->Info("test-message"); + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithBatchProcessorConfigured) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + processor->schedule_delay = 60000; + processor->max_queue_size = 100; + processor->max_export_batch_size = 50; + processor->export_timeout = 5000; + auto logger_provider_config = std::make_unique(); + logger_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + logs::Provider::GetLoggerProvider()->GetLogger("test")->Info("test-message"); + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +//-------------------------------------------------------------------------- +// MeterProvider tests + +// TODO: These test cases may timeout due to threading in the PeriodicExportingMetricReader +// that cause ForceFlush or Shutdown to block indefinitely. Disabling for now until we can fix the +// underlying issue. +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithDefaults) +{ + auto model = std::make_unique(); + model->meter_provider = MakeMeterProviderConfig(); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + metrics::Provider::GetMeterProvider() + ->GetMeter("test") + ->CreateUInt64Counter("test-counter") + ->Add(1); + + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(metric_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithMeterConfigurator) +{ + auto disabled_meter_config = config_sdk::MeterMatcherAndConfigConfiguration(); + disabled_meter_config.name = "disabled-meter"; + disabled_meter_config.config.enabled = false; + + auto meter_configurator = std::make_unique(); + meter_configurator->default_config.enabled = true; + meter_configurator->meters.push_back(disabled_meter_config); + + auto model = std::make_unique(); + model->meter_provider = MakeMeterProviderConfig(); + model->meter_provider->meter_configurator = std::move(meter_configurator); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + auto default_meter = metrics::Provider::GetMeterProvider()->GetMeter("default-meter"); + default_meter->CreateUInt64Counter("test-counter")->Add(1); + + auto disabled_meter = metrics::Provider::GetMeterProvider()->GetMeter(disabled_meter_config.name); + disabled_meter->CreateUInt64Counter("disabled-test-counter")->Add(1); + + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(metric_buffer_->size(), 1); + for (const auto &metric : *metric_buffer_) + { + EXPECT_NE(metric.instrument_descriptor.name_, "disabled-test-counter"); + } +} + +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithViews) +{ + // View 1: Base2 exponential aggregation + const std::size_t max_scale = 10; + const std::size_t max_buckets = 135; + auto base2_histogram_aggregation = + std::make_unique(); + base2_histogram_aggregation->max_scale = max_scale; + base2_histogram_aggregation->max_size = max_buckets; + + auto base2_histogram_stream = std::make_unique(); + base2_histogram_stream->aggregation = std::move(base2_histogram_aggregation); + + auto base2_histogram_selector = std::make_unique(); + base2_histogram_selector->instrument_type = config_sdk::InstrumentType::histogram; + base2_histogram_selector->instrument_name = "exponential-histogram"; + + auto base2_histogram_view = std::make_unique(); + base2_histogram_view->selector = std::move(base2_histogram_selector); + base2_histogram_view->stream = std::move(base2_histogram_stream); + + // View 2: Explicit bucket histogram aggregation. + auto explicit_histogram_aggregation = + std::make_unique(); + explicit_histogram_aggregation->boundaries = {0.0, 10.0, 50.0, 100.0}; + explicit_histogram_aggregation->record_min_max = true; + + auto explicit_histogram_stream = std::make_unique(); + explicit_histogram_stream->aggregation = std::move(explicit_histogram_aggregation); + + auto explicit_selector = std::make_unique(); + explicit_selector->instrument_type = config_sdk::InstrumentType::histogram; + explicit_selector->instrument_name = "explicit-histogram"; + + auto explicit_histogram_view = std::make_unique(); + explicit_histogram_view->selector = std::move(explicit_selector); + explicit_histogram_view->stream = std::move(explicit_histogram_stream); + + // View 3: no aggregation set + auto default_stream = std::make_unique(); + default_stream->aggregation = nullptr; // intentionally null + + auto default_selector = std::make_unique(); + default_selector->instrument_type = config_sdk::InstrumentType::counter; + default_selector->instrument_name = "default-counter"; + + auto default_view = std::make_unique(); + default_view->selector = std::move(default_selector); + default_view->stream = std::move(default_stream); + + auto meter_provider_config = MakeMeterProviderConfig(); + meter_provider_config->views.emplace_back(std::move(base2_histogram_view)); + meter_provider_config->views.emplace_back(std::move(explicit_histogram_view)); + meter_provider_config->views.emplace_back(std::move(default_view)); + + auto model = std::make_unique(); + model->meter_provider = std::move(meter_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + auto context = context::Context{}; + auto meter = metrics::Provider::GetMeterProvider()->GetMeter("test"); + meter->CreateDoubleHistogram("exponential-histogram")->Record(42.0, context); + meter->CreateDoubleHistogram("explicit-histogram")->Record(42.0, context); + meter->CreateUInt64Counter("default-counter")->Add(1, context); + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + // check that instances of the three data points were collected and are of the right type. + EXPECT_GE(metric_buffer_->size(), 3); + bool found_base2_histogram = false; + bool found_explicit_histogram = false; + bool found_default_counter = false; + + for (const auto &metric : *metric_buffer_) + { + auto &point_data = metric.point_data_attr_.front().point_data; + if (nostd::holds_alternative(point_data)) + { + found_base2_histogram = true; + auto &base2_point_data = + nostd::get(point_data); + EXPECT_EQ(base2_point_data.max_buckets_, max_buckets); + EXPECT_LE(base2_point_data.scale_, max_scale); + } + else if (nostd::holds_alternative(point_data)) + { + found_explicit_histogram = true; + } + else if (nostd::holds_alternative(point_data)) + { + found_default_counter = true; + } + } + + EXPECT_TRUE(found_base2_histogram); + EXPECT_TRUE(found_explicit_histogram); + EXPECT_TRUE(found_default_counter); +} + +//--------------------------------------------------------------------------- +// TracerProvider tests + +TEST_F(ProgrammaticConfigTest, TracerProviderWithDefaults) +{ + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + auto default_tracer = trace::Provider::GetTracerProvider()->GetTracer("default-tracer"); + default_tracer->StartSpan("test-span")->End(); + + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithTracerConfigurator) +{ + auto disabled_tracer_matcher = config_sdk::TracerMatcherAndConfigConfiguration(); + disabled_tracer_matcher.name = "disabled-tracer"; + disabled_tracer_matcher.config.enabled = false; + + auto tracer_configurator = std::make_unique(); + tracer_configurator->default_config.enabled = true; + tracer_configurator->tracers.push_back(disabled_tracer_matcher); + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->tracer_configurator = std::move(tracer_configurator); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + auto default_tracer = trace::Provider::GetTracerProvider()->GetTracer("default-tracer"); + default_tracer->StartSpan("test-span")->End(); + + auto disabled_tracer = trace::Provider::GetTracerProvider()->GetTracer("disabled-tracer"); + disabled_tracer->StartSpan("disabled-test-span")->End(); + + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + + ASSERT_EQ(span_buffer_->size(), 1); + EXPECT_NE(span_buffer_->at(0)->GetName(), "disabled-test-span"); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithSampler) +{ + auto sampler = std::make_unique(); + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->sampler = std::move(sampler); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 0); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithParentBasedSamplerNullRoot) +{ + auto sampler = std::make_unique(); + sampler->root = nullptr; // explicitly null + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->sampler = std::move(sampler); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 1); +} + +// TODO: Re-enable this test once the BatchSpanProcessorConfiguration is initialized with spec +// defaults. +TEST_F(ProgrammaticConfigTest, TracerProviderWithBatchProcessor) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto tracer_provider_config = std::make_unique(); + tracer_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->tracer_provider = std::move(tracer_provider_config); + + CreateAndInstallSdk(model); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(span_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithBatchProcessorConfigured) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->schedule_delay = 60000; + processor->max_queue_size = 100; + processor->max_export_batch_size = 50; + processor->export_timeout = 5000; + processor->exporter = std::move(exporter); + auto tracer_provider_config = std::make_unique(); + tracer_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->tracer_provider = std::move(tracer_provider_config); + + CreateAndInstallSdk(model); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(span_buffer_->size(), 1); +} + +//--------------------------------------------------------------------------- +// Propagator tests + +namespace +{ +void CheckPropagators() +{ + auto tracer = trace::Provider::GetTracerProvider()->GetTracer("test"); + + auto span = tracer->StartSpan("test-span"); + auto ctx0 = context::RuntimeContext::GetCurrent(); + auto ctx1 = trace::SetSpan(ctx0, span); + + // Add baggage so the baggage propagator has something to inject + auto baggage = baggage::Baggage::GetDefault()->Set("key", "value"); + auto ctx = baggage::SetBaggage(ctx1, baggage); + + MapCarrier carrier; + propagation::GlobalTextMapPropagator::GetGlobalPropagator()->Inject(carrier, ctx); + + ASSERT_NE(carrier.map().find("traceparent"), carrier.map().end()); // tracecontext + // traceparent format: "00-<32 hex trace_id>-<16 hex span_id>-<2 hex flags>" = 55 chars + const std::string &traceparent = carrier.map().at("traceparent"); + EXPECT_EQ(traceparent.size(), 55U); + EXPECT_EQ(traceparent.substr(0, 3), "00-"); + EXPECT_NE(carrier.map().find("baggage"), carrier.map().end()); // baggage + EXPECT_NE(carrier.map().find("b3"), carrier.map().end()); // b3 single + EXPECT_NE(carrier.map().find("X-B3-TraceId"), carrier.map().end()); // b3multi + EXPECT_NE(carrier.map().find("uber-trace-id"), carrier.map().end()); // jaeger +} +} // namespace + +TEST_F(ProgrammaticConfigTest, PropagatorsCompositeList) +{ + auto propagator_config = std::make_unique(); + propagator_config->composite_list = "tracecontext,baggage,b3,b3multi,jaeger"; + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->propagator = std::move(propagator_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->propagator, nullptr); + + CheckPropagators(); +} + +TEST_F(ProgrammaticConfigTest, PropagatorsComposite) +{ + auto propagator_config = std::make_unique(); + propagator_config->composite.emplace_back("tracecontext"); + propagator_config->composite.emplace_back("baggage"); + propagator_config->composite.emplace_back("b3"); + propagator_config->composite.emplace_back("b3multi"); + propagator_config->composite.emplace_back("jaeger"); + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->propagator = std::move(propagator_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->propagator, nullptr); + + CheckPropagators(); +} diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc index 22d07a8fc0..b4678b0fd6 100644 --- a/sdk/test/configuration/sdk_builder_test.cc +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -2,23 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 #include + #include #include +#include #include - #include "opentelemetry/logs/severity.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/configuration/always_off_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/always_on_sampler_configuration.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/parent_based_sampler_configuration.h" #include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/sampler_configuration.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/trace_id_ratio_based_sampler_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/sampler.h" #include "opentelemetry/sdk/trace/span_limits.h" #include "opentelemetry/sdk/trace/tracer_provider.h" @@ -26,9 +34,17 @@ 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 = opentelemetry::logs; namespace logs_sdk = opentelemetry::sdk::logs; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace config_sdk = opentelemetry::sdk::configuration; + +//------------------------------------------------------------------------------ +// Tests for the SdkBuilder class methods that create SDK components from configuration models +// These tests focus on the API of the SdkBuilder for creating SDK components that can be +// independently verified. For full integration tests of the SdkBuilder with configuration models, +// see the programmatic_configuration_test.cc file. TEST(SdkBuilder, SpanLimitsDefaults) { @@ -116,14 +132,93 @@ TEST(SdkBuilder, CreateLoggerConfigurator) 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_EQ(sdk_logger_config_default.GetMinimumSeverity(), 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_EQ(sdk_logger_config_1.GetMinimumSeverity(), 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_EQ(sdk_logger_config_2.GetMinimumSeverity(), logs::Severity::kDebug); EXPECT_TRUE(sdk_logger_config_2.IsTraceBased()); } + +TEST(SdkBuilder, CreateParentBasedSampler) +{ + // parent based with no root configured should default to always on + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = nullptr; + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOnSampler})"); + } + + // parent based with root always on + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = std::make_unique(); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOnSampler})"); + } + + // parent based with root always off + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = + std::make_unique(); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOffSampler})"); + } + + // parent based with a custom root sampler + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + auto trace_id_ratio_based_sampler_config = + std::make_unique(); + trace_id_ratio_based_sampler_config->ratio = 0.5; + parent_based_sampler_config.root = std::move(trace_id_ratio_based_sampler_config); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, + R"(ParentBased{TraceIdRatioBasedSampler{0.500000}})"); + } + + // parent based with all sub samplers set + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + auto trace_id_ratio_based_sampler_config = + std::make_unique(); + trace_id_ratio_based_sampler_config->ratio = 0.25; + parent_based_sampler_config.root = std::move(trace_id_ratio_based_sampler_config); + + auto always_off_sampler_config = std::make_unique(); + parent_based_sampler_config.remote_parent_sampled = std::move(always_off_sampler_config); + + auto always_on_sampler_config = std::make_unique(); + parent_based_sampler_config.remote_parent_not_sampled = std::move(always_on_sampler_config); + + auto trace_id_ratio_based_sampler_config_2 = + std::make_unique(); + trace_id_ratio_based_sampler_config_2->ratio = 0.35; + parent_based_sampler_config.local_parent_sampled = + std::move(trace_id_ratio_based_sampler_config_2); + + auto always_off_sampler_config_2 = + std::make_unique(); + parent_based_sampler_config.local_parent_not_sampled = std::move(always_off_sampler_config_2); + + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, + R"(ParentBased{TraceIdRatioBasedSampler{0.250000}})"); + } +} From e19357ffbaa013ac836696f843c97dc5e9e14c92 Mon Sep 17 00:00:00 2001 From: Ravi Date: Sat, 18 Jul 2026 10:39:40 -0400 Subject: [PATCH 55/61] [METRICS SDK] Validate Base2 Exponential Histogram Aggregation config (#4253) --- CHANGELOG.md | 10 ++ examples/metrics_simple/metrics_ostream.cc | 2 +- ...cket_histogram_aggregation_configuration.h | 12 +-- .../sdk/configuration/document_node.h | 5 + .../sdk/configuration/ryml_document_node.h | 3 + .../metrics/aggregation/aggregation_config.h | 9 +- .../sdk/metrics/exemplar/reservoir_utils.h | 3 +- sdk/src/configuration/configuration_parser.cc | 21 +++- sdk/src/configuration/document_node.cc | 30 ++++++ sdk/src/configuration/ryml_document_node.cc | 27 +++++ sdk/src/configuration/sdk_builder.cc | 4 +- ...base2_exponential_histogram_aggregation.cc | 22 +++- sdk/test/configuration/yaml_metrics_test.cc | 100 +++++++++++++++++- sdk/test/metrics/aggregation_test.cc | 46 ++++++-- .../metrics/histogram_aggregation_test.cc | 4 +- 15 files changed, 269 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc6be2fb5..b98735d43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ Increment the: `GetSpan(context)->GetContext()` incurs, and use it at existing call sites. [#4254](https://github.com/open-telemetry/opentelemetry-cpp/pull/4254) +* [METRICS SDK] Validate Base2 Exponential Histogram Aggregation config + [#4253](https://github.com/open-telemetry/opentelemetry-cpp/pull/4253) + * [SDK] Implement the ProbabilitySampler [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) @@ -36,6 +39,13 @@ Increment the: to align with the OpenTelemetry specification [#4161](https://github.com/open-telemetry/opentelemetry-cpp/issues/4161) +Breaking changes: + +* [METRICS SDK] Rename Base2 Exponential Histogram Aggregation config field + [#4253](https://github.com/open-telemetry/opentelemetry-cpp/pull/4253) + * The public configuration member `max_buckets_` was renamed to `max_size_` to + match the configuration schema. Please adjust SDK configuration accordingly. + ## [1.28.0] 2026-07-16 * [RELEASE] Bump main branch to 1.28.0-dev diff --git a/examples/metrics_simple/metrics_ostream.cc b/examples/metrics_simple/metrics_ostream.cc index 46a775d32f..1834b40b9f 100644 --- a/examples/metrics_simple/metrics_ostream.cc +++ b/examples/metrics_simple/metrics_ostream.cc @@ -124,7 +124,7 @@ void InitMetrics(const std::string &name) new metrics_sdk::Base2ExponentialHistogramAggregationConfig); histogram_base2_aggregation_config->max_scale_ = 3; histogram_base2_aggregation_config->record_min_max_ = true; - histogram_base2_aggregation_config->max_buckets_ = 100; + histogram_base2_aggregation_config->max_size_ = 100; std::shared_ptr base2_aggregation_config( std::move(histogram_base2_aggregation_config)); 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 e684eadae7..adfaac3f94 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 @@ -4,6 +4,7 @@ #pragma once #include +#include #include "opentelemetry/sdk/configuration/aggregation_configuration.h" #include "opentelemetry/sdk/configuration/aggregation_configuration_visitor.h" @@ -20,19 +21,16 @@ 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; + static constexpr std::int32_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{kDefaultMaxScale}; + std::int32_t max_scale{kDefaultMaxScale}; std::size_t max_size{kDefaultMaxSize}; bool record_min_max{kDefaultRecordMinMax}; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/document_node.h b/sdk/include/opentelemetry/sdk/configuration/document_node.h index 7a725a3116..2dc715eec9 100644 --- a/sdk/include/opentelemetry/sdk/configuration/document_node.h +++ b/sdk/include/opentelemetry/sdk/configuration/document_node.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -49,6 +50,9 @@ class DocumentNode virtual std::size_t GetRequiredInteger(const std::string &name) const = 0; virtual std::size_t GetInteger(const std::string &name, std::size_t default_value) const = 0; + virtual std::int64_t GetSignedInteger(const std::string &name, + std::int64_t default_value) const = 0; + virtual double GetRequiredDouble(const std::string &name) const = 0; virtual double GetDouble(const std::string &name, double default_value) const = 0; @@ -71,6 +75,7 @@ class DocumentNode bool BooleanFromString(const std::string &value) const; std::size_t IntegerFromString(const std::string &value) const; + std::int64_t SignedIntegerFromString(const std::string &value) const; double DoubleFromString(const std::string &value) const; }; diff --git a/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h index 23e559977a..1d37b0a7d2 100644 --- a/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h +++ b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -49,6 +50,8 @@ class RymlDocumentNode : public DocumentNode std::size_t GetRequiredInteger(const std::string &name) const override; std::size_t GetInteger(const std::string &name, std::size_t default_value) const override; + std::int64_t GetSignedInteger(const std::string &name, std::int64_t default_value) const override; + double GetRequiredDouble(const std::string &name) const override; double GetDouble(const std::string &name, double default_value) const override; diff --git a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h index 33792ca688..e96736fca9 100644 --- a/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h +++ b/sdk/include/opentelemetry/sdk/metrics/aggregation/aggregation_config.h @@ -56,6 +56,13 @@ class HistogramAggregationConfig : public AggregationConfig bool record_min_max_ = true; }; +// Valid ranges per the declarative configuration schema; the schema defines no maximum for +// max_size. +// https://github.com/open-telemetry/opentelemetry-configuration/blob/main/schema/meter_provider.yaml +constexpr std::int32_t kMaxScaleMin = -10; +constexpr std::int32_t kMaxScaleMax = 20; +constexpr std::size_t kMaxSizeMin = 2; + class Base2ExponentialHistogramAggregationConfig : public AggregationConfig { public: @@ -69,7 +76,7 @@ class Base2ExponentialHistogramAggregationConfig : public AggregationConfig return AggregationType::kBase2ExponentialHistogram; } - size_t max_buckets_ = 160; + size_t max_size_ = 160; int32_t max_scale_ = 20; bool record_min_max_ = true; }; diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h index 6ca88411af..f820eddc25 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/reservoir_utils.h @@ -39,8 +39,7 @@ static inline size_t GetSimpleFixedReservoirDefaultSize(const AggregationType ag { const auto *histogram_agg_config = static_cast(agg_config); - return (std::min)(kMaxBase2ExponentialHistogramReservoirSize, - histogram_agg_config->max_buckets_); + return (std::min)(kMaxBase2ExponentialHistogramReservoirSize, histogram_agg_config->max_size_); } return SimpleFixedSizeExemplarReservoir::kDefaultSimpleReservoirSize; diff --git a/sdk/src/configuration/configuration_parser.cc b/sdk/src/configuration/configuration_parser.cc index cc83201a97..bfe4dfa373 100644 --- a/sdk/src/configuration/configuration_parser.cc +++ b/sdk/src/configuration/configuration_parser.cc @@ -128,6 +128,7 @@ #include "opentelemetry/sdk/configuration/view_configuration.h" #include "opentelemetry/sdk/configuration/view_selector_configuration.h" #include "opentelemetry/sdk/configuration/view_stream_configuration.h" +#include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -1370,8 +1371,24 @@ ConfigurationParser::ParseBase2ExponentialBucketHistogramAggregationConfiguratio using Config = Base2ExponentialBucketHistogramAggregationConfiguration; auto model = std::make_unique(); - model->max_scale = node->GetInteger("max_scale", Config::kDefaultMaxScale); - model->max_size = node->GetInteger("max_size", Config::kDefaultMaxSize); + std::int64_t max_scale = node->GetSignedInteger("max_scale", Config::kDefaultMaxScale); + if (max_scale < opentelemetry::sdk::metrics::kMaxScaleMin || + max_scale > opentelemetry::sdk::metrics::kMaxScaleMax) + { + std::string message("Illegal max_scale: "); + message.append(std::to_string(max_scale)); + throw InvalidSchemaException(node->Location(), message); + } + model->max_scale = static_cast(max_scale); + + model->max_size = node->GetInteger("max_size", Config::kDefaultMaxSize); + if (model->max_size < opentelemetry::sdk::metrics::kMaxSizeMin) + { + std::string message("Illegal max_size: "); + message.append(std::to_string(model->max_size)); + throw InvalidSchemaException(node->Location(), message); + } + model->record_min_max = node->GetBoolean("record_min_max", Config::kDefaultRecordMinMax); return model; diff --git a/sdk/src/configuration/document_node.cc b/sdk/src/configuration/document_node.cc index f5aafe9f76..61af16de90 100644 --- a/sdk/src/configuration/document_node.cc +++ b/sdk/src/configuration/document_node.cc @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include +#include #include #include "opentelemetry/sdk/common/env_variables.h" @@ -250,6 +252,34 @@ size_t DocumentNode::IntegerFromString(const std::string &value) const return val; } +std::int64_t DocumentNode::SignedIntegerFromString(const std::string &value) const +{ + try + { + std::size_t pos = 0; + std::int64_t val = std::stoll(value, &pos); + if (pos != value.length()) + { + std::string message("Illegal integer value: "); + message.append(value); + throw InvalidSchemaException(Location(), message); + } + return val; + } + catch (const std::invalid_argument &) + { + std::string message("Illegal integer value: "); + message.append(value); + throw InvalidSchemaException(Location(), message); + } + catch (const std::out_of_range &) + { + std::string message("Illegal integer value: "); + message.append(value); + throw InvalidSchemaException(Location(), message); + } +} + double DocumentNode::DoubleFromString(const std::string &value) const { const char *ptr = value.c_str(); diff --git a/sdk/src/configuration/ryml_document_node.cc b/sdk/src/configuration/ryml_document_node.cc index ea5986ee3c..062b3214d1 100644 --- a/sdk/src/configuration/ryml_document_node.cc +++ b/sdk/src/configuration/ryml_document_node.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include @@ -281,6 +282,32 @@ size_t RymlDocumentNode::GetInteger(const std::string &name, size_t default_valu return IntegerFromString(value); } +std::int64_t RymlDocumentNode::GetSignedInteger(const std::string &name, + std::int64_t default_value) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetSignedInteger(" << name << ", " << default_value + << ")"); + + auto ryml_child = GetRymlChildNode(name); + + if (ryml_child.invalid()) + { + return default_value; + } + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + if (value.empty()) + { + return default_value; + } + + return SignedIntegerFromString(value); +} + double RymlDocumentNode::GetRequiredDouble(const std::string &name) const { OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredDouble(" << name << ")"); diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index 7e3a9600fc..3d4494e7a6 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -1619,8 +1619,8 @@ SdkBuilder::CreateBase2ExponentialBucketHistogramAggregation( auto sdk = std::make_unique(); - sdk->max_buckets_ = model->max_size; - sdk->max_scale_ = static_cast(model->max_scale); + sdk->max_size_ = model->max_size; + sdk->max_scale_ = model->max_scale; sdk->record_min_max_ = model->record_min_max; return sdk; diff --git a/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc b/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc index 7909b0a384..4803a4674f 100644 --- a/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc +++ b/sdk/src/metrics/aggregation/base2_exponential_histogram_aggregation.cc @@ -124,8 +124,26 @@ Base2ExponentialHistogramAggregation::Base2ExponentialHistogramAggregation( ac = &default_config; } - point_data_.max_buckets_ = (std::max)(ac->max_buckets_, static_cast(2)); - point_data_.scale_ = ac->max_scale_; + size_t max_size = ac->max_size_; + if (max_size < kMaxSizeMin) + { + OTEL_INTERNAL_LOG_WARN("[Base2ExponentialHistogramAggregation] max_size " + << max_size << " is less than " << kMaxSizeMin << "; using default " + << default_config.max_size_); + max_size = default_config.max_size_; + } + + int32_t max_scale = ac->max_scale_; + if (max_scale < kMaxScaleMin || max_scale > kMaxScaleMax) + { + OTEL_INTERNAL_LOG_WARN("[Base2ExponentialHistogramAggregation] max_scale " + << max_scale << " is out of range [" << kMaxScaleMin << ", " + << kMaxScaleMax << "]; using default " << default_config.max_scale_); + max_scale = default_config.max_scale_; + } + + point_data_.max_buckets_ = max_size; + point_data_.scale_ = max_scale; point_data_.record_min_max_ = ac->record_min_max_; point_data_.min_ = (std::numeric_limits::max)(); point_data_.max_ = (std::numeric_limits::min)(); diff --git a/sdk/test/configuration/yaml_metrics_test.cc b/sdk/test/configuration/yaml_metrics_test.cc index 7272d2fc02..304a7301a0 100644 --- a/sdk/test/configuration/yaml_metrics_test.cc +++ b/sdk/test/configuration/yaml_metrics_test.cc @@ -925,7 +925,7 @@ file_format: "1.0-metrics" stream: aggregation: base2_exponential_bucket_histogram: - max_scale: 40 + max_scale: 10 max_size: 320 record_min_max: false )"; @@ -943,12 +943,108 @@ file_format: "1.0-metrics" auto *base2_exponential_bucket_histogram = reinterpret_cast< opentelemetry::sdk::configuration::Base2ExponentialBucketHistogramAggregationConfiguration *>( aggregation); - ASSERT_EQ(base2_exponential_bucket_histogram->max_scale, 40); + ASSERT_EQ(base2_exponential_bucket_histogram->max_scale, 10); ASSERT_EQ(base2_exponential_bucket_histogram->max_size, 320); ASSERT_EQ(base2_exponential_bucket_histogram->record_min_max, false); ASSERT_EQ(view->stream->attribute_keys, nullptr); } +TEST(YamlMetrics, stream_aggregation_base2_exponential_bucket_histogram_min_values) +{ + std::string yaml = R"( +file_format: "1.0-metrics" +meter_provider: + readers: + - periodic: + exporter: + console: + views: + - selector: + stream: + aggregation: + base2_exponential_bucket_histogram: + max_scale: -10 + max_size: 2 +)"; + + auto config = DoParse(yaml); + ASSERT_NE(config, nullptr); + ASSERT_NE(config->meter_provider, nullptr); + ASSERT_EQ(config->meter_provider->views.size(), 1); + auto *view = config->meter_provider->views[0].get(); + ASSERT_NE(view, nullptr); + ASSERT_NE(view->stream, nullptr); + ASSERT_NE(view->stream->aggregation, nullptr); + auto *base2_exponential_bucket_histogram = reinterpret_cast< + opentelemetry::sdk::configuration::Base2ExponentialBucketHistogramAggregationConfiguration *>( + view->stream->aggregation.get()); + ASSERT_EQ(base2_exponential_bucket_histogram->max_scale, -10); + ASSERT_EQ(base2_exponential_bucket_histogram->max_size, 2); +} + +TEST(YamlMetrics, stream_aggregation_base2_exponential_bucket_histogram_max_scale_too_small) +{ + std::string yaml = R"( +file_format: "1.0-metrics" +meter_provider: + readers: + - periodic: + exporter: + console: + views: + - selector: + stream: + aggregation: + base2_exponential_bucket_histogram: + max_scale: -11 +)"; + + auto config = DoParse(yaml); + ASSERT_EQ(config, nullptr); +} + +TEST(YamlMetrics, stream_aggregation_base2_exponential_bucket_histogram_max_scale_too_large) +{ + std::string yaml = R"( +file_format: "1.0-metrics" +meter_provider: + readers: + - periodic: + exporter: + console: + views: + - selector: + stream: + aggregation: + base2_exponential_bucket_histogram: + max_scale: 21 +)"; + + auto config = DoParse(yaml); + ASSERT_EQ(config, nullptr); +} + +TEST(YamlMetrics, stream_aggregation_base2_exponential_bucket_histogram_max_size_too_small) +{ + std::string yaml = R"( +file_format: "1.0-metrics" +meter_provider: + readers: + - periodic: + exporter: + console: + views: + - selector: + stream: + aggregation: + base2_exponential_bucket_histogram: + max_size: 1 +)"; + + auto config = DoParse(yaml); + ASSERT_EQ(config, nullptr); +} + TEST(YamlMetrics, stream_aggregation_last_value) { std::string yaml = R"( diff --git a/sdk/test/metrics/aggregation_test.cc b/sdk/test/metrics/aggregation_test.cc index e8c7ab0dcb..8cd8a1a4ec 100644 --- a/sdk/test/metrics/aggregation_test.cc +++ b/sdk/test/metrics/aggregation_test.cc @@ -51,11 +51,11 @@ uint64_t SumAllBuckets(const Base2ExponentialHistogramPointData &point) return total; } -Base2ExponentialHistogramAggregationConfig MakeAggregationConfig(int max_scale, size_t max_buckets) +Base2ExponentialHistogramAggregationConfig MakeAggregationConfig(int max_scale, size_t max_size) { Base2ExponentialHistogramAggregationConfig config; - config.max_scale_ = max_scale; - config.max_buckets_ = max_buckets; + config.max_scale_ = max_scale; + config.max_size_ = max_size; return config; } @@ -315,7 +315,7 @@ TEST(Aggregation, Base2ExponentialHistogramAggregation) auto MAX_BUCKETS0 = 7; Base2ExponentialHistogramAggregationConfig scale0_config; scale0_config.max_scale_ = SCALE0; - scale0_config.max_buckets_ = MAX_BUCKETS0; + scale0_config.max_size_ = MAX_BUCKETS0; scale0_config.record_min_max_ = true; Base2ExponentialHistogramAggregation scale0_aggr(&scale0_config); auto point = scale0_aggr.ToPoint(); @@ -388,7 +388,7 @@ TEST(Aggregation, Base2ExponentialHistogramAggregation) Base2ExponentialHistogramAggregationConfig scale1_config; scale1_config.max_scale_ = 1; - scale1_config.max_buckets_ = 14; + scale1_config.max_size_ = 14; scale1_config.record_min_max_ = true; Base2ExponentialHistogramAggregation scale1_aggr(&scale1_config); @@ -441,11 +441,41 @@ TEST(Aggregation, Base2ExponentialHistogramAggregation) EXPECT_EQ(diffd_point.positive_buckets_->Get(2), 1); } +TEST(Aggregation, Base2ExponentialHistogramAggregationInvalidConfigFallsBackToDefault) +{ + const Base2ExponentialHistogramAggregationConfig default_config; + + auto point_of = [](const Base2ExponentialHistogramAggregationConfig &config) { + Base2ExponentialHistogramAggregation aggr(&config); + return nostd::get(aggr.ToPoint()); + }; + + { + auto point = point_of(MakeAggregationConfig(kMaxScaleMax + 1, 100)); + EXPECT_EQ(point.scale_, default_config.max_scale_); + EXPECT_EQ(point.max_buckets_, 100); + } + { + auto point = point_of(MakeAggregationConfig(kMaxScaleMin - 1, 100)); + EXPECT_EQ(point.scale_, default_config.max_scale_); + } + { + auto point = point_of(MakeAggregationConfig(0, kMaxSizeMin - 1)); + EXPECT_EQ(point.max_buckets_, default_config.max_size_); + EXPECT_EQ(point.scale_, 0); + } + { + auto point = point_of(MakeAggregationConfig(kMaxScaleMin, kMaxSizeMin)); + EXPECT_EQ(point.scale_, kMaxScaleMin); + EXPECT_EQ(point.max_buckets_, kMaxSizeMin); + } +} + TEST(Aggregation, Base2ExponentialHistogramAggregationMerge) { Base2ExponentialHistogramAggregationConfig config; config.max_scale_ = 10; - config.max_buckets_ = 100; + config.max_size_ = 100; config.record_min_max_ = true; Base2ExponentialHistogramAggregation aggr(&config); @@ -468,7 +498,7 @@ TEST(Aggregation, Base2ExponentialHistogramAggregationMerge) ASSERT_DOUBLE_EQ(aggr_point.sum_, expected_sum); ASSERT_EQ(aggr_point.zero_count_, 0); ASSERT_GT(aggr_point.scale_, -10); - ASSERT_EQ(aggr_point.max_buckets_, config.max_buckets_); + ASSERT_EQ(aggr_point.max_buckets_, config.max_size_); auto test_merge = [](const std::unique_ptr &merged_aggr, int expected_count, double expected_sum, int expected_zero_count, int expected_scale, @@ -943,7 +973,7 @@ TEST(Aggregation, Base2ExponentialHistogramAggregationDefaultConfigMerge) ExpectCountInvariant(pb.count_, pb, "b"); EXPECT_LE(pa.scale_, 20); EXPECT_LE(pb.scale_, 20); - EXPECT_EQ(pa.max_buckets_, config.max_buckets_); + EXPECT_EQ(pa.max_buckets_, config.max_size_); EXPECT_GT(pa.max_, pa.min_); EXPECT_GT(pb.max_, pb.min_); diff --git a/sdk/test/metrics/histogram_aggregation_test.cc b/sdk/test/metrics/histogram_aggregation_test.cc index 7c74b8b385..23f14f095d 100644 --- a/sdk/test/metrics/histogram_aggregation_test.cc +++ b/sdk/test/metrics/histogram_aggregation_test.cc @@ -195,8 +195,8 @@ std::shared_ptr MakeBase2ExponentialHistogramViewProvider( meter_provider->AddMetricReader(reader); Base2ExponentialHistogramAggregationConfig config; - config.max_scale_ = 5; - config.max_buckets_ = 160; + config.max_scale_ = 5; + config.max_size_ = 160; auto view = std::make_unique("exponential_histogram", "exponential_histogram_description", AggregationType::kBase2ExponentialHistogram, From 99d8b86da9923c892de55baa12903ef4a03b5749 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:54:39 -0400 Subject: [PATCH 56/61] [OTLP EXPORTER] Support setting byte arrays in all attribute collections and reject null keys (#4226) --- CHANGELOG.md | 4 + exporters/otlp/CMakeLists.txt | 4 + .../otlp/otlp_populate_attribute_utils.h | 54 +- exporters/otlp/src/otlp_log_recordable.cc | 16 +- exporters/otlp/src/otlp_metric_utils.cc | 17 +- .../otlp/src/otlp_populate_attribute_utils.cc | 408 ++++++------- exporters/otlp/src/otlp_recordable.cc | 19 +- .../otlp/test/otlp_log_recordable_test.cc | 15 +- .../test/otlp_metrics_serialization_test.cc | 140 +++++ .../otlp_populate_attribute_utils_test.cc | 548 ++++++++++++++++++ exporters/otlp/test/otlp_recordable_test.cc | 81 ++- .../sdk/common/attribute_utils.h | 60 +- sdk/src/logs/multi_recordable.cc | 4 + sdk/src/trace/span.cc | 4 + 14 files changed, 1092 insertions(+), 282 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b98735d43c..899a46701c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,10 @@ Breaking changes: * [CODE HEALTH] Move registry.cc propagator builders into anonymous namespace [#4121](https://github.com/open-telemetry/opentelemetry-cpp/pull/4121) +* [EXPORTER] Spec-compliant fix to allow byte arrays in all attribute + collections and disallow empty attribute keys + [#4226](https://github.com/open-telemetry/opentelemetry-cpp/pull/4226) + * [CODE HEALTH] Move sdk_builder.cc builders into anonymous namespace [#4122](https://github.com/open-telemetry/opentelemetry-cpp/pull/4122) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index eef251a1de..792e25ccf2 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -897,6 +897,10 @@ if(BUILD_TESTING) target_link_libraries( otlp_populate_attribute_utils_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_otlp_recordable) + if(WITH_OTLP_UTF8_VALIDITY AND TARGET utf8_range::utf8_validity) + target_compile_definitions(otlp_populate_attribute_utils_test + PRIVATE ENABLE_OTLP_UTF8_VALIDITY) + endif() gtest_add_tests( TARGET otlp_populate_attribute_utils_test TEST_PREFIX exporter.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 fe4f5ac0e0..5322e5dcca 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 @@ -8,6 +8,7 @@ #include #include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/common/macros.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" @@ -45,8 +46,33 @@ namespace exporter { namespace otlp { + +/** + * AttributeConverterOptions control the behavior of OtlpPopulateAttributeUtils::PopulateAnyValue + * and OtlpPopulateAttributeUtils::PopulateAttribute. + * + * When `attribute_value_length_limit` is less than `std::numeric_limits::max()`, + * string attribute values are truncated to at most `attribute_value_length_limit` bytes using + * UTF-8-safe truncation (Utf8SafePrefixLength) so the resulting protobuf + * `string_value` stays valid UTF-8 when the input was. Byte array attribute values + * (span) are truncated at the raw byte boundary. Non-string + * alternatives are unaffected. + * + */ +struct AttributeConverterOptions +{ + /// Maximum length of string or bytes attribute values in bytes. When the value is longer than + /// this limit, it will be truncated to this limit. The default value is no limit. + std::size_t attribute_value_length_limit{(std::numeric_limits::max)()}; + + AttributeConverterOptions() noexcept = default; + explicit AttributeConverterOptions(std::size_t value_length_limit) noexcept + : attribute_value_length_limit(value_length_limit) + {} +}; + /** - * The OtlpCommoneUtils contains utility functions to populate attributes + * The OtlpPopulateAttributeUtils contains utility functions to populate attributes */ class OtlpPopulateAttributeUtils { @@ -59,41 +85,27 @@ class OtlpPopulateAttributeUtils const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) 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( + static bool 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; + AttributeConverterOptions options = AttributeConverterOptions{}) noexcept; - static void PopulateAnyValue( + static bool 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; + AttributeConverterOptions options = AttributeConverterOptions{}) 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; + AttributeConverterOptions options = AttributeConverterOptions{}) 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; + AttributeConverterOptions options = AttributeConverterOptions{}) noexcept; /** * Byte length of the longest prefix of `value` that fits within `max_bytes` diff --git a/exporters/otlp/src/otlp_log_recordable.cc b/exporters/otlp/src/otlp_log_recordable.cc index dc10abfe41..2697785b29 100644 --- a/exporters/otlp/src/otlp_log_recordable.cc +++ b/exporters/otlp/src/otlp_log_recordable.cc @@ -1,10 +1,12 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#include "opentelemetry/exporters/otlp/otlp_log_recordable.h" #include +#include + #include "opentelemetry/common/attribute_value.h" #include "opentelemetry/common/timestamp.h" +#include "opentelemetry/exporters/otlp/otlp_log_recordable.h" #include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" #include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/span.h" @@ -196,7 +198,7 @@ void OtlpLogRecordable::SetSeverity(opentelemetry::logs::Severity severity) noex void OtlpLogRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept { - OtlpPopulateAttributeUtils::PopulateAnyValue(proto_record_.mutable_body(), message, true); + OtlpPopulateAttributeUtils::PopulateAnyValue(proto_record_.mutable_body(), message); } void OtlpLogRecordable::SetEventId(int64_t /* id */, nostd::string_view event_name) noexcept @@ -238,14 +240,20 @@ void OtlpLogRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &tr void OtlpLogRecordable::SetAttribute(opentelemetry::nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { + if (key.empty()) + { + return; + } 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); + AttributeConverterOptions options; + options.attribute_value_length_limit = limits_.attribute_value_length_limit; + OtlpPopulateAttributeUtils::PopulateAttribute(proto_record_.add_attributes(), key, value, + options); } void OtlpLogRecordable::SetLogRecordLimits( diff --git a/exporters/otlp/src/otlp_metric_utils.cc b/exporters/otlp/src/otlp_metric_utils.cc index e57359ee48..e5f9ca5a60 100644 --- a/exporters/otlp/src/otlp_metric_utils.cc +++ b/exporters/otlp/src/otlp_metric_utils.cc @@ -92,7 +92,8 @@ void OtlpMetricUtils::ConvertSumMetric(const metric_sdk::MetricData &metric_data proto::metrics::v1::NumberDataPoint *proto_sum_point_data = sum->add_data_points(); proto_sum_point_data->set_start_time_unix_nano(start_ts); proto_sum_point_data->set_time_unix_nano(ts); - auto sum_data = nostd::get(point_data_with_attributes.point_data); + const auto &sum_data = + nostd::get(point_data_with_attributes.point_data); if ((nostd::holds_alternative(sum_data.value_))) { @@ -106,7 +107,7 @@ void OtlpMetricUtils::ConvertSumMetric(const metric_sdk::MetricData &metric_data for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_sum_point_data->add_attributes(), - kv_attr.first, kv_attr.second, false); + kv_attr.first, kv_attr.second); } } } @@ -125,7 +126,7 @@ void OtlpMetricUtils::ConvertHistogramMetric( histogram->add_data_points(); proto_histogram_point_data->set_start_time_unix_nano(start_ts); proto_histogram_point_data->set_time_unix_nano(ts); - auto histogram_data = + const auto &histogram_data = nostd::get(point_data_with_attributes.point_data); // sum if ((nostd::holds_alternative(histogram_data.sum_))) @@ -178,7 +179,7 @@ void OtlpMetricUtils::ConvertHistogramMetric( for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), - kv_attr.first, kv_attr.second, false); + kv_attr.first, kv_attr.second); } } } @@ -197,7 +198,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( histogram->add_data_points(); proto_histogram_point_data->set_start_time_unix_nano(start_ts); proto_histogram_point_data->set_time_unix_nano(ts); - auto histogram_data = nostd::get( + const auto &histogram_data = nostd::get( point_data_with_attributes.point_data); if (histogram_data.positive_buckets_ == nullptr && histogram_data.negative_buckets_ == nullptr) { @@ -242,7 +243,7 @@ void OtlpMetricUtils::ConvertExponentialHistogramMetric( for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_histogram_point_data->add_attributes(), - kv_attr.first, kv_attr.second, false); + kv_attr.first, kv_attr.second); } } } @@ -257,7 +258,7 @@ void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::Metr proto::metrics::v1::NumberDataPoint *proto_gauge_point_data = gauge->add_data_points(); proto_gauge_point_data->set_start_time_unix_nano(start_ts); proto_gauge_point_data->set_time_unix_nano(ts); - auto gauge_data = + const auto &gauge_data = nostd::get(point_data_with_attributes.point_data); if ((nostd::holds_alternative(gauge_data.value_))) @@ -272,7 +273,7 @@ void OtlpMetricUtils::ConvertGaugeMetric(const opentelemetry::sdk::metrics::Metr for (auto &kv_attr : point_data_with_attributes.attributes) { OtlpPopulateAttributeUtils::PopulateAttribute(proto_gauge_point_data->add_attributes(), - kv_attr.first, kv_attr.second, false); + kv_attr.first, kv_attr.second); } } } diff --git a/exporters/otlp/src/otlp_populate_attribute_utils.cc b/exporters/otlp/src/otlp_populate_attribute_utils.cc index 943121e570..cbf9927c3e 100644 --- a/exporters/otlp/src/otlp_populate_attribute_utils.cc +++ b/exporters/otlp/src/otlp_populate_attribute_utils.cc @@ -5,11 +5,13 @@ # include #endif -#include #include +#include #include #include +#include #include +#include #include #include @@ -19,12 +21,15 @@ #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/version.h" // clang-format off #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep +#include +// IWYU pragma: no_include "net/proto2/public/repeated_field.h" #include "opentelemetry/proto/common/v1/common.pb.h" #include "opentelemetry/proto/resource/v1/resource.pb.h" #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep @@ -36,180 +41,129 @@ namespace exporter namespace otlp { +namespace +{ + // // See `attribute_value.h` for details. // -const int kAttributeValueSize = 16; -const int kOwnedAttributeValueSize = 15; +constexpr std::size_t kAttributeValueSize = 16; +constexpr std::size_t kOwnedAttributeValueSize = 15; -namespace -{ -// Per OpenTelemetry spec, uint64_t attribute values exceeding INT64_MAX must be +// Per OpenTelemetry spec, std::uint64_t attribute values exceeding INT64_MAX must be // encoded as a decimal string rather than wrapping to a negative int64 via narrowing. // https://opentelemetry.io/docs/specs/otel/common/attribute-type-mapping/#integer-values -inline void SetUint64Value(opentelemetry::proto::common::v1::AnyValue *proto_value, uint64_t val) +inline void SetUint64Value(opentelemetry::proto::common::v1::AnyValue *proto_value, + std::uint64_t val) { - if (val <= static_cast(std::numeric_limits::max())) + if (val <= static_cast(std::numeric_limits::max())) { - proto_value->set_int_value(static_cast(val)); + proto_value->set_int_value(static_cast(val)); } else { proto_value->set_string_value(std::to_string(val)); } } -} // namespace -void OtlpPopulateAttributeUtils::PopulateAnyValue( - opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::common::AttributeValue &value, - bool allow_bytes, - std::size_t max_length) noexcept -{ - if (nullptr == proto_value) - { - return; - } +template +struct IsSupportedAttributeValue : std::false_type +{}; - // Assert size of variant to ensure that this method gets updated if the variant - // definition changes - static_assert( - nostd::variant_size::value == kAttributeValueSize, - "AttributeValue contains unknown type"); +struct AttributeValueVisitor +{ + opentelemetry::proto::common::v1::AnyValue *proto_value_; + AttributeConverterOptions options; - if (nostd::holds_alternative(value)) - { - proto_value->set_bool_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) + template + void operator()(T &&) const { - proto_value->set_int_value(nostd::get(value)); + static_assert( + IsSupportedAttributeValue::value, + "AttributeValueVisitor: Value type in opentelemetry::common::AttributeValue does not have " + "an overload operator implemented in this visitor OR implicit conversion attempted!"); } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - SetUint64Value(proto_value, nostd::get(value)); - } - else if (nostd::holds_alternative(value)) + + void operator()(bool value) const { proto_value_->set_bool_value(value); } + void operator()(std::int32_t value) const { proto_value_->set_int_value(value); } + void operator()(std::int64_t value) const { proto_value_->set_int_value(value); } + void operator()(std::uint32_t value) const { proto_value_->set_int_value(value); } + void operator()(std::uint64_t value) const { SetUint64Value(proto_value_, value); } + void operator()(double value) const { proto_value_->set_double_value(value); } + void operator()(const char *value) const { - proto_value->set_double_value(nostd::get(value)); + operator()(nostd::string_view{value, std::strlen(value)}); } - else if (nostd::holds_alternative(value)) + void operator()(nostd::string_view value) const { - 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); + const std::size_t kept_len = OtlpPopulateAttributeUtils::Utf8SafePrefixLength( + value.data(), value.size(), options.attribute_value_length_limit); #if defined(ENABLE_OTLP_UTF8_VALIDITY) - // 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})) + if (utf8_range::IsStructurallyValid({value.data(), kept_len})) { - proto_value->set_string_value(str_value, kept_len); + proto_value_->set_string_value(value.data(), kept_len); } else { - proto_value->set_bytes_value(str_value, kept_len); + proto_value_->set_bytes_value(value.data(), kept_len); } #else - proto_value->set_string_value(str_value, kept_len); + proto_value_->set_string_value(value.data(), kept_len); #endif } - else if (nostd::holds_alternative(value)) + void operator()(nostd::span values) const { - 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(), kept_len); - } - else - { - proto_value->set_bytes_value(str_value.data(), kept_len); - } -#else - 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) - { - 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 : bytes) - { - array_value->add_values()->set_int_value(val); - } - } - } - else if (nostd::holds_alternative>(value)) - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const bool val : values) { array_value->add_values()->set_bool_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::int32_t val : values) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::int64_t val : values) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::uint32_t val : values) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) - { - SetUint64Value(array_value->add_values(), val); - } - } - else if (nostd::holds_alternative>(value)) - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const double val : values) { array_value->add_values()->set_double_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(nostd::span values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const nostd::string_view val : values) { - const std::size_t kept_len = Utf8SafePrefixLength(val.data(), val.size(), max_length); + const std::size_t kept_len = OtlpPopulateAttributeUtils::Utf8SafePrefixLength( + val.data(), val.size(), options.attribute_value_length_limit); #if defined(ENABLE_OTLP_UTF8_VALIDITY) if (utf8_range::IsStructurallyValid({val.data(), val.size()})) { @@ -224,140 +178,114 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( #endif } } -} - -void OtlpPopulateAttributeUtils::PopulateAnyValue( - opentelemetry::proto::common::v1::AnyValue *proto_value, - const opentelemetry::sdk::common::OwnedAttributeValue &value, - bool allow_bytes, - std::size_t max_length) noexcept -{ - if (nullptr == proto_value) + void operator()(nostd::span values) const { - return; - } - - // Assert size of variant to ensure that this method gets updated if the variant - // definition changes - static_assert(nostd::variant_size::value == - kOwnedAttributeValueSize, - "OwnedAttributeValue contains unknown type"); - - if (nostd::holds_alternative(value)) - { - proto_value->set_bool_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - proto_value->set_int_value(nostd::get(value)); - } - else if (nostd::holds_alternative(value)) - { - SetUint64Value(proto_value, nostd::get(value)); + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::uint64_t val : values) + { + SetUint64Value(array_value->add_values(), val); + } } - else if (nostd::holds_alternative(value)) + void operator()(nostd::span values) const { - proto_value->set_double_value(nostd::get(value)); + const std::size_t kept_len = (std::min)(values.size(), options.attribute_value_length_limit); + proto_value_->set_bytes_value(reinterpret_cast(values.data()), kept_len); } - else if (nostd::holds_alternative>(value)) +}; + +struct OwnedAttributeValueVisitor +{ + opentelemetry::proto::common::v1::AnyValue *proto_value_; + AttributeConverterOptions options; + + template + void operator()(T &&) const { - if (allow_bytes) - { - const std::vector &byte_array = nostd::get>(value); - 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 - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) - { - array_value->add_values()->set_int_value(val); - } - } + static_assert(IsSupportedAttributeValue::value, + "OwnedAttributeValueVisitor: Value type in " + "opentelemetry::sdk::common::OwnedAttributeValue does not have an overload " + "operator implemented in this visitor OR implicit conversion attempted!"); } - 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); + + void operator()(bool value) const { proto_value_->set_bool_value(value); } + void operator()(std::int32_t value) const { proto_value_->set_int_value(value); } + void operator()(std::uint32_t value) const { proto_value_->set_int_value(value); } + void operator()(std::int64_t value) const { proto_value_->set_int_value(value); } + void operator()(std::uint64_t value) const { SetUint64Value(proto_value_, value); } + void operator()(double value) const { proto_value_->set_double_value(value); } + void operator()(const std::string &value) const + { + const std::size_t kept_len = OtlpPopulateAttributeUtils::Utf8SafePrefixLength( + value.data(), value.size(), options.attribute_value_length_limit); #if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid(str_value)) + if (utf8_range::IsStructurallyValid({value.data(), kept_len})) { - proto_value->set_string_value(str_value.data(), kept_len); + proto_value_->set_string_value(value.data(), kept_len); } else { - proto_value->set_bytes_value(str_value.data(), kept_len); + proto_value_->set_bytes_value(value.data(), kept_len); } #else - proto_value->set_string_value(str_value.data(), kept_len); + proto_value_->set_string_value(value.data(), kept_len); #endif } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const bool val : values) { array_value->add_values()->set_bool_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::int32_t val : values) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) - { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) - { - array_value->add_values()->set_int_value( - val); // NOLINT(cppcoreguidelines-narrowing-conversions) - } - } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::uint32_t val : values) { array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::int64_t val : values) { - SetUint64Value(array_value->add_values(), val); + array_value->add_values()->set_int_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const double val : values) { array_value->add_values()->set_double_value(val); } } - else if (nostd::holds_alternative>(value)) + void operator()(const std::vector &values) const { - auto array_value = proto_value->mutable_array_value(); - for (const auto &val : nostd::get>(value)) + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::string &val : values) { - const std::size_t kept_len = Utf8SafePrefixLength(val, max_length); + const std::size_t kept_len = OtlpPopulateAttributeUtils::Utf8SafePrefixLength( + val, options.attribute_value_length_limit); #if defined(ENABLE_OTLP_UTF8_VALIDITY) - if (utf8_range::IsStructurallyValid(val)) + if (utf8_range::IsStructurallyValid({val.data(), kept_len})) { array_value->add_values()->set_string_value(val.data(), kept_len); } @@ -370,14 +298,49 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue( #endif } } + void operator()(const std::vector &values) const + { + opentelemetry::proto::common::v1::ArrayValue *array_value = proto_value_->mutable_array_value(); + array_value->mutable_values()->Reserve(static_cast(values.size())); + for (const std::uint64_t val : values) + { + SetUint64Value(array_value->add_values(), val); + } + } + void operator()(const std::vector &values) const + { + const std::size_t kept_len = (std::min)(values.size(), options.attribute_value_length_limit); + proto_value_->set_bytes_value(reinterpret_cast(values.data()), kept_len); + } +}; + +} // namespace + +bool OtlpPopulateAttributeUtils::PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::common::AttributeValue &value, + AttributeConverterOptions options) noexcept +{ + auto result = + opentelemetry::sdk::common::VisitVariant(AttributeValueVisitor{proto_value, options}, value); + return result.second; +} + +bool OtlpPopulateAttributeUtils::PopulateAnyValue( + opentelemetry::proto::common::v1::AnyValue *proto_value, + const opentelemetry::sdk::common::OwnedAttributeValue &value, + AttributeConverterOptions options) noexcept +{ + auto result = opentelemetry::sdk::common::VisitVariant( + OwnedAttributeValueVisitor{proto_value, options}, value); + return result.second; } void OtlpPopulateAttributeUtils::PopulateAttribute( opentelemetry::proto::common::v1::KeyValue *attribute, nostd::string_view key, const opentelemetry::common::AttributeValue &value, - bool allow_bytes, - std::size_t max_length) noexcept + AttributeConverterOptions options) noexcept { if (nullptr == attribute) { @@ -391,7 +354,13 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "AttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value, allow_bytes, max_length); + bool result = PopulateAnyValue(attribute->mutable_value(), value, options); + if (!result) + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] PopulateAnyValue from AttributeValue failed for key: " + << std::string(key)); + } } /** Maps from C++ attribute into OTLP proto attribute. */ @@ -399,8 +368,7 @@ void OtlpPopulateAttributeUtils::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) noexcept + AttributeConverterOptions options) noexcept { if (nullptr == attribute) { @@ -414,7 +382,13 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( "OwnedAttributeValue contains unknown type"); attribute->set_key(key.data(), key.size()); - PopulateAnyValue(attribute->mutable_value(), value, allow_bytes, max_length); + bool result = PopulateAnyValue(attribute->mutable_value(), value, options); + if (!result) + { + OTEL_INTERNAL_LOG_ERROR( + "[OTLP Populate Attribute] PopulateAnyValue from OwnedAttributeValue failed for key: " + << std::string(key)); + } } void OtlpPopulateAttributeUtils::PopulateAttribute( @@ -429,7 +403,7 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( for (const auto &kv : resource.GetAttributes()) { OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second, - false); + AttributeConverterOptions{}); } } @@ -441,7 +415,7 @@ void OtlpPopulateAttributeUtils::PopulateAttribute( for (const auto &kv : instrumentation_scope.GetAttributes()) { OtlpPopulateAttributeUtils::PopulateAttribute(proto->add_attributes(), kv.first, kv.second, - false); + AttributeConverterOptions{}); } } diff --git a/exporters/otlp/src/otlp_recordable.cc b/exporters/otlp/src/otlp_recordable.cc index 017fc51905..8eb8b029e9 100644 --- a/exporters/otlp/src/otlp_recordable.cc +++ b/exporters/otlp/src/otlp_recordable.cc @@ -134,6 +134,11 @@ void OtlpRecordable::SetSpanLimits(const opentelemetry::sdk::trace::SpanLimits & void OtlpRecordable::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept { + if (key.empty()) + { + return; + } + if (static_cast(span_.attributes_size()) >= span_limits_.attribute_count_limit) { span_.set_dropped_attributes_count(span_.dropped_attributes_count() + 1); @@ -141,8 +146,8 @@ void OtlpRecordable::SetAttribute(nostd::string_view key, } auto *attribute = span_.add_attributes(); - OtlpPopulateAttributeUtils::PopulateAttribute(attribute, key, value, false, - span_limits_.attribute_value_length_limit); + OtlpPopulateAttributeUtils::PopulateAttribute( + attribute, key, value, AttributeConverterOptions{span_limits_.attribute_value_length_limit}); } void OtlpRecordable::AddEvent(nostd::string_view name, @@ -166,8 +171,9 @@ void OtlpRecordable::AddEvent(nostd::string_view name, event->set_dropped_attributes_count(event->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(event->add_attributes(), key, value, false, - span_limits_.attribute_value_length_limit); + OtlpPopulateAttributeUtils::PopulateAttribute( + event->add_attributes(), key, value, + AttributeConverterOptions{span_limits_.attribute_value_length_limit}); return true; }); } @@ -193,8 +199,9 @@ void OtlpRecordable::AddLink(const trace::SpanContext &span_context, link->set_dropped_attributes_count(link->dropped_attributes_count() + 1); return true; } - OtlpPopulateAttributeUtils::PopulateAttribute(link->add_attributes(), key, value, false, - span_limits_.attribute_value_length_limit); + OtlpPopulateAttributeUtils::PopulateAttribute( + link->add_attributes(), key, value, + AttributeConverterOptions{span_limits_.attribute_value_length_limit}); return true; }); } diff --git a/exporters/otlp/test/otlp_log_recordable_test.cc b/exporters/otlp/test/otlp_log_recordable_test.cc index 3801afe043..af2f4c506a 100644 --- a/exporters/otlp/test/otlp_log_recordable_test.cc +++ b/exporters/otlp/test/otlp_log_recordable_test.cc @@ -548,8 +548,19 @@ TEST(OtlpLogRecordable, DefaultRecordAppliesNoLimitUntilConfigured) { 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); + const auto &proto_log_record = rec.log_record(); + EXPECT_EQ(proto_log_record.attributes_size(), 200); + EXPECT_EQ(proto_log_record.dropped_attributes_count(), 0u); +} + +// Test that an empty key is rejected and not stored. +TEST(OtlpLogRecordable, SetAttributeEmptyKeyIsRejected) +{ + OtlpLogRecordable rec; + rec.SetAttribute("", opentelemetry::common::AttributeValue{static_cast(1)}); + + const auto &proto_log_record = rec.log_record(); + EXPECT_EQ(proto_log_record.attributes_size(), 0); } } // namespace otlp diff --git a/exporters/otlp/test/otlp_metrics_serialization_test.cc b/exporters/otlp/test/otlp_metrics_serialization_test.cc index 5e8c677b41..cfe6199aab 100644 --- a/exporters/otlp/test/otlp_metrics_serialization_test.cc +++ b/exporters/otlp/test/otlp_metrics_serialization_test.cc @@ -2,13 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include +#include +#include #include #include #include #include "opentelemetry/common/timestamp.h" #include "opentelemetry/exporters/otlp/otlp_metric_utils.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" @@ -22,6 +27,7 @@ // clang-format off #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep +#include #include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" #include "opentelemetry/proto/common/v1/common.pb.h" #include "opentelemetry/proto/metrics/v1/metrics.pb.h" @@ -452,6 +458,140 @@ TEST(OtlpMetricSerializationTest, PopulateExportMetricsServiceRequest) EXPECT_EQ("scope_value", scope_attributes_proto.value().string_value()); } +TEST(OtlpMetricSerializationTest, AttributeTypes) +{ + metrics_sdk::MetricData data; + data.start_ts = opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now()); + data.end_ts = data.start_ts; + data.aggregation_temporality = metrics_sdk::AggregationTemporality::kCumulative; + data.instrument_descriptor = {"TypesCounter", "desc", "unit", + metrics_sdk::InstrumentType::kCounter, + metrics_sdk::InstrumentValueType::kDouble}; + + metrics_sdk::SumPointData point; + point.value_ = 1.0; + + const std::array byte_values = {0xDE, 0xAD, 0xBE, 0xEF}; + const std::array bool_values = {true, false, true}; + const std::array int32_values = {-1, 0, 1}; + const std::array int64_values = {-100, 200}; + const std::array uint32_values = {10u, 20u}; + const std::array uint64_values = {300u, 400u}; + const std::array double_values = {1.1, 2.2}; + const std::array string_values = {"foo", "bar"}; + + namespace nostd = opentelemetry::nostd; + + metrics_sdk::PointDataAttributes point_data_attributes; + point_data_attributes.point_data = point; + point_data_attributes.attributes = { + {"bool_attr", bool{true}}, + {"int32_attr", int32_t{-7}}, + {"int64_attr", int64_t{-99}}, + {"uint32_attr", uint32_t{42u}}, + {"uint64_attr", uint64_t{1000u}}, + {"double_attr", 3.14}, + {"string_attr", "hello"}, + {"bytes_attr", nostd::span{byte_values.data(), byte_values.size()}}, + {"bool_array_attr", nostd::span{bool_values.data(), bool_values.size()}}, + {"int32_array_attr", nostd::span{int32_values.data(), int32_values.size()}}, + {"int64_array_attr", nostd::span{int64_values.data(), int64_values.size()}}, + {"uint32_array_attr", + nostd::span{uint32_values.data(), uint32_values.size()}}, + {"uint64_array_attr", + nostd::span{uint64_values.data(), uint64_values.size()}}, + {"double_array_attr", nostd::span{double_values.data(), double_values.size()}}, + {"string_array_attr", + nostd::span{string_values.data(), string_values.size()}}, + }; + data.point_data_attr_ = {point_data_attributes}; + + const auto resource = resource::Resource::Create({{"service.name", "svc"}}); + const auto scope = + opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create("lib", "1"); + + metrics_sdk::ScopeMetrics scope_metrics{scope.get(), std::move(data)}; + metrics_sdk::ResourceMetrics resource_metrics{&resource, scope_metrics}; + + proto::collector::metrics::v1::ExportMetricsServiceRequest request; + otlp_exporter::OtlpMetricUtils::PopulateRequest(resource_metrics, &request); + + ASSERT_EQ(1, request.resource_metrics_size()); + ASSERT_EQ(1, request.resource_metrics(0).scope_metrics_size()); + ASSERT_EQ(1, request.resource_metrics(0).scope_metrics(0).metrics_size()); + const auto &data_point = + request.resource_metrics(0).scope_metrics(0).metrics(0).sum().data_points(0); + + std::map attributes; + for (const auto &kv : data_point.attributes()) + { + attributes[kv.key()] = kv.value(); + } + + ASSERT_EQ(attributes.size(), 15u); + + EXPECT_TRUE(attributes.at("bool_attr").bool_value()); + EXPECT_EQ(attributes.at("int32_attr").int_value(), -7); + EXPECT_EQ(attributes.at("int64_attr").int_value(), -99); + EXPECT_EQ(attributes.at("uint32_attr").int_value(), 42); + EXPECT_EQ(attributes.at("uint64_attr").int_value(), 1000); + EXPECT_DOUBLE_EQ(attributes.at("double_attr").double_value(), 3.14); + EXPECT_EQ(attributes.at("string_attr").string_value(), "hello"); + + { + const std::string &bytes = attributes.at("bytes_attr").bytes_value(); + ASSERT_EQ(bytes.size(), 4u); + EXPECT_EQ(static_cast(bytes[0]), byte_values[0]); + EXPECT_EQ(static_cast(bytes[1]), byte_values[1]); + EXPECT_EQ(static_cast(bytes[2]), byte_values[2]); + EXPECT_EQ(static_cast(bytes[3]), byte_values[3]); + } + { + const auto &array_value = attributes.at("bool_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 3); + EXPECT_EQ(array_value.values(0).bool_value(), bool_values[0]); + EXPECT_EQ(array_value.values(1).bool_value(), bool_values[1]); + EXPECT_EQ(array_value.values(2).bool_value(), bool_values[2]); + } + { + const auto &array_value = attributes.at("int32_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 3); + EXPECT_EQ(array_value.values(0).int_value(), int32_values[0]); + EXPECT_EQ(array_value.values(1).int_value(), int32_values[1]); + EXPECT_EQ(array_value.values(2).int_value(), int32_values[2]); + } + { + const auto &array_value = attributes.at("int64_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 2); + EXPECT_EQ(array_value.values(0).int_value(), int64_values[0]); + EXPECT_EQ(array_value.values(1).int_value(), int64_values[1]); + } + { + const auto &array_value = attributes.at("uint32_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 2); + EXPECT_EQ(array_value.values(0).int_value(), uint32_values[0]); + EXPECT_EQ(array_value.values(1).int_value(), uint32_values[1]); + } + { + const auto &array_value = attributes.at("uint64_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 2); + EXPECT_EQ(array_value.values(0).int_value(), static_cast(uint64_values[0])); + EXPECT_EQ(array_value.values(1).int_value(), static_cast(uint64_values[1])); + } + { + const auto &array_value = attributes.at("double_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 2); + EXPECT_DOUBLE_EQ(array_value.values(0).double_value(), double_values[0]); + EXPECT_DOUBLE_EQ(array_value.values(1).double_value(), double_values[1]); + } + { + const auto &array_value = attributes.at("string_array_attr").array_value(); + ASSERT_EQ(array_value.values_size(), 2); + EXPECT_EQ(array_value.values(0).string_value(), string_values[0]); + EXPECT_EQ(array_value.values(1).string_value(), string_values[1]); + } +} + } // 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 index ed1f5f5adb..1a8158b16b 100644 --- a/exporters/otlp/test/otlp_populate_attribute_utils_test.cc +++ b/exporters/otlp/test/otlp_populate_attribute_utils_test.cc @@ -2,11 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include +#include +#include #include +#include +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/utility.h" +#include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/version.h" +// clang-format off +#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep +#include "opentelemetry/proto/common/v1/common.pb.h" +#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep +// clang-format on + OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { @@ -71,6 +86,539 @@ TEST(Utf8SafePrefixLength, TruncatedTailLeadByteFallsBack) EXPECT_EQ(OtlpPopulateAttributeUtils::Utf8SafePrefixLength("ab\xC3", 10), 3u); } +// --------------------------------------------------------------------------- +// OtlpPopulateAttributeUtils::PopulateAnyValue with common::AttributeValue +// --------------------------------------------------------------------------- + +namespace +{ +class PopulateAnyValueTest : public ::testing::Test +{ +protected: + opentelemetry::proto::common::v1::AnyValue proto_anyvalue_; +}; +} // namespace + +TEST_F(PopulateAnyValueTest, AnyValueBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, common::AttributeValue{true}); + EXPECT_TRUE(proto_anyvalue_.bool_value()); +} + +TEST_F(PopulateAnyValueTest, AnyValueInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + common::AttributeValue{int32_t{42}}); + EXPECT_EQ(proto_anyvalue_.int_value(), 42); +} + +TEST_F(PopulateAnyValueTest, AnyValueInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + common::AttributeValue{int64_t{-1}}); + EXPECT_EQ(proto_anyvalue_.int_value(), -1); +} + +TEST_F(PopulateAnyValueTest, AnyValueUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + common::AttributeValue{uint32_t{100}}); + EXPECT_EQ(proto_anyvalue_.int_value(), 100); +} + +TEST_F(PopulateAnyValueTest, AnyValueDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, common::AttributeValue{3.14}); + EXPECT_DOUBLE_EQ(proto_anyvalue_.double_value(), 3.14); +} + +TEST_F(PopulateAnyValueTest, AnyValueCString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, common::AttributeValue{"test123"}); + EXPECT_EQ(proto_anyvalue_.string_value(), "test123"); +} + +TEST_F(PopulateAnyValueTest, AnyValueStringView) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::string_view{"test123"}}); + EXPECT_EQ(proto_anyvalue_.string_value(), "test123"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, AnyValueCStringInvalidUtf8) +{ + // Bare continuation bytes are structurally invalid UTF-8. + const std::array invalid_utf8 = {'\x80', '\x81', '\x82', '\x83', '\0'}; + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + common::AttributeValue{invalid_utf8.data()}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} + +TEST_F(PopulateAnyValueTest, AnyValueStringViewInvalidUtf8) +{ + // Bare continuation bytes are structurally invalid UTF-8. + const std::array invalid_utf8 = {'\x80', '\x81', '\x82', '\x83'}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + common::AttributeValue{nostd::string_view{invalid_utf8.data(), invalid_utf8.size()}}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// uint64_t values within int64_t range map to int_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueUint64InRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + common::AttributeValue{static_cast(std::numeric_limits::max())}); + EXPECT_EQ(proto_anyvalue_.int_value(), std::numeric_limits::max()); +} + +// uint64_t values above INT64_MAX map to string_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueUint64OutOfRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{std::numeric_limits::max()}); + EXPECT_EQ(proto_anyvalue_.string_value(), std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint8) +{ + const std::array data = {0x01, 0x02, 0x03}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\x01\x02\x03", 3)); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanBool) +{ + const std::array data = {true, false, true}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_TRUE(proto_anyvalue_.array_value().values(0).bool_value()); + EXPECT_FALSE(proto_anyvalue_.array_value().values(1).bool_value()); + EXPECT_TRUE(proto_anyvalue_.array_value().values(2).bool_value()); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanInt32) +{ + const std::array data = {10, -20, 30}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 10); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), -20); + EXPECT_EQ(proto_anyvalue_.array_value().values(2).int_value(), 30); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanInt64) +{ + const std::array data = {-1000, 2000}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), -1000); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 2000); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint32) +{ + const std::array data = {100, 200}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanUint64) +{ + const std::array data = {100, 200, 300}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 200); + EXPECT_EQ(proto_anyvalue_.array_value().values(2).int_value(), 300); +} + +// uint64_t values above INT64_MAX in a span map to string_value per the `Mapping Arbitrary Data +// to OTLP AnyValue` specification. +TEST_F(PopulateAnyValueTest, AnyValueSpanUint64Overflow) +{ + const std::array data = {static_cast(std::numeric_limits::max()), + std::numeric_limits::max()}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), + std::numeric_limits::max()); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), + std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanDouble) +{ + const std::array data = {1.5, 2.5}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_DOUBLE_EQ(proto_anyvalue_.array_value().values(0).double_value(), 1.5); + EXPECT_DOUBLE_EQ(proto_anyvalue_.array_value().values(1).double_value(), 2.5); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanStringView) +{ + const std::array data = {"foo", "bar"}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "foo"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), "bar"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, AnyValueSpanStringViewInvalidUtf8) +{ + // Mix of valid and invalid UTF-8 entries. + const std::array invalid_utf8 = {'\x80', '\x81', '\x82', '\x83'}; + const std::array data = { + "valid", nostd::string_view{invalid_utf8.data(), invalid_utf8.size()}}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "valid"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).bytes_value(), + std::string("\x80\x81\x82\x83", 4)); +} + +TEST_F(PopulateAnyValueTest, AnyValueSpanStringViewInvalidUtf8TruncatedAtMaxLength) +{ + // Invalid UTF-8 string that is also longer than max_length. + // Must store the truncated string as a byte array. + const std::array invalid_utf8 = {'\x80', '\x81', '\x82', '\x83', '\x84', '\x85'}; + const std::array data = { + nostd::string_view{invalid_utf8.data(), invalid_utf8.size()}}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}, + AttributeConverterOptions{3}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 1); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).bytes_value(), std::string("\x80\x81\x82", 3)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// --------------------------------------------------------------------------- +// OtlpPopulateAttributeUtils::PopulateAnyValue with sdk::common::OwnedAttributeValue +// --------------------------------------------------------------------------- + +TEST_F(PopulateAnyValueTest, OwnedAnyValueBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{false}); + EXPECT_FALSE(proto_anyvalue_.bool_value()); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{int32_t{7}}); + EXPECT_EQ(proto_anyvalue_.int_value(), 7); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{uint32_t{200}}); + EXPECT_EQ(proto_anyvalue_.int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{int64_t{-99}}); + EXPECT_EQ(proto_anyvalue_.int_value(), -99); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{2.71}); + EXPECT_DOUBLE_EQ(proto_anyvalue_.double_value(), 2.71); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::string{"owned"}}); + EXPECT_EQ(proto_anyvalue_.string_value(), "owned"); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint8) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{0xAA, 0xBB}}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\xAA\xBB", 2)); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint8TruncatedAtMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + sdk::common::OwnedAttributeValue{std::vector{0x01, 0x02, 0x03, 0x04, 0x05}}, + AttributeConverterOptions{3}); + ASSERT_EQ(proto_anyvalue_.bytes_value().size(), 3u); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[0]), 0x01u); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[1]), 0x02u); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[2]), 0x03u); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorBool) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{true, false, true}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_TRUE(proto_anyvalue_.array_value().values(0).bool_value()); + EXPECT_FALSE(proto_anyvalue_.array_value().values(1).bool_value()); + EXPECT_TRUE(proto_anyvalue_.array_value().values(2).bool_value()); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorInt32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{10, -20, 30}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 10); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), -20); + EXPECT_EQ(proto_anyvalue_.array_value().values(2).int_value(), 30); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint32) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{100, 200}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 200); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorInt64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{-1000, 2000}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), -1000); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 2000); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorDouble) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{1.1, 2.2, 3.3}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_DOUBLE_EQ(proto_anyvalue_.array_value().values(0).double_value(), 1.1); + EXPECT_DOUBLE_EQ(proto_anyvalue_.array_value().values(1).double_value(), 2.2); + EXPECT_DOUBLE_EQ(proto_anyvalue_.array_value().values(2).double_value(), 3.3); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorString) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + sdk::common::OwnedAttributeValue{std::vector{"one", "two", "three"}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "one"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), "two"); + EXPECT_EQ(proto_anyvalue_.array_value().values(2).string_value(), "three"); +} + +#if defined(ENABLE_OTLP_UTF8_VALIDITY) +TEST_F(PopulateAnyValueTest, OwnedAnyValueStringInvalidUtf8) +{ + // Bare continuation bytes are structurally invalid UTF-8. + const std::string invalid_utf8("\x80\x81\x82\x83", 4); + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{invalid_utf8}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\x80\x81\x82\x83", 4)); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueStringInvalidUtf8TruncatedAtMaxLength) +{ + // Invalid UTF-8 string that is also longer than max_length + const std::string invalid_utf8("\x80\x81\x82\x83\x84\x85", 6); + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{invalid_utf8}, + AttributeConverterOptions{3}); + EXPECT_EQ(proto_anyvalue_.bytes_value(), std::string("\x80\x81\x82", 3)); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorStringInvalidUtf8) +{ + // Mix of valid and invalid UTF-8 entries. + const std::string invalid_utf8("\x80\x81\x82\x83", 4); + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + sdk::common::OwnedAttributeValue{std::vector{"valid", invalid_utf8}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "valid"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).bytes_value(), + std::string("\x80\x81\x82\x83", 4)); +} +#endif // defined(ENABLE_OTLP_UTF8_VALIDITY) + +// uint64_t values within int64_t range map to int_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint64InRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + sdk::common::OwnedAttributeValue{static_cast(std::numeric_limits::max())}); + EXPECT_EQ(proto_anyvalue_.int_value(), std::numeric_limits::max()); +} + +// uint64_t values above INT64_MAX map to string_value per the `Mapping Arbitrary Data to OTLP +// AnyValue` specification. +TEST_F(PopulateAnyValueTest, OwnedAnyValueUint64OutOfRange) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::numeric_limits::max()}); + EXPECT_EQ(proto_anyvalue_.string_value(), std::to_string(std::numeric_limits::max())); +} + +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint64) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::vector{100, 200, 300}}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 3); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), 100); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).int_value(), 200); + EXPECT_EQ(proto_anyvalue_.array_value().values(2).int_value(), 300); +} + +// uint64_t values above INT64_MAX in a vector map to string_value per the `Mapping Arbitrary Data +// to OTLP AnyValue` specification. +TEST_F(PopulateAnyValueTest, OwnedAnyValueVectorUint64Overflow) +{ + const std::vector data = {static_cast(std::numeric_limits::max()), + std::numeric_limits::max()}; + OtlpPopulateAttributeUtils::PopulateAnyValue(&proto_anyvalue_, + sdk::common::OwnedAttributeValue{data}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).int_value(), + std::numeric_limits::max()); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), + std::to_string(std::numeric_limits::max())); +} + +// --------------------------------------------------------------------------- +// OtlpPopulateAttributeUtils::PopulateAnyValue AttributeConverterOptions truncation +// --------------------------------------------------------------------------- + +TEST_F(PopulateAnyValueTest, CStringTruncatedAtMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{"hello world"}, AttributeConverterOptions{5}); + EXPECT_EQ(proto_anyvalue_.string_value(), "hello"); +} + +TEST_F(PopulateAnyValueTest, StringViewTruncatedAtMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::string_view{"hello world"}}, + AttributeConverterOptions{5}); + EXPECT_EQ(proto_anyvalue_.string_value(), "hello"); +} + +TEST_F(PopulateAnyValueTest, StringViewNotTruncatedWhenWithinLimit) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::string_view{"hello world"}}, + AttributeConverterOptions{15}); + EXPECT_EQ(proto_anyvalue_.string_value(), "hello world"); +} + +TEST_F(PopulateAnyValueTest, OwnedStringTruncatedAtMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, sdk::common::OwnedAttributeValue{std::string{"hello world"}}, + AttributeConverterOptions{5}); + EXPECT_EQ(proto_anyvalue_.string_value(), "hello"); +} + +TEST_F(PopulateAnyValueTest, ByteSpanTruncatedAtMaxLength) +{ + const std::array data = {0x01, 0x02, 0x03, 0x04, 0x05}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}, + AttributeConverterOptions{3}); + EXPECT_EQ(proto_anyvalue_.bytes_value().size(), 3u); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[0]), 0x01); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[1]), 0x02); + EXPECT_EQ(static_cast(proto_anyvalue_.bytes_value()[2]), 0x03); +} + +TEST_F(PopulateAnyValueTest, NonStringNotAffectedByMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{int64_t{42}}, AttributeConverterOptions{1}); + EXPECT_EQ(proto_anyvalue_.int_value(), 42); +} + +TEST_F(PopulateAnyValueTest, StringViewSpanElementsTruncatedAtMaxLength) +{ + const std::array data = {"hello", "world"}; + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, common::AttributeValue{nostd::span{data}}, + AttributeConverterOptions{3}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "hel"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), "wor"); +} + +TEST_F(PopulateAnyValueTest, OwnedVectorOfStringsTruncatedAtMaxLength) +{ + OtlpPopulateAttributeUtils::PopulateAnyValue( + &proto_anyvalue_, + sdk::common::OwnedAttributeValue{std::vector{"hello", "world"}}, + AttributeConverterOptions{3}); + ASSERT_EQ(proto_anyvalue_.array_value().values_size(), 2); + EXPECT_EQ(proto_anyvalue_.array_value().values(0).string_value(), "hel"); + EXPECT_EQ(proto_anyvalue_.array_value().values(1).string_value(), "wor"); +} + +// ----------------------------------------------------------- +// OtlpPopulateAttributeUtils::PopulateAttribute tests +// ----------------------------------------------------------- + +namespace +{ +class PopulateAttributeTest : public ::testing::Test +{ +protected: + opentelemetry::proto::common::v1::KeyValue proto_keyvalue_; +}; +} // namespace + +TEST_F(PopulateAttributeTest, PopulateAttributeKeyValue) +{ + OtlpPopulateAttributeUtils::PopulateAttribute(&proto_keyvalue_, nostd::string_view{"my-key"}, + common::AttributeValue{int64_t{123}}); + EXPECT_EQ(proto_keyvalue_.key(), "my-key"); + EXPECT_EQ(proto_keyvalue_.value().int_value(), 123); +} + +TEST_F(PopulateAttributeTest, PopulateAttributeKeyValueOwned) +{ + OtlpPopulateAttributeUtils::PopulateAttribute( + &proto_keyvalue_, nostd::string_view{"my-key"}, + sdk::common::OwnedAttributeValue{std::string{"my-value"}}); + EXPECT_EQ(proto_keyvalue_.key(), "my-key"); + EXPECT_EQ(proto_keyvalue_.value().string_value(), "my-value"); +} + } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/test/otlp_recordable_test.cc b/exporters/otlp/test/otlp_recordable_test.cc index 2481383f85..7aeba55d45 100644 --- a/exporters/otlp/test/otlp_recordable_test.cc +++ b/exporters/otlp/test/otlp_recordable_test.cc @@ -321,13 +321,13 @@ TEST(OtlpRecordable, SetResource) } else if (attr.key() == "bytes_value") { - EXPECT_EQ(attr.value().array_value().values_size(), 6); - EXPECT_EQ(attr.value().array_value().values(0).int_value(), 1); - EXPECT_EQ(attr.value().array_value().values(1).int_value(), 0); - EXPECT_EQ(attr.value().array_value().values(2).int_value(), 3); - EXPECT_EQ(attr.value().array_value().values(3).int_value(), static_cast('a')); - EXPECT_EQ(attr.value().array_value().values(4).int_value(), static_cast('b')); - EXPECT_EQ(attr.value().array_value().values(5).int_value(), static_cast('c')); + EXPECT_EQ(attr.value().bytes_value().size(), 6u); + EXPECT_EQ(static_cast(attr.value().bytes_value()[0]), 1); + EXPECT_EQ(static_cast(attr.value().bytes_value()[1]), 0); + EXPECT_EQ(static_cast(attr.value().bytes_value()[2]), 3); + EXPECT_EQ(attr.value().bytes_value()[3], 'a'); + EXPECT_EQ(attr.value().bytes_value()[4], 'b'); + EXPECT_EQ(attr.value().bytes_value()[5], 'c'); ++found_attribute_count; } else if (attr.key() == "bool_array") @@ -412,15 +412,11 @@ TEST(OtlpRecordable, SetSingleAttribute) nostd::get(str_val).data()); EXPECT_EQ(rec.span().attributes(3).key(), byte_key); - EXPECT_EQ(rec.span().attributes(3).value().array_value().values_size(), 4); - EXPECT_EQ(rec.span().attributes(3).value().array_value().values(0).int_value(), - static_cast('T')); - EXPECT_EQ(rec.span().attributes(3).value().array_value().values(1).int_value(), - static_cast('e')); - EXPECT_EQ(rec.span().attributes(3).value().array_value().values(2).int_value(), - static_cast('s')); - EXPECT_EQ(rec.span().attributes(3).value().array_value().values(3).int_value(), - static_cast('t')); + EXPECT_EQ(rec.span().attributes(3).value().bytes_value().size(), 4u); + EXPECT_EQ(rec.span().attributes(3).value().bytes_value()[0], 'T'); + EXPECT_EQ(rec.span().attributes(3).value().bytes_value()[1], 'e'); + EXPECT_EQ(rec.span().attributes(3).value().bytes_value()[2], 's'); + EXPECT_EQ(rec.span().attributes(3).value().bytes_value()[3], 't'); } // Test non-int array types. Int array types are tested using templates (see IntAttributeTest) @@ -559,7 +555,7 @@ struct EmptyArrayAttributeTest : public testing::Test } // namespace using ArrayElementTypes = - testing::Types; + testing::Types; TYPED_TEST_SUITE(EmptyArrayAttributeTest, ArrayElementTypes); // Test empty arrays. @@ -788,6 +784,57 @@ 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 that setting an attribute with an existing key overwrites the value in place +// without creating a duplicate entry (spec: attribute keys MUST be unique). +TEST(OtlpRecordable, DISABLED_SetAttributeDeduplicatesKey) +{ + OtlpRecordable rec; + rec.SetAttribute("key", common::AttributeValue{static_cast(1)}); + rec.SetAttribute("key", common::AttributeValue{static_cast(2)}); + + const auto &proto_span = rec.span(); + // Only one attribute; the second call must overwrite, not append. + ASSERT_EQ(proto_span.attributes_size(), 1); + EXPECT_EQ(proto_span.attributes(0).key(), "key"); + EXPECT_EQ(proto_span.attributes(0).value().int_value(), 2); +} + +// Test that updating a duplicate key does not consume an attribute slot or increment +// the dropped-attributes counter, even when the attribute limit is already reached. +TEST(OtlpRecordable, DISABLED_SetAttributeDeduplicateDoesNotIncrementDropped) +{ + // Limit to exactly one attribute so the slot is full after the first call. + OtlpRecordable rec(/*max_attributes=*/1); + rec.SetAttribute("duplicate_attribute", common::AttributeValue{static_cast(1)}); + rec.SetAttribute("duplicate_attribute", common::AttributeValue{static_cast(2)}); + + const auto &proto_span = rec.span(); + ASSERT_EQ(proto_span.attributes_size(), 1); + EXPECT_EQ(proto_span.attributes(0).value().int_value(), 2); + EXPECT_EQ(proto_span.dropped_attributes_count(), 0u); +} + +// Test that updating a duplicate key changes the type as well as the value. +TEST(OtlpRecordable, DISABLED_SetAttributeDeduplicateChangesType) +{ + OtlpRecordable rec; + rec.SetAttribute("type_change_attribute", common::AttributeValue{static_cast(42)}); + rec.SetAttribute("type_change_attribute", common::AttributeValue{nostd::string_view("hello")}); + + const auto &proto_span = rec.span(); + ASSERT_EQ(proto_span.attributes_size(), 1); + EXPECT_EQ(proto_span.attributes(0).value().string_value(), "hello"); +} + +// Test that an empty key is rejected and not stored. +TEST(OtlpRecordable, SetAttributeEmptyKeyIsRejected) +{ + OtlpRecordable rec; + rec.SetAttribute("", common::AttributeValue{static_cast(1)}); + + const auto &proto_span = rec.span(); + EXPECT_EQ(proto_span.attributes_size(), 0); +} TEST(OtlpRecordable, SpanLimits) { diff --git a/sdk/include/opentelemetry/sdk/common/attribute_utils.h b/sdk/include/opentelemetry/sdk/common/attribute_utils.h index 8b83b363b0..0a913b3339 100644 --- a/sdk/include/opentelemetry/sdk/common/attribute_utils.h +++ b/sdk/include/opentelemetry/sdk/common/attribute_utils.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -302,23 +303,68 @@ AttributeInsertOrAssign(Map &map, opentelemetry::nostd::string_view key, Value & #endif } +// Variant visitor utility. + +// VisitVariantResult is a pair of the visitor return value (or nostd::monostate for void visitors) +// and a bool that is true for success and false if an exception was caught +template +using VisitVariantResult = std::pair; + +namespace detail +{ + +/* + * VisitVariantImpl is a helper for VisitVariant to handle void and non-void visitors. + * ValueType is the VisitorReturnType for non-void visitors, and + * nostd::monostate for visitors that do not return a value. + */ + +// VisitVariantImpl for visitors that do not return values +template +inline std::pair VisitVariantImpl(Visitor &&visitor, + const Variant &value, + std::true_type) +{ + opentelemetry::nostd::visit(std::forward(visitor), value); + return VisitVariantResult{ValueType{}, true}; +} + +// VisitVariantImpl for visitors that return values +template +inline std::pair VisitVariantImpl(Visitor &&visitor, + const Variant &value, + std::false_type) +{ + return VisitVariantResult{ + opentelemetry::nostd::visit(std::forward(visitor), value), true}; +} +} // namespace detail + /** * 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. + * Returns pair where bool is true on success and false if an + * exception was caught. + * + * ResultValueType is VisitorReturnType for non-void visitors, and nostd::monostate for void + * visitors. + * */ template inline auto VisitVariant(Visitor &&visitor, const Variant &value) noexcept - -> std::pair(visitor), value)), bool> { + using VisitorReturnType = + decltype(opentelemetry::nostd::visit(std::forward(visitor), value)); + using ResultValueType = + typename std::conditional::value, + opentelemetry::nostd::monostate, VisitorReturnType>::type; #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}; + return detail::VisitVariantImpl( + std::forward(visitor), value, std::is_void{}); #if OPENTELEMETRY_HAVE_EXCEPTIONS } # if defined(OPENTELEMETRY_HAVE_STD_VARIANT) @@ -327,11 +373,11 @@ inline auto VisitVariant(Visitor &&visitor, const Variant &value) noexcept catch (const opentelemetry::nostd::bad_variant_access &) # endif { - return {ReturnType{}, false}; + return VisitVariantResult{ResultValueType{}, false}; } catch (const std::bad_alloc &) { - return {ReturnType{}, false}; + return VisitVariantResult{ResultValueType{}, false}; } #endif } diff --git a/sdk/src/logs/multi_recordable.cc b/sdk/src/logs/multi_recordable.cc index f63ea6ce41..ab43b7e890 100644 --- a/sdk/src/logs/multi_recordable.cc +++ b/sdk/src/logs/multi_recordable.cc @@ -151,6 +151,10 @@ void MultiRecordable::SetTraceFlags(const opentelemetry::trace::TraceFlags &trac void MultiRecordable::SetAttribute(nostd::string_view key, const opentelemetry::common::AttributeValue &value) noexcept { + if (key.empty()) + { + return; + } for (auto &recordable : recordables_) { if (recordable.second) diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index 1233e3361d..6b8126902c 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -100,6 +100,10 @@ Span::~Span() void Span::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept { + if (key.empty()) + { + return; + } std::lock_guard lock_guard{mu_}; if (recordable_ == nullptr) { From 1d87de8f13689ecbde3e6c93b34f4c1961048910 Mon Sep 17 00:00:00 2001 From: Doug Barker <3782873+dbarker@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:13:15 -0400 Subject: [PATCH 57/61] [CODE HEALTH] Fix more clang tidy warnings (member initialization) (#4270) --- .github/workflows/clang-tidy.yaml | 4 ++-- CHANGELOG.md | 3 +++ api/include/opentelemetry/baggage/baggage.h | 6 ++--- api/include/opentelemetry/trace/span_id.h | 4 ++-- api/include/opentelemetry/trace/trace_id.h | 4 ++-- examples/http/server.cc | 2 +- .../exporters/otlp/otlp_grpc_client_options.h | 2 +- .../otlp/otlp_grpc_metric_exporter_options.h | 3 ++- .../exporters/otlp/otlp_http_client.h | 2 +- .../otlp/otlp_http_exporter_options.h | 16 ++++++------- .../otlp_http_log_record_exporter_options.h | 16 ++++++------- .../otlp/otlp_http_metric_exporter_options.h | 19 ++++++++------- exporters/otlp/src/otlp_file_client.cc | 14 +++++------ .../src/otlp_grpc_metric_exporter_options.cc | 3 --- .../otlp/src/otlp_http_exporter_options.cc | 21 +--------------- .../otlp_http_log_record_exporter_options.cc | 21 +--------------- .../src/otlp_http_metric_exporter_options.cc | 24 +------------------ .../http/client/curl/http_operation_curl.h | 4 ++-- .../ext/http/server/http_server.h | 10 ++++---- .../ext/http/server/socket_tools.h | 4 ++-- .../http/client/curl/http_operation_curl.cc | 11 +++++---- functional/otlp/func_grpc_main.cc | 2 +- functional/otlp/func_http_main.cc | 2 +- .../sdk/metrics/data/metric_data.h | 2 +- .../exemplar/fixed_size_exemplar_reservoir.h | 2 +- .../opentelemetry/sdk/metrics/instruments.h | 4 ++-- sdk/include/opentelemetry/sdk/trace/sampler.h | 2 +- sdk/src/trace/samplers/ot_trace_state.cc | 4 ++-- sdk/test/configuration/yaml_resource_test.cc | 3 ++- sdk/test/logs/logger_sdk_test.cc | 2 +- ...exponential_histogram_indexer_benchmark.cc | 4 ++-- sdk/test/metrics/multi_metric_storage_test.cc | 4 ++-- sdk/test/trace/composable_sampler_test.cc | 2 +- 33 files changed, 86 insertions(+), 140 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 08b66a0699..d667747b93 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 216 + warning_limit: 163 - cmake_options: all-options-abiv2-preview - warning_limit: 226 + warning_limit: 173 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 899a46701c..dc84ee734d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CODE HEALTH] Fix more clang tidy warnings (member initialization) + [#4270](https://github.com/open-telemetry/opentelemetry-cpp/pull/4270) + * docs: update supported development platforms [#4260](https://github.com/open-telemetry/opentelemetry-cpp/pull/4260) diff --git a/api/include/opentelemetry/baggage/baggage.h b/api/include/opentelemetry/baggage/baggage.h index 0dbf21281a..2ee9f8ec6d 100644 --- a/api/include/opentelemetry/baggage/baggage.h +++ b/api/include/opentelemetry/baggage/baggage.h @@ -142,7 +142,7 @@ class OPENTELEMETRY_EXPORT Baggage value = value.substr(0, metadata_separator); } - bool err = 0; + bool err = false; auto key_str = UrlDecode(common::StringUtil::Trim(key), err); auto value_str = UrlDecode(common::StringUtil::Trim(value), err); @@ -264,7 +264,7 @@ class OPENTELEMETRY_EXPORT Baggage { if (i + 2 >= str.size() || !IsHex(str[i + 1]) || !IsHex(str[i + 2])) { - err = 1; + err = true; return ""; } ret.push_back(static_cast(from_hex(str[i + 1]) << 4 | from_hex(str[i + 2]))); @@ -286,7 +286,7 @@ class OPENTELEMETRY_EXPORT Baggage } else { - err = 1; + err = true; return ""; } } diff --git a/api/include/opentelemetry/trace/span_id.h b/api/include/opentelemetry/trace/span_id.h index 6537f77fc7..249150c2f4 100644 --- a/api/include/opentelemetry/trace/span_id.h +++ b/api/include/opentelemetry/trace/span_id.h @@ -20,7 +20,7 @@ class SpanId final static constexpr size_t kSize = 8; // An invalid SpanId (all zeros). - SpanId() noexcept : rep_{0} {} + SpanId() noexcept = default; // Creates a SpanId with the given ID. explicit SpanId(nostd::span id) noexcept { memcpy(rep_, id.data(), kSize); } @@ -60,7 +60,7 @@ class SpanId final } private: - uint8_t rep_[kSize]; + uint8_t rep_[kSize]{}; }; } // namespace trace diff --git a/api/include/opentelemetry/trace/trace_id.h b/api/include/opentelemetry/trace/trace_id.h index 533037123e..7992744bd2 100644 --- a/api/include/opentelemetry/trace/trace_id.h +++ b/api/include/opentelemetry/trace/trace_id.h @@ -23,7 +23,7 @@ class TraceId final static constexpr size_t kSize = 16; // An invalid TraceId (all zeros). - TraceId() noexcept : rep_{0} {} + TraceId() noexcept = default; // Creates a TraceId with the given ID. explicit TraceId(nostd::span id) noexcept @@ -65,7 +65,7 @@ class TraceId final } private: - uint8_t rep_[kSize]; + uint8_t rep_[kSize]{}; }; } // namespace trace diff --git a/examples/http/server.cc b/examples/http/server.cc index 3452e7a495..267dd20343 100644 --- a/examples/http/server.cc +++ b/examples/http/server.cc @@ -107,7 +107,7 @@ int main(int argc, char *argv[]) Scope scope(root_span); http_server.Start(); std::cout << "Server is running..Press ctrl-c to exit...\n"; - while (1) + while (true) { std::this_thread::sleep_for(std::chrono::seconds(100)); } diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_client_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_client_options.h index f524b27b9a..43d4435f54 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_client_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_client_options.h @@ -61,7 +61,7 @@ struct OtlpGrpcClientOptions #endif /** Export timeout. */ - std::chrono::system_clock::duration timeout; + std::chrono::system_clock::duration timeout{}; /** Additional HTTP headers. */ OtlpHeaders metadata; diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_options.h index ab6c8795f7..0ead376fae 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_options.h @@ -35,7 +35,8 @@ struct OPENTELEMETRY_EXPORT OtlpGrpcMetricExporterOptions : public OtlpGrpcClien ~OtlpGrpcMetricExporterOptions() override; /** Preferred Aggregation Temporality. */ - PreferredAggregationTemporality aggregation_temporality; + PreferredAggregationTemporality aggregation_temporality{ + PreferredAggregationTemporality::kCumulative}; }; } // namespace otlp diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h index 2d891f175b..da28fbfdec 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h @@ -72,7 +72,7 @@ struct OtlpHttpClientOptions // Whether to print the status of the HTTP client in the console bool console_debug = false; - std::chrono::system_clock::duration timeout; + std::chrono::system_clock::duration timeout{}; // Additional HTTP headers OtlpHeaders http_headers; 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 b146fd3b0a..56750015a4 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 @@ -47,7 +47,7 @@ struct OPENTELEMETRY_EXPORT OtlpHttpExporterOptions std::string url; /** HTTP content type. */ - HttpRequestContentType content_type; + HttpRequestContentType content_type{HttpRequestContentType::kBinary}; /** Json byte mapping. @@ -55,32 +55,32 @@ struct OPENTELEMETRY_EXPORT OtlpHttpExporterOptions Used only for HttpRequestContentType::kJson. Convert bytes to hex / base64. */ - JsonBytesMappingKind json_bytes_mapping; + JsonBytesMappingKind json_bytes_mapping{JsonBytesMappingKind::kHexId}; /** Use json names (true) or protobuf field names (false) to set the json key. */ - bool use_json_name; + bool use_json_name{false}; /** Print debug messages. */ - bool console_debug; + bool console_debug{false}; /** Export timeout. */ - std::chrono::system_clock::duration timeout; + std::chrono::system_clock::duration timeout{}; /** Additional HTTP headers. */ OtlpHeaders http_headers; #ifdef ENABLE_ASYNC_EXPORT /** Max number of concurrent requests. */ - std::size_t max_concurrent_requests; + std::size_t max_concurrent_requests{64}; /** Max number of requests per connection. */ - std::size_t max_requests_per_connection; + std::size_t max_requests_per_connection{8}; #endif /** True do disable SSL. */ - bool ssl_insecure_skip_verify; + bool ssl_insecure_skip_verify{}; /** CA CERT, path to a file. */ std::string ssl_ca_cert_path; diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h index 60785ed9f8..6f176a4fd1 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h @@ -46,7 +46,7 @@ struct OPENTELEMETRY_EXPORT OtlpHttpLogRecordExporterOptions std::string url; /** HTTP content type. */ - HttpRequestContentType content_type; + HttpRequestContentType content_type{HttpRequestContentType::kBinary}; /** Json byte mapping. @@ -54,32 +54,32 @@ struct OPENTELEMETRY_EXPORT OtlpHttpLogRecordExporterOptions Used only for HttpRequestContentType::kJson. Convert bytes to hex / base64. */ - JsonBytesMappingKind json_bytes_mapping; + JsonBytesMappingKind json_bytes_mapping{JsonBytesMappingKind::kHexId}; /** Use json names (true) or protobuf field names (false) to set the json key. */ - bool use_json_name; + bool use_json_name{false}; /** Print debug messages. */ - bool console_debug; + bool console_debug{false}; /** Export timeout. */ - std::chrono::system_clock::duration timeout; + std::chrono::system_clock::duration timeout{}; /** Additional HTTP headers. */ OtlpHeaders http_headers; #ifdef ENABLE_ASYNC_EXPORT /** Max number of concurrent requests. */ - std::size_t max_concurrent_requests; + std::size_t max_concurrent_requests{64}; /** Max number of requests per connection. */ - std::size_t max_requests_per_connection; + std::size_t max_requests_per_connection{8}; #endif /** True do disable SSL. */ - bool ssl_insecure_skip_verify; + bool ssl_insecure_skip_verify{}; /** CA CERT, path to a file. */ std::string ssl_ca_cert_path; diff --git a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h index 76f2e9a513..3d872d638e 100644 --- a/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h +++ b/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h @@ -47,7 +47,7 @@ struct OPENTELEMETRY_EXPORT OtlpHttpMetricExporterOptions std::string url; /** HTTP content type. */ - HttpRequestContentType content_type; + HttpRequestContentType content_type{HttpRequestContentType::kBinary}; /** Json byte mapping. @@ -55,34 +55,35 @@ struct OPENTELEMETRY_EXPORT OtlpHttpMetricExporterOptions Used only for HttpRequestContentType::kJson. Convert bytes to hex / base64. */ - JsonBytesMappingKind json_bytes_mapping; + JsonBytesMappingKind json_bytes_mapping{JsonBytesMappingKind::kHexId}; /** Use json names (true) or protobuf field names (false) to set the json key. */ - bool use_json_name; + bool use_json_name{false}; /** Print debug messages. */ - bool console_debug; + bool console_debug{false}; /** Export timeout. */ - std::chrono::system_clock::duration timeout; + std::chrono::system_clock::duration timeout{}; /** Additional HTTP headers. */ OtlpHeaders http_headers; - PreferredAggregationTemporality aggregation_temporality; + PreferredAggregationTemporality aggregation_temporality{ + PreferredAggregationTemporality::kCumulative}; #ifdef ENABLE_ASYNC_EXPORT /** Max number of concurrent requests. */ - std::size_t max_concurrent_requests; + std::size_t max_concurrent_requests{64}; /** Max number of requests per connection. */ - std::size_t max_requests_per_connection; + std::size_t max_requests_per_connection{8}; #endif /** True do disable SSL. */ - bool ssl_insecure_skip_verify; + bool ssl_insecure_skip_verify{}; /** CA CERT, path to a file. */ std::string ssl_ca_cert_path; diff --git a/exporters/otlp/src/otlp_file_client.cc b/exporters/otlp/src/otlp_file_client.cc index f7c757fe79..7cdc467e83 100644 --- a/exporters/otlp/src/otlp_file_client.cc +++ b/exporters/otlp/src/otlp_file_client.cc @@ -1569,16 +1569,16 @@ class OPENTELEMETRY_LOCAL_SYMBOL OtlpFileSystemBackend : public OtlpFileAppender struct FileStats { - std::atomic is_shutdown; - std::size_t rotate_index; - std::size_t written_size; - std::size_t left_flush_record_count; + std::atomic is_shutdown{false}; + std::size_t rotate_index{0}; + std::size_t written_size{0}; + std::size_t left_flush_record_count{0}; std::shared_ptr current_file; std::mutex file_lock; - std::time_t last_checkpoint; + std::time_t last_checkpoint{0}; std::string file_path; - std::atomic record_count; - std::atomic flushed_record_count; + std::atomic record_count{0}; + std::atomic flushed_record_count{0}; std::unique_ptr background_flush_thread; std::mutex background_thread_lock; diff --git a/exporters/otlp/src/otlp_grpc_metric_exporter_options.cc b/exporters/otlp/src/otlp_grpc_metric_exporter_options.cc index 540357ff9c..1c00faefcd 100644 --- a/exporters/otlp/src/otlp_grpc_metric_exporter_options.cc +++ b/exporters/otlp/src/otlp_grpc_metric_exporter_options.cc @@ -6,7 +6,6 @@ #include "opentelemetry/exporters/otlp/otlp_environment.h" #include "opentelemetry/exporters/otlp/otlp_grpc_metric_exporter_options.h" -#include "opentelemetry/exporters/otlp/otlp_preferred_temporality.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -16,7 +15,6 @@ namespace otlp { OtlpGrpcMetricExporterOptions::OtlpGrpcMetricExporterOptions() - : aggregation_temporality(PreferredAggregationTemporality::kCumulative) { endpoint = GetOtlpDefaultGrpcMetricsEndpoint(); use_ssl_credentials = !GetOtlpDefaultGrpcMetricsIsInsecure(); /* negation intended. */ @@ -48,7 +46,6 @@ OtlpGrpcMetricExporterOptions::OtlpGrpcMetricExporterOptions() } OtlpGrpcMetricExporterOptions::OtlpGrpcMetricExporterOptions(void *) - : aggregation_temporality(PreferredAggregationTemporality::kCumulative) { use_ssl_credentials = true; max_threads = 0; diff --git a/exporters/otlp/src/otlp_http_exporter_options.cc b/exporters/otlp/src/otlp_http_exporter_options.cc index c7ac41afc4..05be6a0140 100644 --- a/exporters/otlp/src/otlp_http_exporter_options.cc +++ b/exporters/otlp/src/otlp_http_exporter_options.cc @@ -15,16 +15,8 @@ namespace otlp OtlpHttpExporterOptions::OtlpHttpExporterOptions() : url(GetOtlpDefaultHttpTracesEndpoint()), content_type(GetOtlpHttpProtocolFromString(GetOtlpDefaultHttpTracesProtocol())), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), timeout(GetOtlpDefaultTracesTimeout()), http_headers(GetOtlpDefaultTracesHeaders()), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false), ssl_ca_cert_path(GetOtlpDefaultTracesSslCertificatePath()), ssl_ca_cert_string(GetOtlpDefaultTracesSslCertificateString()), ssl_client_key_path(GetOtlpDefaultTracesSslClientKeyPath()), @@ -42,18 +34,7 @@ OtlpHttpExporterOptions::OtlpHttpExporterOptions() retry_policy_backoff_multiplier(GetOtlpDefaultTracesRetryBackoffMultiplier()) {} -OtlpHttpExporterOptions::OtlpHttpExporterOptions(void *) - : url(), - content_type(HttpRequestContentType::kBinary), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false) -{} +OtlpHttpExporterOptions::OtlpHttpExporterOptions(void *) {} OtlpHttpExporterOptions::~OtlpHttpExporterOptions() {} diff --git a/exporters/otlp/src/otlp_http_log_record_exporter_options.cc b/exporters/otlp/src/otlp_http_log_record_exporter_options.cc index 1ff1c4354d..9698836976 100644 --- a/exporters/otlp/src/otlp_http_log_record_exporter_options.cc +++ b/exporters/otlp/src/otlp_http_log_record_exporter_options.cc @@ -15,16 +15,8 @@ namespace otlp OtlpHttpLogRecordExporterOptions::OtlpHttpLogRecordExporterOptions() : url(GetOtlpDefaultHttpLogsEndpoint()), content_type(GetOtlpHttpProtocolFromString(GetOtlpDefaultHttpLogsProtocol())), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), timeout(GetOtlpDefaultLogsTimeout()), http_headers(GetOtlpDefaultLogsHeaders()), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false), ssl_ca_cert_path(GetOtlpDefaultLogsSslCertificatePath()), ssl_ca_cert_string(GetOtlpDefaultLogsSslCertificateString()), ssl_client_key_path(GetOtlpDefaultLogsSslClientKeyPath()), @@ -42,18 +34,7 @@ OtlpHttpLogRecordExporterOptions::OtlpHttpLogRecordExporterOptions() retry_policy_backoff_multiplier(GetOtlpDefaultLogsRetryBackoffMultiplier()) {} -OtlpHttpLogRecordExporterOptions::OtlpHttpLogRecordExporterOptions(void *) - : url(), - content_type(exporter::otlp::HttpRequestContentType::kBinary), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false) -{} +OtlpHttpLogRecordExporterOptions::OtlpHttpLogRecordExporterOptions(void *) {} OtlpHttpLogRecordExporterOptions::~OtlpHttpLogRecordExporterOptions() {} diff --git a/exporters/otlp/src/otlp_http_metric_exporter_options.cc b/exporters/otlp/src/otlp_http_metric_exporter_options.cc index 78f724301e..9cbac492e3 100644 --- a/exporters/otlp/src/otlp_http_metric_exporter_options.cc +++ b/exporters/otlp/src/otlp_http_metric_exporter_options.cc @@ -4,7 +4,6 @@ #include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h" #include "opentelemetry/exporters/otlp/otlp_environment.h" #include "opentelemetry/exporters/otlp/otlp_http.h" -#include "opentelemetry/exporters/otlp/otlp_preferred_temporality.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -16,17 +15,8 @@ namespace otlp OtlpHttpMetricExporterOptions::OtlpHttpMetricExporterOptions() : url(GetOtlpDefaultMetricsEndpoint()), content_type(GetOtlpHttpProtocolFromString(GetOtlpDefaultHttpMetricsProtocol())), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), timeout(GetOtlpDefaultMetricsTimeout()), http_headers(GetOtlpDefaultMetricsHeaders()), - aggregation_temporality(PreferredAggregationTemporality::kCumulative), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false), ssl_ca_cert_path(GetOtlpDefaultMetricsSslCertificatePath()), ssl_ca_cert_string(GetOtlpDefaultMetricsSslCertificateString()), ssl_client_key_path(GetOtlpDefaultMetricsSslClientKeyPath()), @@ -44,19 +34,7 @@ OtlpHttpMetricExporterOptions::OtlpHttpMetricExporterOptions() retry_policy_backoff_multiplier(GetOtlpDefaultMetricsRetryBackoffMultiplier()) {} -OtlpHttpMetricExporterOptions::OtlpHttpMetricExporterOptions(void *) - : url(), - content_type(exporter::otlp::HttpRequestContentType::kBinary), - json_bytes_mapping(JsonBytesMappingKind::kHexId), - use_json_name(false), - console_debug(false), - aggregation_temporality(PreferredAggregationTemporality::kCumulative), -#ifdef ENABLE_ASYNC_EXPORT - max_concurrent_requests{64}, - max_requests_per_connection{8}, -#endif - ssl_insecure_skip_verify(false) -{} +OtlpHttpMetricExporterOptions::OtlpHttpMetricExporterOptions(void *) {} OtlpHttpMetricExporterOptions::~OtlpHttpMetricExporterOptions() {} 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 541b74a85c..38d026d07a 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 @@ -323,7 +323,7 @@ class HttpOperation const bool reuse_connection_{false}; // Reuse connection const std::chrono::milliseconds http_conn_timeout_; // Timeout for connect. Default: 5000ms - char curl_error_message_[CURL_ERROR_SIZE]; + char curl_error_message_[CURL_ERROR_SIZE]{}; HttpCurlEasyResource curl_resource_; CURLcode last_curl_result_{CURLE_OK}; // Curl result OR HTTP status code if successful @@ -361,7 +361,7 @@ class HttpOperation struct AsyncData { - Session *session; // Owner Session + Session *session{nullptr}; // Owner Session std::thread::id callback_thread; std::function callback; diff --git a/ext/include/opentelemetry/ext/http/server/http_server.h b/ext/include/opentelemetry/ext/http/server/http_server.h index 2129c2b643..97e9e3705f 100644 --- a/ext/include/opentelemetry/ext/http/server/http_server.h +++ b/ext/include/opentelemetry/ext/http/server/http_server.h @@ -42,7 +42,7 @@ struct HttpRequest struct HttpResponse { - int code; + int code{0}; std::string message; std::map headers; std::string body; @@ -108,9 +108,9 @@ class HttpServer : private SocketTools::Reactor::SocketCallback SendingHeaders, SendingBody, Closing - } state; - size_t contentLength; - bool keepalive; + } state{Idle}; + size_t contentLength{0}; + bool keepalive{false}; HttpRequest request; HttpResponse response; }; @@ -783,7 +783,7 @@ class HttpServer : private SocketTools::Reactor::SocketCallback static std::string formatTimestamp(time_t time) { - tm tm; + tm tm{}; #ifdef _WIN32 gmtime_s(&tm, &time); #else diff --git a/ext/include/opentelemetry/ext/http/server/socket_tools.h b/ext/include/opentelemetry/ext/http/server/socket_tools.h index b2e3217818..6823fae35d 100644 --- a/ext/include/opentelemetry/ext/http/server/socket_tools.h +++ b/ext/include/opentelemetry/ext/http/server/socket_tools.h @@ -165,13 +165,13 @@ struct SocketAddr { static u_long const Loopback = 0x7F000001; - sockaddr m_data; + sockaddr m_data{}; /// /// SocketAddr constructor /// /// SocketAddr - SocketAddr() { memset(&m_data, 0, sizeof(m_data)); } + SocketAddr() {} SocketAddr(u_long addr, int port) { diff --git a/ext/src/http/client/curl/http_operation_curl.cc b/ext/src/http/client/curl/http_operation_curl.cc index abd8b3ea77..9ee0917b2f 100644 --- a/ext/src/http/client/curl/http_operation_curl.cc +++ b/ext/src/http/client/curl/http_operation_curl.cc @@ -856,7 +856,8 @@ CURLcode HttpOperation::Setup() const char *data = ssl_options_.ssl_ca_cert_string.c_str(); size_t data_len = ssl_options_.ssl_ca_cert_string.length(); - struct curl_blob stblob; + struct curl_blob stblob + {}; stblob.data = const_cast(data); stblob.len = data_len; stblob.flags = CURL_BLOB_COPY; @@ -897,7 +898,8 @@ CURLcode HttpOperation::Setup() const char *data = ssl_options_.ssl_client_key_string.c_str(); size_t data_len = ssl_options_.ssl_client_key_string.length(); - struct curl_blob stblob; + struct curl_blob stblob + {}; stblob.data = const_cast(data); stblob.len = data_len; stblob.flags = CURL_BLOB_COPY; @@ -944,7 +946,8 @@ CURLcode HttpOperation::Setup() const char *data = ssl_options_.ssl_client_cert_string.c_str(); size_t data_len = ssl_options_.ssl_client_cert_string.length(); - struct curl_blob stblob; + struct curl_blob stblob + {}; stblob.data = const_cast(data); stblob.len = data_len; stblob.flags = CURL_BLOB_COPY; @@ -1252,7 +1255,7 @@ CURLcode HttpOperation::Setup() return rc; } - rc = SetCurlStrOption(CURLOPT_POSTFIELDS, NULL); + rc = SetCurlStrOption(CURLOPT_POSTFIELDS, nullptr); if (rc != CURLE_OK) { return rc; diff --git a/functional/otlp/func_grpc_main.cc b/functional/otlp/func_grpc_main.cc index f53cde3f1b..0e71ef90dd 100644 --- a/functional/otlp/func_grpc_main.cc +++ b/functional/otlp/func_grpc_main.cc @@ -320,7 +320,7 @@ typedef int (*test_func_t)(); struct test_case { nostd::string_view m_name; - test_func_t m_func; + test_func_t m_func{nullptr}; }; } // namespace diff --git a/functional/otlp/func_http_main.cc b/functional/otlp/func_http_main.cc index ae7399f46d..6e68a52420 100644 --- a/functional/otlp/func_http_main.cc +++ b/functional/otlp/func_http_main.cc @@ -337,7 +337,7 @@ typedef int (*test_func_t)(); struct test_case { nostd::string_view m_name; - test_func_t m_func; + test_func_t m_func{nullptr}; }; } // namespace diff --git a/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h b/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h index 30fa04e47f..4be493c0ac 100644 --- a/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h +++ b/sdk/include/opentelemetry/sdk/metrics/data/metric_data.h @@ -34,7 +34,7 @@ class MetricData { public: InstrumentDescriptor instrument_descriptor; - AggregationTemporality aggregation_temporality; + AggregationTemporality aggregation_temporality{AggregationTemporality::kUnspecified}; opentelemetry::common::SystemTimestamp start_ts; opentelemetry::common::SystemTimestamp end_ts; std::vector point_data_attr_; diff --git a/sdk/include/opentelemetry/sdk/metrics/exemplar/fixed_size_exemplar_reservoir.h b/sdk/include/opentelemetry/sdk/metrics/exemplar/fixed_size_exemplar_reservoir.h index d33c46a8db..5be8b2513e 100644 --- a/sdk/include/opentelemetry/sdk/metrics/exemplar/fixed_size_exemplar_reservoir.h +++ b/sdk/include/opentelemetry/sdk/metrics/exemplar/fixed_size_exemplar_reservoir.h @@ -98,7 +98,7 @@ class FixedSizeExemplarReservoir : public ExemplarReservoir explicit FixedSizeExemplarReservoir() = default; std::vector storage_; std::shared_ptr reservoir_cell_selector_; - MapAndResetCellType map_and_reset_cell_; + MapAndResetCellType map_and_reset_cell_{nullptr}; }; } // namespace metrics diff --git a/sdk/include/opentelemetry/sdk/metrics/instruments.h b/sdk/include/opentelemetry/sdk/metrics/instruments.h index 5b5278fb2e..938d87fd8a 100644 --- a/sdk/include/opentelemetry/sdk/metrics/instruments.h +++ b/sdk/include/opentelemetry/sdk/metrics/instruments.h @@ -64,8 +64,8 @@ struct InstrumentDescriptor std::string name_; std::string description_; std::string unit_; - InstrumentType type_; - InstrumentValueType value_type_; + InstrumentType type_{InstrumentType::kCounter}; + InstrumentValueType value_type_{InstrumentValueType::kInt}; }; struct InstrumentDescriptorUtil diff --git a/sdk/include/opentelemetry/sdk/trace/sampler.h b/sdk/include/opentelemetry/sdk/trace/sampler.h index 888a44383b..1973db431a 100644 --- a/sdk/include/opentelemetry/sdk/trace/sampler.h +++ b/sdk/include/opentelemetry/sdk/trace/sampler.h @@ -52,7 +52,7 @@ enum class Decision : std::uint8_t */ struct SamplingResult { - Decision decision; + Decision decision{Decision::DROP}; // A set of span Attributes that will also be added to the Span. Can be nullptr. std::unique_ptr> attributes; // The tracestate used by the span. diff --git a/sdk/src/trace/samplers/ot_trace_state.cc b/sdk/src/trace/samplers/ot_trace_state.cc index edf4d060d2..756dab7841 100644 --- a/sdk/src/trace/samplers/ot_trace_state.cc +++ b/sdk/src/trace/samplers/ot_trace_state.cc @@ -139,7 +139,7 @@ OtelTraceState OtelTraceState::Parse(const std::string &ot_value) noexcept std::size_t value_len = end - value_start; if (ot_value.compare(pos, colon - pos, "th") == 0) { - uint64_t threshold_value; + uint64_t threshold_value{0}; if (value_len <= 14 && ParseHex(ot_value, value_start, value_len, threshold_value)) { state.has_threshold = true; @@ -148,7 +148,7 @@ OtelTraceState OtelTraceState::Parse(const std::string &ot_value) noexcept } else if (ot_value.compare(pos, colon - pos, "rv") == 0) { - uint64_t random_value; + uint64_t random_value{0}; if (value_len == 14 && ParseHex(ot_value, value_start, value_len, random_value)) { state.has_random_value = true; diff --git a/sdk/test/configuration/yaml_resource_test.cc b/sdk/test/configuration/yaml_resource_test.cc index c5a91b5e7d..fed0fc7a6f 100644 --- a/sdk/test/configuration/yaml_resource_test.cc +++ b/sdk/test/configuration/yaml_resource_test.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -30,7 +31,7 @@ static std::unique_ptr DoParse namespace { -enum class DetectorType +enum class DetectorType : std::uint8_t { kNone, kContainer, diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 70d34004af..f85b7ef0d7 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -230,7 +230,7 @@ class MockLogRecordable final : public opentelemetry::sdk::logs::Recordable private: opentelemetry::logs::Severity severity_ = opentelemetry::logs::Severity::kInvalid; std::string body_; - int64_t event_id_; + int64_t event_id_{0}; std::string log_record_event_name_; opentelemetry::trace::TraceId trace_id_; opentelemetry::trace::SpanId span_id_; diff --git a/sdk/test/metrics/base2_exponential_histogram_indexer_benchmark.cc b/sdk/test/metrics/base2_exponential_histogram_indexer_benchmark.cc index 40ea14d575..9eb6471fcd 100644 --- a/sdk/test/metrics/base2_exponential_histogram_indexer_benchmark.cc +++ b/sdk/test/metrics/base2_exponential_histogram_indexer_benchmark.cc @@ -14,7 +14,7 @@ namespace void BM_NewIndexer(benchmark::State &state) { - std::array batch; + std::array batch{}; std::default_random_engine generator; std::uniform_int_distribution distribution(1, 32); @@ -38,7 +38,7 @@ BENCHMARK(BM_NewIndexer); void BM_ComputeIndex(benchmark::State &state) { - std::array batch; + std::array batch{}; std::default_random_engine generator; std::uniform_real_distribution distribution(0, 1000); Base2ExponentialHistogramIndexer indexer(static_cast(state.range(0))); diff --git a/sdk/test/metrics/multi_metric_storage_test.cc b/sdk/test/metrics/multi_metric_storage_test.cc index 3168f54c1d..8da13f0d8a 100644 --- a/sdk/test/metrics/multi_metric_storage_test.cc +++ b/sdk/test/metrics/multi_metric_storage_test.cc @@ -46,8 +46,8 @@ class TestMetricStorage : public SyncWritableMetricStorage num_calls_double++; } - size_t num_calls_long; - size_t num_calls_double; + size_t num_calls_long{0}; + size_t num_calls_double{0}; }; TEST(MultiMetricStorageTest, BasicTests) diff --git a/sdk/test/trace/composable_sampler_test.cc b/sdk/test/trace/composable_sampler_test.cc index e5885e7ab7..13a4415393 100644 --- a/sdk/test/trace/composable_sampler_test.cc +++ b/sdk/test/trace/composable_sampler_test.cc @@ -172,7 +172,7 @@ class AnnotatingSampler : public ComposableSampler return attrs; }; intent.trace_state_provider = - [](opentelemetry::nostd::shared_ptr trace_state) { + [](const opentelemetry::nostd::shared_ptr &trace_state) { return trace_state->Set("p", "1"); }; return intent; From 9e1375257f29cbcdd293d3dc816d765aec53c4ea Mon Sep 17 00:00:00 2001 From: Dmitrii Korzhimanov <52735121+HrMathematiker@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:41:25 +0200 Subject: [PATCH 58/61] [SDK] Read span limits from environment variables (#4258) --- CHANGELOG.md | 6 + .../opentelemetry/sdk/trace/span_limits.h | 20 +++ sdk/src/trace/BUILD | 1 + sdk/src/trace/CMakeLists.txt | 1 + sdk/src/trace/span_limits.cc | 75 +++++++++++ sdk/src/trace/tracer_provider_factory.cc | 4 +- sdk/test/trace/tracer_provider_test.cc | 116 ++++++++++++++++++ 7 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 sdk/src/trace/span_limits.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index dc84ee734d..ac5a7dd12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,12 @@ Increment the: * [METRICS SDK] Validate Base2 Exponential Histogram Aggregation config [#4253](https://github.com/open-telemetry/opentelemetry-cpp/pull/4253) +* [SDK] Read span limits from the environment variables defined in the + specification (OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, + OTEL_SPAN_LINK_COUNT_LIMIT, ...) in the TracerProviderFactory overloads + that do not receive explicit SpanLimits + [#4258](https://github.com/open-telemetry/opentelemetry-cpp/pull/4258) + * [SDK] Implement the ProbabilitySampler [#4135](https://github.com/open-telemetry/opentelemetry-cpp/pull/4135) diff --git a/sdk/include/opentelemetry/sdk/trace/span_limits.h b/sdk/include/opentelemetry/sdk/trace/span_limits.h index 3f2db6e3cc..bbadcedc6e 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_limits.h +++ b/sdk/include/opentelemetry/sdk/trace/span_limits.h @@ -63,6 +63,26 @@ struct SpanLimits } }; +namespace span_limits_env +{ + +/** + * @brief Returns span limits read from the environment variables defined in the + * OpenTelemetry specification + * (https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#span-limits). + * + * A field whose environment variable is not set keeps its value from @p defaults, so with no + * variables set the result equals @p defaults. The default of SpanLimits::NoLimits() leaves SDK + * behavior unchanged; pass SpanLimits{} to fall back to the specification defaults instead. The + * span-specific variables (OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT, + * OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT) take precedence over the general ones + * (OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_ATTRIBUTE_COUNT_LIMIT). + */ +OPENTELEMETRY_EXPORT SpanLimits +GetSpanLimitsFromEnv(const SpanLimits &defaults = SpanLimits::NoLimits()); + +} // namespace span_limits_env + } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/BUILD b/sdk/src/trace/BUILD index 17c37367f9..854ce7d261 100644 --- a/sdk/src/trace/BUILD +++ b/sdk/src/trace/BUILD @@ -15,6 +15,7 @@ cc_library( "//sdk:headers", "//sdk/src/common:disabled", "//sdk/src/common:empty_attributes", + "//sdk/src/common:env_variables", "//sdk/src/common:global_log_handler", "//sdk/src/common:random", "//sdk/src/resource", diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index b00ccf17dd..5408f5877d 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -16,6 +16,7 @@ add_library( batch_span_processor_options.cc simple_processor_factory.cc span_data.cc + span_limits.cc samplers/always_on_factory.cc samplers/always_off_factory.cc samplers/parent.cc diff --git a/sdk/src/trace/span_limits.cc b/sdk/src/trace/span_limits.cc new file mode 100644 index 0000000000..aba19b12d0 --- /dev/null +++ b/sdk/src/trace/span_limits.cc @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/trace/span_limits.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace span_limits_env +{ + +// Environment variable names, see +// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#span-limits +static constexpr const char *kAttributeValueLengthLimitEnv = "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"; +static constexpr const char *kAttributeCountLimitEnv = "OTEL_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kSpanAttributeValueLengthLimitEnv = + "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"; +static constexpr const char *kSpanAttributeCountLimitEnv = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kSpanEventCountLimitEnv = "OTEL_SPAN_EVENT_COUNT_LIMIT"; +static constexpr const char *kSpanLinkCountLimitEnv = "OTEL_SPAN_LINK_COUNT_LIMIT"; +static constexpr const char *kEventAttributeCountLimitEnv = "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"; +static constexpr const char *kLinkAttributeCountLimitEnv = "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"; + +namespace +{ + +void UpdateFromEnv(const char *env_var_name, std::uint32_t &limit) +{ + std::uint32_t value{}; + if (opentelemetry::sdk::common::GetUintEnvironmentVariable(env_var_name, value)) + { + limit = value; + } +} + +void UpdateFromEnv(const char *env_var_name, std::size_t &limit) +{ + std::uint32_t value{}; + if (opentelemetry::sdk::common::GetUintEnvironmentVariable(env_var_name, value)) + { + limit = static_cast(value); + } +} + +} // namespace + +SpanLimits GetSpanLimitsFromEnv(const SpanLimits &defaults) +{ + SpanLimits limits = defaults; + + // General attribute limits first; the span-specific variables below take precedence. + UpdateFromEnv(kAttributeValueLengthLimitEnv, limits.attribute_value_length_limit); + UpdateFromEnv(kAttributeCountLimitEnv, limits.attribute_count_limit); + + UpdateFromEnv(kSpanAttributeValueLengthLimitEnv, limits.attribute_value_length_limit); + UpdateFromEnv(kSpanAttributeCountLimitEnv, limits.attribute_count_limit); + UpdateFromEnv(kSpanEventCountLimitEnv, limits.event_count_limit); + UpdateFromEnv(kSpanLinkCountLimitEnv, limits.link_count_limit); + UpdateFromEnv(kEventAttributeCountLimitEnv, limits.event_attribute_count_limit); + UpdateFromEnv(kLinkAttributeCountLimitEnv, limits.link_attribute_count_limit); + + return limits; +} + +} // namespace span_limits_env +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/tracer_provider_factory.cc b/sdk/src/trace/tracer_provider_factory.cc index 06f792c659..6ae418430c 100644 --- a/sdk/src/trace/tracer_provider_factory.cc +++ b/sdk/src/trace/tracer_provider_factory.cc @@ -71,7 +71,7 @@ std::unique_ptr TracerProviderFactory 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::move(tracer_configurator), span_limits_env::GetSpanLimitsFromEnv()); } std::unique_ptr TracerProviderFactory::Create( @@ -135,7 +135,7 @@ std::unique_ptr TracerProviderFactory 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::move(tracer_configurator), span_limits_env::GetSpanLimitsFromEnv()); } std::unique_ptr TracerProviderFactory::Create( diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index 66efbbf522..080a110988 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #include #include @@ -37,6 +38,12 @@ #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/tracer.h" +#if defined(_MSC_VER) +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#endif + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include # include @@ -569,6 +576,115 @@ TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreate) EXPECT_EQ(stored.link_attribute_count_limit, limits.link_attribute_count_limit); } +namespace +{ + +void UnsetSpanLimitsEnv() +{ + unsetenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"); + unsetenv("OTEL_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"); + unsetenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_EVENT_COUNT_LIMIT"); + unsetenv("OTEL_SPAN_LINK_COUNT_LIMIT"); + unsetenv("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"); + unsetenv("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"); +} + +} // namespace + +TEST(TracerProvider, SpanLimitsFromEnvUnsetIsNoLimits) +{ + UnsetSpanLimitsEnv(); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + const SpanLimits no_limits = SpanLimits::NoLimits(); + EXPECT_EQ(limits.attribute_count_limit, no_limits.attribute_count_limit); + EXPECT_EQ(limits.attribute_value_length_limit, no_limits.attribute_value_length_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); +} + +TEST(TracerProvider, SpanLimitsFromEnvReadsVariables) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "100", 1); + setenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "5", 1); + setenv("OTEL_SPAN_EVENT_COUNT_LIMIT", "3", 1); + setenv("OTEL_SPAN_LINK_COUNT_LIMIT", "2", 1); + setenv("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", "4", 1); + setenv("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", "6", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 100u); + EXPECT_EQ(limits.attribute_count_limit, 5u); + EXPECT_EQ(limits.event_count_limit, 3u); + EXPECT_EQ(limits.link_count_limit, 2u); + EXPECT_EQ(limits.event_attribute_count_limit, 4u); + EXPECT_EQ(limits.link_attribute_count_limit, 6u); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsFromEnvCustomFallbackDefaults) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_SPAN_EVENT_COUNT_LIMIT", "42", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(SpanLimits{}); + EXPECT_EQ(limits.event_count_limit, 42u); + EXPECT_EQ(limits.attribute_count_limit, 128u); + EXPECT_EQ(limits.link_count_limit, 128u); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsFromEnvGeneralAttributeVariablesApply) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "50", 1); + setenv("OTEL_ATTRIBUTE_COUNT_LIMIT", "10", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 50u); + EXPECT_EQ(limits.attribute_count_limit, 10u); + EXPECT_EQ(limits.event_count_limit, (std::numeric_limits::max)()); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsFromEnvSpanSpecificTakesPrecedence) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "50", 1); + setenv("OTEL_ATTRIBUTE_COUNT_LIMIT", "10", 1); + setenv("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "100", 1); + setenv("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "20", 1); + + const SpanLimits limits = span_limits_env::GetSpanLimitsFromEnv(); + EXPECT_EQ(limits.attribute_value_length_limit, 100u); + EXPECT_EQ(limits.attribute_count_limit, 20u); + + UnsetSpanLimitsEnv(); +} + +TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefaultReadsEnv) +{ + UnsetSpanLimitsEnv(); + setenv("OTEL_SPAN_EVENT_COUNT_LIMIT", "42", 1); + + auto provider = TracerProviderFactory::Create(std::make_unique(nullptr)); + + const auto &limits = provider->GetSpanLimits(); + EXPECT_EQ(limits.event_count_limit, 42u); + EXPECT_EQ(limits.attribute_count_limit, (std::numeric_limits::max)()); + EXPECT_EQ(limits.link_count_limit, (std::numeric_limits::max)()); + + UnsetSpanLimitsEnv(); +} + TEST(TracerProvider, SpanLimitsTracerProviderFactoryCreateDefault) { auto provider = TracerProviderFactory::Create(std::make_unique(nullptr)); From fc8f725e468b8390301353e3d130117f1f4ef8ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:07:38 +0200 Subject: [PATCH 59/61] Bump actions/checkout from 7.0.0 to 7.0.1 (#4271) Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 4 +- .github/workflows/ci.yml | 108 +++++++++++------------ .github/workflows/clang-tidy.yaml | 2 +- .github/workflows/cmake_install.yml | 20 ++--- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/cppcheck.yml | 2 +- .github/workflows/dependencies_image.yml | 2 +- .github/workflows/doxygen-tidy.yaml | 2 +- .github/workflows/fossa.yml | 2 +- .github/workflows/iwyu.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- 11 files changed, 74 insertions(+), 74 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 61ba188891..54a452fc70 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,7 +17,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -59,7 +59,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # main March 2025 with: name: benchmark_results diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1983f747fd..80ad535bd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: # name: CMake test arm64 (with modern protobuf,grpc and abseil) # runs-on: actuated-arm64-4cpu-16gb # steps: -# - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # with: # submodules: 'recursive' # - name: setup @@ -42,7 +42,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -64,7 +64,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -87,7 +87,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'false' - name: checkout googletest @@ -117,7 +117,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -155,7 +155,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -193,7 +193,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -231,7 +231,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -262,7 +262,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -293,7 +293,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -329,7 +329,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -360,7 +360,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -388,7 +388,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -406,7 +406,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -426,7 +426,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -446,7 +446,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -472,7 +472,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -490,7 +490,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -517,7 +517,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -541,7 +541,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -565,7 +565,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -599,7 +599,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -623,7 +623,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -657,7 +657,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -686,7 +686,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -708,7 +708,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -730,7 +730,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -754,7 +754,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -778,7 +778,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -804,7 +804,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -830,7 +830,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -856,7 +856,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -882,7 +882,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -908,7 +908,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -934,7 +934,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -960,7 +960,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -986,7 +986,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Install CMake @@ -1013,7 +1013,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Mount Bazel Cache @@ -1046,7 +1046,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: setup run: sudo ./ci/install_format_tools.sh - name: run tests @@ -1061,7 +1061,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: check copyright run: ./tools/check_copyright.sh @@ -1074,7 +1074,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1094,7 +1094,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1116,7 +1116,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1136,7 +1136,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1156,7 +1156,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1174,7 +1174,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1204,7 +1204,7 @@ jobs: egress-policy: audit - name: check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: install markdownlint-cli run: sudo npm install -g markdownlint-cli@0.46.0 @@ -1221,7 +1221,7 @@ jobs: egress-policy: audit - name: check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: install shellcheck run: sudo apt install --assume-yes shellcheck - name: run shellcheck @@ -1236,7 +1236,7 @@ jobs: egress-policy: audit - name: check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: install misspell run: | curl -L -o ./install-misspell.sh https://git.io/misspell @@ -1253,7 +1253,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: install docfx run: choco install docfx -y --version=2.58.5 - name: run ./ci/docfx.cmd @@ -1270,7 +1270,7 @@ jobs: egress-policy: audit - name: Checkout open-telemetry/opentelemetry-cpp - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1288,7 +1288,7 @@ jobs: cd $HOME/build/ext/test/w3c_tracecontext_http_test_server ./w3c_tracecontext_http_test_server & - name: Checkout w3c/trace-context repo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: w3c/trace-context path: trace-context @@ -1314,7 +1314,7 @@ jobs: egress-policy: audit - name: Checkout open-telemetry/opentelemetry-cpp - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: setup @@ -1332,7 +1332,7 @@ jobs: cd $HOME/build/ext/test/w3c_tracecontext_http_test_server ./w3c_tracecontext_http_test_server & - name: Checkout w3c/trace-context repo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: w3c/trace-context path: trace-context diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index d667747b93..8fb2de5f0e 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: recursive diff --git a/.github/workflows/cmake_install.yml b/.github/workflows/cmake_install.yml index c8c5ccbe40..7bc345707f 100644 --- a/.github/workflows/cmake_install.yml +++ b/.github/workflows/cmake_install.yml @@ -23,7 +23,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Build dependencies with vcpkg submodule @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Build dependencies with vcpkg submodule @@ -67,7 +67,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Setup CI Environment @@ -99,7 +99,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Setup CI Environment @@ -129,7 +129,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Setup CI Environment @@ -162,7 +162,7 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Setup CI Environment @@ -195,7 +195,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Free Disk Space @@ -234,7 +234,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Free Disk Space @@ -274,7 +274,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Install CMake @@ -306,7 +306,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Install Dependencies with Homebrew diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 739c723e8f..589a962a9d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -28,7 +28,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' - name: Install dependencies diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index 93a857bfea..0b4ce7d0b6 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' diff --git a/.github/workflows/dependencies_image.yml b/.github/workflows/dependencies_image.yml index db6e9837f6..66db0b72c1 100644 --- a/.github/workflows/dependencies_image.yml +++ b/.github/workflows/dependencies_image.yml @@ -19,7 +19,7 @@ jobs: - name: checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 diff --git a/.github/workflows/doxygen-tidy.yaml b/.github/workflows/doxygen-tidy.yaml index 404e8170d4..42cb00dbd2 100644 --- a/.github/workflows/doxygen-tidy.yaml +++ b/.github/workflows/doxygen-tidy.yaml @@ -17,7 +17,7 @@ jobs: DOXYFILE_FOLDER: 'docs/public' DOXYFILE_NAME: 'Doxyfile.lint' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install doxygen run: | sudo apt-get update diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 870f1e0f1e..d8b0abe388 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -17,7 +17,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: fossas/fossa-action@29693cc50323968e039056be419b32989fc5880c # v2.0.0 with: diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index 11946dc0a5..3076fd2683 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -30,7 +30,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: 'recursive' diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 319fdcfea1..45a8237b8e 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false From 54593ad2b11c1a850882fadb14cb13d7aea99504 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Wed, 22 Jul 2026 03:32:34 +0530 Subject: [PATCH 60/61] address review: warn and default to 1.0 on invalid ratio, avoid trace state allocations --- .../sdk/trace/samplers/probability.h | 10 +++-- .../sdk/trace/samplers/probability_factory.h | 3 ++ sdk/src/trace/samplers/probability.cc | 45 ++++++++++++------- sdk/test/trace/probability_sampler_test.cc | 28 +++++++----- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h index c1e57510dc..66eba369bc 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/probability.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h @@ -26,9 +26,11 @@ class ProbabilitySampler : public Sampler { public: /** - * @param ratio a required value, 1.0 >= ratio >= 0.0. If the randomness - * value of the span is greater than or equal to the rejection threshold - * derived from the ratio, ShouldSample will return RECORD_AND_SAMPLE. + * @param ratio the sampling probability, in range [0.0, 1.0]. Values + * outside the range (including NaN) log a warning and fall back to the + * default of 1.0. If the randomness value of the span is greater than or + * equal to the rejection threshold derived from the ratio, ShouldSample + * will return RECORD_AND_SAMPLE. */ explicit ProbabilitySampler(double ratio); @@ -52,7 +54,7 @@ class ProbabilitySampler : public Sampler private: std::string description_; - const uint64_t threshold_; + uint64_t threshold_; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h index 4dd573d62f..16e7a37cd6 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h @@ -22,6 +22,9 @@ class ProbabilitySamplerFactory public: /** * Create a ProbabilitySampler. + * @param ratio the sampling probability, in range [0.0, 1.0]. Values + * outside the range (including NaN) log a warning and fall back to the + * default of 1.0. */ static std::unique_ptr Create(double ratio); }; diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index 003642837f..3ed113544a 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -23,13 +23,16 @@ namespace trace_api = opentelemetry::trace; namespace { -// Clamps ratio to [0, 1]. NaN and negatives map to 0 (never-sample). -double ClampProbability(double ratio) noexcept +// Returns ratio when it is inside [0.0, 1.0]; otherwise (including NaN) logs a +// warning and returns 1.0, the default of the configuration specification. +double ValidateRatio(double ratio) noexcept { - if (!(ratio > 0.0)) - return 0.0; - if (ratio > 1.0) + if (!(ratio >= 0.0 && ratio <= 1.0)) + { + OTEL_INTERNAL_LOG_WARN("[ProbabilitySampler] ratio " + << ratio << " is outside [0.0, 1.0], using the default 1.0"); return 1.0; + } return ratio; } } // namespace @@ -41,9 +44,11 @@ namespace trace { ProbabilitySampler::ProbabilitySampler(double ratio) - : description_("ProbabilitySampler{" + std::to_string(ClampProbability(ratio)) + "}"), - threshold_(CalculateThreshold(ClampProbability(ratio))) -{} +{ + ratio = ValidateRatio(ratio); + description_ = "ProbabilitySampler{" + std::to_string(ratio) + "}"; + threshold_ = CalculateThreshold(ratio); +} SamplingResult ProbabilitySampler::ShouldSample( const trace_api::SpanContext &parent_context, @@ -53,16 +58,19 @@ SamplingResult ProbabilitySampler::ShouldSample( const opentelemetry::common::KeyValueIterable & /*attributes*/, const trace_api::SpanContextKeyValueIterable & /*links*/) noexcept { - auto parent_trace_state = parent_context.trace_state(); - if (!parent_trace_state) + const auto &parent_trace_state = parent_context.trace_state(); + const bool has_parent_entries = parent_trace_state && !parent_trace_state->Empty(); + + OtelTraceState ot_state; + if (has_parent_entries) { - parent_trace_state = trace_api::TraceState::GetDefault(); + std::string ot_value; + if (parent_trace_state->Get(kOtTraceStateKey, ot_value)) + { + ot_state = OtelTraceState::Parse(ot_value); + } } - std::string ot_value; - parent_trace_state->Get(kOtTraceStateKey, ot_value); - OtelTraceState ot_state = OtelTraceState::Parse(ot_value); - bool drop = threshold_ == kMaxThreshold; if (!drop) { @@ -104,9 +112,14 @@ SamplingResult ProbabilitySampler::ShouldSample( } std::string new_ot_value = ot_state.Serialize(); - auto trace_state = parent_trace_state->Delete(kOtTraceStateKey); + auto trace_state = + has_parent_entries ? parent_trace_state->Delete(kOtTraceStateKey) : parent_trace_state; if (!new_ot_value.empty()) { + if (!trace_state) + { + trace_state = trace_api::TraceState::GetDefault(); + } trace_state = trace_state->Set(kOtTraceStateKey, new_ot_value); } diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index 7d4d67dcbe..18b6777508 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -135,6 +135,20 @@ TEST(ProbabilitySampler, ShouldSampleNever) ASSERT_FALSE(sampling_result.trace_state->Get("ot", ot_value)); } +TEST(ProbabilitySampler, InvalidRatioFallsBackToDefault) +{ + // Out-of-range ratios (including NaN) fall back to the default of 1.0. + for (double ratio : {-0.5, 1.5, std::nan("")}) + { + ProbabilitySampler s1(ratio); + ASSERT_EQ("ProbabilitySampler{1.000000}", s1.GetDescription()); + + auto sampling_result = + SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), TraceIdWithRandomness(0)); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + } +} + TEST(ProbabilitySampler, ShouldSampleAtThreshold) { // For ratio 0.5 the rejection threshold is 2^55 (0x80000000000000). @@ -472,18 +486,8 @@ TEST(ProbabilitySampler, GetDescription) ASSERT_EQ("ProbabilitySampler{1.000000}", s4.GetDescription()); ProbabilitySampler s5(-3.00); - ASSERT_EQ("ProbabilitySampler{0.000000}", s5.GetDescription()); + ASSERT_EQ("ProbabilitySampler{1.000000}", s5.GetDescription()); ProbabilitySampler s6(std::nan("")); - ASSERT_EQ("ProbabilitySampler{0.000000}", s6.GetDescription()); -} - -TEST(ProbabilitySampler, ShouldSampleNeverWithNaN) -{ - ProbabilitySampler s1(std::nan("")); - - auto sampling_result = SampleWithContext(s1, trace_api::SpanContext::GetInvalid(), - TraceIdWithRandomness(0xffffffffffffff)); - - ASSERT_EQ(Decision::DROP, sampling_result.decision); + ASSERT_EQ("ProbabilitySampler{1.000000}", s6.GetDescription()); } From d329a452075a087cf17dbcf1e7386bf2d2b6c41e Mon Sep 17 00:00:00 2001 From: ayush-that Date: Wed, 22 Jul 2026 04:13:10 +0530 Subject: [PATCH 61/61] preserve inherited ot subkeys on tracestate overflow and enforce the 2^-56 minimum ratio --- .../sdk/trace/samplers/probability.h | 10 ++--- .../sdk/trace/samplers/probability_factory.h | 6 +-- sdk/src/trace/samplers/ot_trace_state.cc | 45 +++++++++++++------ sdk/src/trace/samplers/probability.cc | 16 ++++--- sdk/test/trace/composable_sampler_test.cc | 10 ++--- sdk/test/trace/probability_sampler_test.cc | 24 +++++----- 6 files changed, 66 insertions(+), 45 deletions(-) diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h index 66eba369bc..0b8ffe4b8b 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/probability.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability.h @@ -26,11 +26,11 @@ class ProbabilitySampler : public Sampler { public: /** - * @param ratio the sampling probability, in range [0.0, 1.0]. Values - * outside the range (including NaN) log a warning and fall back to the - * default of 1.0. If the randomness value of the span is greater than or - * equal to the rejection threshold derived from the ratio, ShouldSample - * will return RECORD_AND_SAMPLE. + * @param ratio the sampling probability: either 0 (never sample) or a + * value in [2^-56, 1.0]. Other values (including NaN) log a warning and + * fall back to the default of 1.0. If the randomness value of the span is + * greater than or equal to the rejection threshold derived from the ratio, + * ShouldSample will return RECORD_AND_SAMPLE. */ explicit ProbabilitySampler(double ratio); diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h index 16e7a37cd6..987964fa67 100644 --- a/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h +++ b/sdk/include/opentelemetry/sdk/trace/samplers/probability_factory.h @@ -22,9 +22,9 @@ class ProbabilitySamplerFactory public: /** * Create a ProbabilitySampler. - * @param ratio the sampling probability, in range [0.0, 1.0]. Values - * outside the range (including NaN) log a warning and fall back to the - * default of 1.0. + * @param ratio the sampling probability: either 0 (never sample) or a + * value in [2^-56, 1.0]. Other values (including NaN) log a warning and + * fall back to the default of 1.0. */ static std::unique_ptr Create(double ratio); }; diff --git a/sdk/src/trace/samplers/ot_trace_state.cc b/sdk/src/trace/samplers/ot_trace_state.cc index 756dab7841..e8ffa48f4a 100644 --- a/sdk/src/trace/samplers/ot_trace_state.cc +++ b/sdk/src/trace/samplers/ot_trace_state.cc @@ -3,12 +3,14 @@ #include "ot_trace_state.h" +#include #include #include #include #include #include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/common/global_log_handler.h" #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/version.h" @@ -172,33 +174,48 @@ OtelTraceState OtelTraceState::Parse(const std::string &ot_value) noexcept std::string OtelTraceState::Serialize() const { - std::string out; - if (has_threshold && threshold < kMaxThreshold) + // Inherited sub-keys are never dropped (the tracestate spec requires + // preserving existing OpenTelemetry concerns); when adding "th" would push + // the value past the 256 char limit, the new threshold is omitted instead. + std::string rest; + if (has_random_value) { - out.append("th:"); - AppendThresholdHex(out, threshold); + rest.append("rv:"); + AppendRandomHex(rest, random_value); } - if (has_random_value) + for (const auto &pair : other_subkeys) { - if (!out.empty()) + if (!rest.empty()) { - out.push_back(';'); + rest.push_back(';'); } - out.append("rv:"); - AppendRandomHex(out, random_value); + rest.append(pair); } - for (const auto &pair : other_subkeys) + + std::string out; + if (has_threshold && threshold < kMaxThreshold) { - std::size_t extra = out.empty() ? pair.size() : pair.size() + 1; - if (out.size() + extra > 256) + out.append("th:"); + AppendThresholdHex(out, threshold); + if (out.size() + (rest.empty() ? 0 : rest.size() + 1) > 256) { - break; + static std::atomic warned{false}; + if (!warned.exchange(true)) + { + OTEL_INTERNAL_LOG_WARN( + "[OtelTraceState] omitting th: recording it would exceed the 256 character tracestate " + "value limit"); + } + out.clear(); } + } + if (!rest.empty()) + { if (!out.empty()) { out.push_back(';'); } - out.append(pair); + out.append(rest); } return out; } diff --git a/sdk/src/trace/samplers/probability.cc b/sdk/src/trace/samplers/probability.cc index 3ed113544a..f2597ebd7b 100644 --- a/sdk/src/trace/samplers/probability.cc +++ b/sdk/src/trace/samplers/probability.cc @@ -23,17 +23,19 @@ namespace trace_api = opentelemetry::trace; namespace { -// Returns ratio when it is inside [0.0, 1.0]; otherwise (including NaN) logs a -// warning and returns 1.0, the default of the configuration specification. +// Valid ratios are 0 (never sample) and [2^-56, 1.0]; 2^-56 is the smallest +// probability expressible with a 56-bit threshold. Anything else (including +// NaN) logs a warning and returns 1.0, the default of the configuration +// specification. double ValidateRatio(double ratio) noexcept { - if (!(ratio >= 0.0 && ratio <= 1.0)) + if (ratio == 0.0 || (ratio >= 0x1p-56 && ratio <= 1.0)) { - OTEL_INTERNAL_LOG_WARN("[ProbabilitySampler] ratio " - << ratio << " is outside [0.0, 1.0], using the default 1.0"); - return 1.0; + return ratio; } - return ratio; + OTEL_INTERNAL_LOG_WARN("[ProbabilitySampler] ratio " + << ratio << " is not 0 or within [2^-56, 1.0], using the default 1.0"); + return 1.0; } } // namespace diff --git a/sdk/test/trace/composable_sampler_test.cc b/sdk/test/trace/composable_sampler_test.cc index 13a4415393..e67a71c08a 100644 --- a/sdk/test/trace/composable_sampler_test.cc +++ b/sdk/test/trace/composable_sampler_test.cc @@ -348,18 +348,18 @@ TEST(ComposableSampler, PreservesOtherSubkeysAndIgnoresInvalidValues) EXPECT_NE(std::string::npos, ot.find("keep:me")); } -TEST(ComposableSampler, DropsTrailingSubkeysOverSizeLimit) +TEST(ComposableSampler, ThresholdOmittedOverSizeLimit) { auto sampler = CompositeSamplerFactory::Create(std::make_shared()); - // Fits in 256 on input, but once th:0 is prepended the trailing subkey no - // longer fits and is dropped. + // Fits in 256 on input, but once th:0 is prepended the value no longer + // fits; the inherited subkeys must be preserved, so th is omitted. std::string parent_ot = "rv:ffffffffffffff;x:" + std::string(236, 'a'); auto parent = MakeParent(false, parent_ot); opentelemetry::sdk::trace::SamplingResult result; EXPECT_EQ(Decision::RECORD_AND_SAMPLE, Sample(*sampler, parent, MakeTraceId(0x00), &result)); std::string ot = OtOf(result); - EXPECT_NE(std::string::npos, ot.find("th:0")); - EXPECT_EQ(std::string::npos, ot.find("x:aaa")); + EXPECT_EQ(std::string::npos, ot.find("th:")); + EXPECT_EQ(parent_ot, ot); } TEST(ComposableSampler, RatioClampedToValidRange) diff --git a/sdk/test/trace/probability_sampler_test.cc b/sdk/test/trace/probability_sampler_test.cc index 18b6777508..c34ef317f0 100644 --- a/sdk/test/trace/probability_sampler_test.cc +++ b/sdk/test/trace/probability_sampler_test.cc @@ -137,8 +137,9 @@ TEST(ProbabilitySampler, ShouldSampleNever) TEST(ProbabilitySampler, InvalidRatioFallsBackToDefault) { - // Out-of-range ratios (including NaN) fall back to the default of 1.0. - for (double ratio : {-0.5, 1.5, std::nan("")}) + // Invalid ratios (out of range, below the 2^-56 minimum, or NaN) fall back + // to the default of 1.0. + for (double ratio : {-0.5, 1.5, 0x1p-57, std::nan("")}) { ProbabilitySampler s1(ratio); ASSERT_EQ("ProbabilitySampler{1.000000}", s1.GetDescription()); @@ -364,7 +365,7 @@ TEST(ProbabilitySampler, FullTraceStateKeepsParentTraceState) ASSERT_EQ("0", k0_value); } -TEST(ProbabilitySampler, OversizedSubKeyDroppedToRecordThreshold) +TEST(ProbabilitySampler, ThresholdOmittedWhenSubKeyWouldOverflow) { ProbabilitySampler s1(0.5); @@ -373,8 +374,8 @@ TEST(ProbabilitySampler, OversizedSubKeyDroppedToRecordThreshold) uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; trace_api::SpanId span_id{span_id_buffer}; - // A sampled span must record th; the foreign sub-key would push the ot value - // past the 256 char limit, so it is dropped to make room for th. + // Adding th would push the ot value past the 256 char limit; the inherited + // foreign sub-key must be preserved, so th is omitted. std::string ot_value = "a:" + std::string(253, 'b'); auto trace_state = trace_api::TraceState::FromHeader("ot=" + ot_value); trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); @@ -386,10 +387,10 @@ TEST(ProbabilitySampler, OversizedSubKeyDroppedToRecordThreshold) std::string result_ot; ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); - ASSERT_EQ("th:8", result_ot); + ASSERT_EQ(ot_value, result_ot); } -TEST(ProbabilitySampler, OversizedSubKeyDroppedReplacesStaleThreshold) +TEST(ProbabilitySampler, StaleThresholdRemovedWhenSubKeyWouldOverflow) { ProbabilitySampler s1(0.1); @@ -398,8 +399,9 @@ TEST(ProbabilitySampler, OversizedSubKeyDroppedReplacesStaleThreshold) uint8_t span_id_buffer[trace_api::SpanId::kSize] = {1}; trace_api::SpanId span_id{span_id_buffer}; - // The inherited th is replaced by this sampler's th; the foreign sub-key - // overflows the 256 char limit and is dropped, keeping the fresh th. + // The inherited th is stale and must go; this sampler's own th does not fit + // next to the foreign sub-key, which must be preserved, so no th is + // recorded. std::string rest = "x:" + std::string(245, 'b'); auto trace_state = trace_api::TraceState::FromHeader("ot=th:8;" + rest); trace_api::SpanContext context(trace_id, span_id, trace_api::TraceFlags{0}, false, trace_state); @@ -411,8 +413,8 @@ TEST(ProbabilitySampler, OversizedSubKeyDroppedReplacesStaleThreshold) std::string result_ot; ASSERT_TRUE(sampling_result.trace_state->Get("ot", result_ot)); - ASSERT_NE(std::string::npos, result_ot.find("th:")); - ASSERT_EQ(std::string::npos, result_ot.find("x:")); + ASSERT_EQ(std::string::npos, result_ot.find("th:")); + ASSERT_EQ(rest, result_ot); } TEST(ProbabilitySampler, IgnoresParentSampledFlag)