Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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 @@ -34,6 +34,9 @@ Increment the:
to compile standalone on newer standard library implementations.
[#4574](https://github.com/open-telemetry/opentelemetry-cpp/pull/4574)

* [SDK] Add Entity support to Resource
[#3652](https://github.com/open-telemetry/opentelemetry-cpp/issues/3652)

## [1.29.0] 2026-09-13

* [RELEASE] Bump main branch to 1.29.0-dev (#4259)
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
Loading