Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
838a3e7
[RESOURCE] Add Entity value type
shashankxrm Aug 26, 2026
be6372e
[RESOURCE] Add Entity storage to Resource
shashankxrm Aug 26, 2026
8d25553
[RESOURCE] Add Entity support to Resource
shashankxrm Aug 26, 2026
f49b062
[RESOURCE] Add entity-aware Merge and Create support
shashankxrm Aug 26, 2026
e167baf
Add Entity support entry to CHANGELOG
shashankxrm Aug 27, 2026
fa79664
Update CHANGELOG.md to remove empty space under the update
shashankxrm Aug 27, 2026
3bac7cc
Update CMakeLists.txt for linespace adjustment
shashankxrm Aug 27, 2026
8a7792e
Update resource.cc to make insertion function more efficient
shashankxrm Aug 27, 2026
44e4f4d
Update resource.cc emplace function
shashankxrm Aug 27, 2026
6696d2f
[RESOURCE] Fix CI formatting and static analysis issues
shashankxrm Aug 27, 2026
1e9bc31
Update resource_test.cc for placement change of include statement
shashankxrm Aug 27, 2026
7a749a2
[RESOURCE] Fix clang-tidy copy warnings
shashankxrm Aug 28, 2026
90a9b09
Merge branch 'main' into fix/resource-entity-3652
dbarker Sep 4, 2026
8fbcaab
Fix Entity constructor and special members
shashankxrm Sep 5, 2026
721fd24
Document Resource merge algorithms
shashankxrm Sep 8, 2026
8bc70ef
Fix Resource merge ordering for entity schema
shashankxrm Sep 9, 2026
87e3d11
Merge branch 'main' into fix/resource-entity-3652
ThomsonTan Sep 10, 2026
7a0f12b
Optimize attribute-only Resource storage
shashankxrm Sep 10, 2026
40438ca
Merge branch 'main' into fix/resource-entity-3652
ThomsonTan Sep 12, 2026
f4bf097
Normalize integer Entity identities
shashankxrm Sep 13, 2026
4f26c21
Include what you use fix
shashankxrm Sep 13, 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Increment the:

## [Unreleased]

* [SDK] Add Entity support to Resource
[#3652](https://github.com/open-telemetry/opentelemetry-cpp/issues/3652)
* [BUG] Install a curl seek callback so an OTLP/HTTP export body can be rewound
when libcurl restarts an upload, instead of failing with
`CURLE_SEND_FAIL_REWIND` and dropping the batch
Expand Down
50 changes: 50 additions & 0 deletions sdk/include/opentelemetry/sdk/resource/entity.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <string>

#include "opentelemetry/sdk/common/attribute_utils.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace resource
{

using ResourceAttributes = opentelemetry::sdk::common::AttributeMap;

class Entity
{
public:
/**
* Constructs an Entity. Integer identity values (and integer arrays) are
* canonicalized to int64_t when they fit, so C++ integer widths do not
* affect identity equality or merge. Description values are not rewritten.
*/
Entity(const std::string &type,
const ResourceAttributes &identity,
const ResourceAttributes &description = ResourceAttributes{},
const std::string &schema_url = std::string{});

const std::string &GetType() const noexcept;
const ResourceAttributes &GetIdentity() const noexcept;
const ResourceAttributes &GetDescription() const noexcept;
const std::string &GetSchemaURL() const noexcept;

bool IsValid() const noexcept;

bool operator==(const Entity &other) const noexcept;

private:
std::string type_;
ResourceAttributes identity_;
ResourceAttributes description_;
std::string schema_url_;
};

} // namespace resource
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
81 changes: 76 additions & 5 deletions sdk/include/opentelemetry/sdk/resource/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#pragma once

#include <string>
#include <vector>

#include "opentelemetry/sdk/common/attribute_utils.h"
#include "opentelemetry/sdk/resource/entity.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
Expand All @@ -25,6 +27,20 @@ class Resource

Resource(const ResourceAttributes &attributes, const std::string &schema_url) noexcept;

/**
* Constructs a Resource from attributes, a schema URL, and entities.
*
* Invalid entities, later entities of a duplicate type, and entities that
* share attribute keys with a higher-priority (earlier) entity are dropped.
* Keys owned by surviving entities are removed from unassociated attributes.
* If any entity survives, the Resource schema URL is taken from those
* entities (common URL, or empty if they differ); the constructor schema
* URL is used only when no entity survives.
*/
Resource(const ResourceAttributes &attributes,
const std::string &schema_url,
const std::vector<Entity> &entities) noexcept;

Resource(const Resource &) = default;
Resource(Resource &&) = default;
Resource &operator=(const Resource &) = default;
Expand All @@ -34,15 +50,21 @@ class Resource

const ResourceAttributes &GetAttributes() const noexcept;
const std::string &GetSchemaURL() const noexcept;
const std::vector<Entity> &GetEntities() const noexcept;
const ResourceAttributes &GetUnassociatedAttributes() const noexcept;

/**
* Returns a new, merged {@link Resource} by merging the current Resource
* with the other Resource. In case of a collision, the other Resource takes
* precedence.
* (old) with the other Resource (updating). In case of a collision, the
* other Resource takes precedence for unassociated attributes.
*
* The specification notes that if schema urls collide, the resulting
* schema url is implementation-defined. In the C++ implementation, the
* schema url of @p other is picked.
* When neither Resource has entities, attributes and schema URLs follow the
* historical merge rules. If schema urls collide, the resulting schema url
* is implementation-defined; this implementation picks @p other.
*
* When either Resource has entities, merge follows the entity-aware
* resource data model: type-rank, description overlay, updating unassociated
* keys evicting entities, then construction-time key uniqueness.
*
* @param other the Resource that will be merged with this.
* @returns the newly merged Resource.
Expand All @@ -61,6 +83,16 @@ class Resource
static Resource Create(const ResourceAttributes &attributes,
const std::string &schema_url = std::string{});

/**
* Returns a newly created Resource with the specified attributes and
* entities. SDK attributes and OTEL attributes are merged in as with the
* two-argument Create.
*/

static Resource Create(const ResourceAttributes &attributes,
const std::string &schema_url,
const std::vector<Entity> &entities);

/**
* Returns an Empty resource.
*/
Expand All @@ -74,6 +106,45 @@ class Resource
static Resource &GetDefault();

private:
/**
* Normalizes entities supplied at construction time.
*
* Drops invalid entities, duplicate types, and entities whose identity or
* description keys conflict with an already-accepted entity (first wins).
* Stores survivors in `entities_`, removes their attribute keys from
* `unassociated_attributes_`, and updates `schema_url_` when any entity survives
* (common entity schema URL, or empty if they differ).
*/
void NormalizeEntities(const std::vector<Entity> &entities) noexcept;
Comment thread
dbarker marked this conversation as resolved.

/**
* Rebuilds the flattened `attributes_` cache for entity-aware Resources from
* `entities_` (identity and description) followed by
* `unassociated_attributes_`. Entity-free Resources leave the cache empty
* and return `unassociated_attributes_` directly from GetAttributes().
*/
void RefreshFlattenedAttributes() noexcept;

/**
* Legacy resource merge when neither resource contains entities.
*
* Spec: Resource SDK, "Merge behavior without Entities" (since 1.60.0):
* https://opentelemetry.io/docs/specs/otel/resource/sdk/#merge-behavior-without-entities
*/
Resource MergeWithoutEntities(const Resource &other) const noexcept;

/**
* Entity-aware resource merge when either resource contains entities.
*
* Spec: Resource SDK, "Merge behavior with entities" (Development, 1.60.0),
* which requires the resource data model merge algorithm:
* https://opentelemetry.io/docs/specs/otel/resource/sdk/#merge-behavior-with-entities
* https://opentelemetry.io/docs/specs/otel/resource/data-model/#merging-resources
*/
Resource MergeWithEntities(const Resource &other) const noexcept;

std::vector<Entity> entities_;
ResourceAttributes unassociated_attributes_;
ResourceAttributes attributes_;
std::string schema_url_;
};
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/resource/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

add_library(opentelemetry_resources resource.cc resource_detector.cc
add_library(opentelemetry_resources resource.cc resource_detector.cc entity.cc
detail/percent_decode.cc)

set_target_properties(opentelemetry_resources PROPERTIES EXPORT_NAME resources)
Expand Down
142 changes: 142 additions & 0 deletions sdk/src/resource/entity.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/sdk/resource/entity.h"

#include <cstdint>
#include <limits>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "opentelemetry/nostd/variant.h"
#include "opentelemetry/sdk/common/attribute_utils.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace resource
{
namespace
{

namespace nostd = opentelemetry::nostd;
using OwnedAttributeValue = opentelemetry::sdk::common::OwnedAttributeValue;

bool Uint64FitsInt64(std::uint64_t value) noexcept
{
return value <= static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max());
}

template <typename T>
OwnedAttributeValue WidenIntegerArray(const std::vector<T> &src)
{
std::vector<std::int64_t> dst;
dst.reserve(src.size());
for (T value : src)
{
dst.push_back(static_cast<std::int64_t>(value));
}
return OwnedAttributeValue{std::move(dst)};
}

OwnedAttributeValue NormalizeIntegerIdentityValue(const OwnedAttributeValue &value)
{
if (nostd::holds_alternative<std::int32_t>(value))
{
return OwnedAttributeValue{static_cast<std::int64_t>(nostd::get<std::int32_t>(value))};
}
if (nostd::holds_alternative<std::uint32_t>(value))
{
return OwnedAttributeValue{static_cast<std::int64_t>(nostd::get<std::uint32_t>(value))};
}
if (nostd::holds_alternative<std::uint64_t>(value))
{
const std::uint64_t raw = nostd::get<std::uint64_t>(value);
if (Uint64FitsInt64(raw))
{
return OwnedAttributeValue{static_cast<std::int64_t>(raw)};
}
return value;
}
if (nostd::holds_alternative<std::vector<std::int32_t>>(value))
{
return WidenIntegerArray(nostd::get<std::vector<std::int32_t>>(value));
}
if (nostd::holds_alternative<std::vector<std::uint32_t>>(value))
{
return WidenIntegerArray(nostd::get<std::vector<std::uint32_t>>(value));
}
if (nostd::holds_alternative<std::vector<std::uint64_t>>(value))
{
const auto &src = nostd::get<std::vector<std::uint64_t>>(value);
for (std::uint64_t element : src)
{
if (!Uint64FitsInt64(element))
{
return value;
}
}
return WidenIntegerArray(src);
}
return value;
}

void NormalizeIdentityIntegers(ResourceAttributes &identity)
{
for (auto &kv : identity)
{
kv.second = NormalizeIntegerIdentityValue(kv.second);
}
}

} // namespace

Entity::Entity(const std::string &type,
const ResourceAttributes &identity,
const ResourceAttributes &description,
const std::string &schema_url)
: type_(type), identity_(identity), description_(description), schema_url_(schema_url)
{
NormalizeIdentityIntegers(identity_);
for (const auto &kv : identity_)
{
description_.erase(kv.first);
}
}

const std::string &Entity::GetType() const noexcept
{
return type_;
}

const ResourceAttributes &Entity::GetIdentity() const noexcept
{
return identity_;
}

const ResourceAttributes &Entity::GetDescription() const noexcept
{
return description_;
}

const std::string &Entity::GetSchemaURL() const noexcept
{
return schema_url_;
}

bool Entity::IsValid() const noexcept
{
return !type_.empty() && !identity_.empty();
}

bool Entity::operator==(const Entity &other) const noexcept
{
return type_ == other.type_ && identity_ == other.identity_ &&
description_ == other.description_ && schema_url_ == other.schema_url_;
}

} // namespace resource
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
Loading