Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e244122
[SDK] LogRecord attribute limits enforcement
thc1006 Jun 14, 2026
1612edc
[SDK] LogRecord limits: address review (store by value, OTLP UTF-8-aw…
thc1006 Jun 17, 2026
c4532cd
[SDK] LogRecord limits: address dbarker review (extract utilities, de…
thc1006 Jun 19, 2026
3144991
[SDK] LogRecord limits: fix noexcept correctness + restore Bazel link
thc1006 Jun 19, 2026
ccdd5e9
Merge branch 'main' into feat/log-record-limits-4126
dbarker Jun 20, 2026
8c965e7
[SDK] LogRecord limits: fix MSVC variable shadow + drop dead includes…
thc1006 Jun 21, 2026
5588f2c
Merge branch 'main' into feat/log-record-limits-4126
dbarker Jun 23, 2026
cd413f8
[SDK] LogRecord limits: address dbarker v3 review (truncate during co…
thc1006 Jun 23, 2026
6c68257
Merge branch 'main' into feat/log-record-limits-4126
ThomsonTan Jun 25, 2026
3e32fd7
[SDK] LogRecord limits: gate SetLogRecordLimits on processor capability
thc1006 Jun 25, 2026
0977ddd
[SDK] LogRecord limits: UTF-8-safe truncation in AttributeConverter
thc1006 Jun 25, 2026
3be2379
[SDK] LogRecord limits: satisfy the clang-tidy warning limit
thc1006 Jun 26, 2026
622d670
[SDK] LogRecord limits: default recordable to no limits, inject via p…
thc1006 Jun 26, 2026
9627722
Merge branch 'main' into feat/log-record-limits-4126
dbarker Jun 26, 2026
980ef8e
[SDK] LogRecord limits: address dbarker review (comments, UTF-8 scan …
thc1006 Jun 27, 2026
0ac2cf3
Merge branch 'main' into feat/log-record-limits-4126
dbarker Jun 29, 2026
378442f
[SDK] LogRecord limits: drop vtable implementation-detail comment
thc1006 Jun 30, 2026
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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)

Expand Down
17 changes: 17 additions & 0 deletions exporters/otlp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
10 changes: 10 additions & 0 deletions exporters/otlp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 referenced limits object must
* outlive this recordable.
*/
void SetLogRecordLimits(
const opentelemetry::sdk::logs::LogRecordLimits &limits) noexcept override;

/**
* Set Resource of this log
* @param Resource the resource to set
Expand All @@ -114,6 +123,10 @@ 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. The default-constructed
// value carries the spec defaults (count=128, length=unlimited).
opentelemetry::sdk::logs::LogRecordLimits limits_{};
};

} // namespace otlp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#pragma once

#include <cstddef>
#include <string>

#include "opentelemetry/common/attribute_value.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/sdk/common/attribute_utils.h"
Expand Down Expand Up @@ -72,6 +75,28 @@ class OtlpPopulateAttributeUtils
nostd::string_view key,
const opentelemetry::sdk::common::OwnedAttributeValue &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 std::string &value, std::size_t max_bytes) noexcept;

/**
* Truncate the `string_value` and `bytes_value` (and recursively into
* `array_value` elements) of an OTLP `AnyValue` to at most `max_length`
* bytes each. `string_value` uses Utf8SafePrefixLength so the resulting
* protobuf string stays valid UTF-8 when the input was. `bytes_value` is
* cut at the raw byte boundary since it is not UTF-8 text.
*/
static void TruncateProtoAttributeValue(opentelemetry::proto::common::v1::AnyValue *value,
std::size_t max_length) noexcept;
};

} // namespace otlp
Expand Down
25 changes: 24 additions & 1 deletion exporters/otlp/src/otlp_log_recordable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/exporters/otlp/otlp_log_recordable.h"
#include <cstddef>
#include <limits>
#include "opentelemetry/common/attribute_value.h"
#include "opentelemetry/common/timestamp.h"
#include "opentelemetry/exporters/otlp/otlp_populate_attribute_utils.h"
#include "opentelemetry/logs/severity.h"
#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"
Expand All @@ -18,6 +21,7 @@

// clang-format off
#include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep
#include "opentelemetry/proto/common/v1/common.pb.h"
#include "opentelemetry/proto/logs/v1/logs.pb.h"
#include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep
// clang-format on
Expand Down Expand Up @@ -236,7 +240,26 @@ 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<std::size_t>(proto_record_.attributes_size()) >= limits_.attribute_count_limit)
{
proto_record_.set_dropped_attributes_count(proto_record_.dropped_attributes_count() + 1);
return;
}

auto *kv = proto_record_.add_attributes();
OtlpPopulateAttributeUtils::PopulateAttribute(kv, key, value, true);

if (limits_.attribute_value_length_limit != (std::numeric_limits<std::size_t>::max)())
{
OtlpPopulateAttributeUtils::TruncateProtoAttributeValue(kv->mutable_value(),
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
Expand Down
89 changes: 89 additions & 0 deletions exporters/otlp/src/otlp_populate_attribute_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,95 @@ void OtlpPopulateAttributeUtils::PopulateAttribute(
}
}

// UTF-8 byte layout (https://en.wikipedia.org/wiki/UTF-8#Description):
// 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
std::size_t OtlpPopulateAttributeUtils::Utf8SafePrefixLength(const std::string &value,
std::size_t max_bytes) noexcept
{
std::size_t i = 0;
while (i < value.size())
{
const auto lead = static_cast<unsigned char>(value[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 > value.size())
{
seq = 1;
}
else
{
for (std::size_t k = 1; k < seq; ++k)
{
const auto continuation = static_cast<unsigned char>(value[i + k]);
if (continuation < 0x80 || continuation > 0xBF)
{
seq = 1;
break;
}
}
}
}
if (i + seq > max_bytes)
{
break;
}
i += seq;
}
return i;
}

// NOLINTBEGIN(misc-no-recursion)
// The recursion is intentional and bounded by the depth of the AnyValue
// array_value tree, which is constructed by the SDK from a fixed-depth
// AttributeValue variant. The codebase precedent for this NOLINT is in
// sdk/src/configuration/configuration_parser.cc.
void OtlpPopulateAttributeUtils::TruncateProtoAttributeValue(
opentelemetry::proto::common::v1::AnyValue *value,
std::size_t max_length) noexcept
{
if (value == nullptr)
{
return;
}
if (value->value_case() == proto::common::v1::AnyValue::kStringValue)
{
if (value->string_value().size() > max_length)
{
value->mutable_string_value()->resize(
Utf8SafePrefixLength(value->string_value(), max_length));
}
return;
}
if (value->value_case() == proto::common::v1::AnyValue::kBytesValue)
{
if (value->bytes_value().size() > max_length)
{
value->mutable_bytes_value()->resize(max_length);
}
return;
}
if (value->value_case() == proto::common::v1::AnyValue::kArrayValue)
{
auto *array = value->mutable_array_value();
for (int i = 0; i < array->values_size(); ++i)
{
TruncateProtoAttributeValue(array->mutable_values(i), max_length);
Comment thread
dbarker marked this conversation as resolved.
Outdated
}
}
}
// NOLINTEND(misc-no-recursion)

} // namespace otlp
} // namespace exporter
OPENTELEMETRY_END_NAMESPACE
Loading
Loading