Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/clang-tidy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
matrix:
include:
- cmake_options: all-options-abiv1-preview
warning_limit: 389
warning_limit: 385
- cmake_options: all-options-abiv2-preview
warning_limit: 395
warning_limit: 391
env:
CC: /usr/bin/clang-22
CXX: /usr/bin/clang++-22
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Increment the:
* [CODE HEALTH] 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)

* [CODE HEALTH] Remove unused alias declarations
[#4091](https://github.com/open-telemetry/opentelemetry-cpp/pull/4091)

Expand Down
31 changes: 23 additions & 8 deletions exporters/otlp/src/otlp_populate_attribute_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#endif

#include <stdint.h>
#include <limits>
#include <string>
#include <utility>
#include <vector>
Expand Down Expand Up @@ -41,6 +42,24 @@ namespace otlp
const int kAttributeValueSize = 16;
const int kOwnedAttributeValueSize = 15;

namespace
{
// Per OpenTelemetry spec, 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)
{
if (val <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))
{
proto_value->set_int_value(static_cast<int64_t>(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,
Expand Down Expand Up @@ -75,8 +94,7 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue(
}
else if (nostd::holds_alternative<uint64_t>(value))
{
proto_value->set_int_value(
nostd::get<uint64_t>(value)); // NOLINT(cppcoreguidelines-narrowing-conversions)
SetUint64Value(proto_value, nostd::get<uint64_t>(value));
}
else if (nostd::holds_alternative<double>(value))
{
Expand Down Expand Up @@ -168,8 +186,7 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue(
auto array_value = proto_value->mutable_array_value();
for (const auto &val : nostd::get<nostd::span<const uint64_t>>(value))
{
array_value->add_values()->set_int_value(
val); // NOLINT(cppcoreguidelines-narrowing-conversions)
SetUint64Value(array_value->add_values(), val);
}
}
else if (nostd::holds_alternative<nostd::span<const double>>(value))
Expand Down Expand Up @@ -235,8 +252,7 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue(
}
else if (nostd::holds_alternative<uint64_t>(value))
{
proto_value->set_int_value(
nostd::get<uint64_t>(value)); // NOLINT(cppcoreguidelines-narrowing-conversions)
SetUint64Value(proto_value, nostd::get<uint64_t>(value));
}
else if (nostd::holds_alternative<double>(value))
{
Expand Down Expand Up @@ -313,8 +329,7 @@ void OtlpPopulateAttributeUtils::PopulateAnyValue(
auto array_value = proto_value->mutable_array_value();
for (const auto &val : nostd::get<std::vector<uint64_t>>(value))
{
array_value->add_values()->set_int_value(
val); // NOLINT(cppcoreguidelines-narrowing-conversions)
SetUint64Value(array_value->add_values(), val);
}
}
else if (nostd::holds_alternative<std::vector<double>>(value))
Expand Down
45 changes: 45 additions & 0 deletions exporters/otlp/test/otlp_recordable_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <chrono>
#include <cstdint>
#include <cstring>
#include <limits>
#include <map>
#include <string>
#include <utility>
Expand Down Expand Up @@ -611,6 +612,50 @@ TYPED_TEST(IntAttributeTest, SetIntArrayAttribute)
}
}

// Per OpenTelemetry spec, uint64_t attribute values exceeding INT64_MAX must be
// encoded as a decimal string rather than wrapping to a negative int64.
// https://opentelemetry.io/docs/specs/otel/common/attribute-type-mapping/#integer-values
TEST(OtlpRecordable, SetUint64OverflowAsStringPerSpec)
{
const uint64_t overflow_val = static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1U;
common::AttributeValue val(overflow_val);
OtlpRecordable rec;
rec.SetAttribute("u64_overflow", val);
EXPECT_EQ(rec.span().attributes(0).value().value_case(),
opentelemetry::proto::common::v1::AnyValue::kStringValue);
EXPECT_EQ(rec.span().attributes(0).value().string_value(), std::to_string(overflow_val));
}

TEST(OtlpRecordable, SetUint64BoundaryAsIntPerSpec)
{
// INT64_MAX boundary still fits int_value (encoding split is val > INT64_MAX).
const uint64_t boundary = static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
common::AttributeValue val(boundary);
OtlpRecordable rec;
rec.SetAttribute("u64_boundary", val);
EXPECT_EQ(rec.span().attributes(0).value().value_case(),
opentelemetry::proto::common::v1::AnyValue::kIntValue);
EXPECT_EQ(rec.span().attributes(0).value().int_value(), std::numeric_limits<int64_t>::max());
}

TEST(OtlpRecordable, SetUint64ArrayOverflowAsStringPerSpec)
{
const uint64_t overflow_val = static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1U;
const uint64_t in_range_val = 42;
const uint64_t arr[] = {in_range_val, overflow_val};
nostd::span<const uint64_t> arr_span(arr, 2);
common::AttributeValue val(arr_span);
OtlpRecordable rec;
rec.SetAttribute("u64_arr_mixed", val);
const auto &array_v = rec.span().attributes(0).value().array_value();
ASSERT_EQ(array_v.values_size(), 2);
EXPECT_EQ(array_v.values(0).value_case(), opentelemetry::proto::common::v1::AnyValue::kIntValue);
EXPECT_EQ(array_v.values(0).int_value(), static_cast<int64_t>(in_range_val));
EXPECT_EQ(array_v.values(1).value_case(),
opentelemetry::proto::common::v1::AnyValue::kStringValue);
EXPECT_EQ(array_v.values(1).string_value(), std::to_string(overflow_val));
}

TEST(OtlpRecordableTest, TestCollectionLimits)
{
// Initialize recordable with strict limits:
Expand Down
Loading