Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ release.

### Entities

- Add specification for communicating entity information as structured log events.
([#4836](https://github.com/open-telemetry/opentelemetry-specification/pull/4836))

### Common

### OpenTelemetry Protocol
Expand Down
1 change: 1 addition & 0 deletions specification/entities/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ Entities can be instantiated through two complementary approaches:

- [Data Model](./data-model.md)
- [Entity Propagation](./entity-propagation.md) - Describes the push-based model for entity instantiation
- [Entity Events](./entity-events.md) - Describes how to communicate entity information as structured log events
281 changes: 281 additions & 0 deletions specification/entities/entity-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
<!--- Hugo front matter used to generate the website version of this page:
linkTitle: Entity Events
weight: 3
--->

# Entity Events

**Status**: [Development](../document-status.md)

<details>
<summary>Table of Contents</summary>

<!-- toc -->

- [Overview](#overview)
- [When to Use Entity Events](#when-to-use-entity-events)
- [Event Types](#event-types)
* [Entity State Event](#entity-state-event)
* [Entity Delete Event](#entity-delete-event)
- [Entity Relationships](#entity-relationships)
* [Relationship Structure](#relationship-structure)
* [Standard Relationship Types](#standard-relationship-types)
* [Relationship Placement](#relationship-placement)
* [Relationship Lifecycle](#relationship-lifecycle)
- [Examples](#examples)
* [Kubernetes Pod Entity State](#kubernetes-pod-entity-state)
* [Entity Delete](#entity-delete)

<!-- tocstop -->

</details>

## Overview

Entity events provide a way to communicate entity information as structured log events.
This approach is complementary to defining entities as part of Resource data (see [Entity Data Model](./data-model.md)).

Entity events are represented as structured events using the OpenTelemetry [Logs Data Model](../logs/data-model.md),
specifically as [Events](../logs/data-model.md#events) with a defined `EventName` and attribute structure.

## When to Use Entity Events

Entity events are particularly useful when:

1. **No Associated Telemetry**: The entity has no telemetry signals associated with it, or
the telemetry is less important than the entity data itself.

2. **Complex Descriptive Information**: Entity descriptive information is too complex for
simple resource attribute values. The resource attribute values are expected to be simple
strings, but entity descriptions can contain complex values like maps and arrays
(e.g., Kubernetes ConfigMap content, complex cloud metadata, nested tags).

3. **Entity Relationships**: The entity information needs to include relationships to other
entities. Resource data cannot contain relationship information.

4. **Lifecycle Tracking**: There is a need to explicitly track entity lifecycle events
(creation, state changes, deletion) independently from telemetry signals.

Entity events can be used alongside telemetry signals associated with entities to provide
additional context and relationship information.

## Event Types

Entity information is communicated through the following event types:

1. **Entity State Event** (`entity.state`): Emitted when an entity is created, when its
attributes change, or periodically to indicate the entity still exists.

2. **Entity Delete Event** (`entity.delete`): Emitted when an entity is removed.

### Entity State Event

The Entity State Event is emitted when an entity is created, when its descriptive attributes
change, or periodically to indicate the entity still exists.

**Event Name**: `entity.state`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these events should be formally defined in semconv where we can validate, document, enforce back compat, etc.

It's still make sense to have some context in the spec, but just link to semconv similarly to what we do for exceptions: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/exceptions.md

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entity SIG discussion - We still need to decide if these will be a separate signal or actual events. We're going to hold of on that part of this until that decision is made.

For now this will describe the entity model and usage of events to report entity state.

Comment on lines +71 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, I think this runs the risk of overloading the term state and could lead to confusion especially when you consider status metrics.

Would it be better to describe this as Entity Update Event.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state here doesn't necessarily imply an update. It can also be a heartbeat-like event. See the OTEP section.

That said, it might be helpful to distinguish between events generated by a change vs heartbeat events. @tigrannajaryan @jsuereth WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update is more vague to me. Does update reflect the full state or the change (delta)? "State" to me says it is the full state, not the delta.

Copy link
Contributor

@thompson-tomo thompson-tomo Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me when I hear state I think of things like created, running, paused etc. Which is Effectively the state attribute.

Howabout splitting heartbeat event out & renaming state event to definition as that would be clear that it is the full definition. Alternatively we call it description but suspect links/relationship would need to be an additional event to avoid confusion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more concerned we iron out the meaning of events FIRST then we can find a nomenclature. Once we understand the meaning of signals, we can find terms we're comfortable with.


**Required Attributes**:

| Attribute | Type | Description |
| --------- | ---- | ----------- |
| `entity.type` | string | Defines the type of the entity. MUST not change during the lifetime of the entity. For example: "service", "host", "k8s.pod". |
| `entity.id` | map<string, string> | Attributes that identify the entity. MUST not change during the lifetime of the entity. The map MUST contain at least one attribute. Keys and values MUST be strings. SHOULD follow OpenTelemetry [semantic conventions](https://github.com/open-telemetry/semantic-conventions) for attribute names. |

**Optional Attributes**:

| Attribute | Type | Description |
| --------- | ---- | ----------- |
| `entity.description` | map<string, AnyValue> | Descriptive (non-identifying) attributes of the entity. These attributes are not part of the entity's identity. Each Entity State event contains the complete current state of the entity's description. When absent, MUST be treated as an empty map. Follows [AnyValue](../common/README.md#anyvalue) definition: can contain scalar values, arrays, or nested maps. SHOULD follow OpenTelemetry [semantic conventions](https://github.com/open-telemetry/semantic-conventions) for attributes. |
| `entity.relationships` | array of maps | Relationships to other entities. Each relationship is a map containing: `type` (string, describes the relationship), `entity.type` (string, the type of the related entity), and `entity.id` (map<string, string>, identifying attributes of the related entity). When absent, MUST be treated as an empty array. |
| `report.interval` | int64 (seconds) | The reporting interval for this entity. MUST be a non-negative value when present. When absent, the reporting interval is unknown. A value of `0` indicates that no periodic heartbeat events will be sent. A positive value indicates the interval at which periodic state events will be emitted. Can be used by receivers to determine when to expect the next event and infer that an entity is gone if events stop arriving. |

**Timestamp Field**:

The `Timestamp` field of the LogRecord represents the time when this event was generated and sent.

**Event Emission**:

Implementations SHOULD emit Entity State events whenever entity descriptive attributes change,
and periodically based on the `report.interval` value to indicate the entity still exists.
Implementations SHOULD also emit Entity Delete events when entities are removed.

**Future Considerations**:

Each Entity State event contains the complete current state of the entity. If scalability
issues arise in the future, the specification may introduce a "patch" event mechanism to
communicate only the changes rather than the full state.

### Entity Delete Event
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I think about usage of this for something like process.

The expectation would be we send an update/state event to set the process.state attribute to terminated and then send a deleted event. Could this not be consolidated into the update event with a boolean attribute entity.final_state to indicate if has ended.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? A delete event can go along with any other state events.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understand, hence the idea to simply have a property on the update event to indicate that this is the final state of the entity and all future updates should be ignored. This would reduce the qty of events and it avoids using the term delete to describe that an entity should no longer be tracked

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To address the use case of the process.state attribute, we can include entity.description in the payload of entity.delete events as well. However, entity.interval and entity.relationships still remain useless for the delete or final state. So I’d rather keep the existing format and add only entity.description to the delete event for backends that want to capture the latest state. Curious what @tigrannajaryan and @jsuereth think about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understand, hence the idea to simply have a property on the update event to indicate that this is the final state of the entity and all future updates should be ignored.

Whoever is reporting that the entity is gone via Entity Delete Event may no longer have full state information about the entity that is gone. Which mean it is impossible to correctly report the Entity State Event, which requires full state to be communicated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if we did the following:

  • Add an additional optional property to what is currently the update event. This property would be finalized which is a boolean (default false) or status which is an enum - unset, finalised (default unset) to mirror span status.
  • rename delete event to match property name

This way we cater for both use cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thompson-tomo Can you outline the use case you want to support here?

I'm with @dmitryax and @tigrannajaryan - I'm not sure we can require description here, and including it may be problematic to implement.

As always, would be nice to see a prototype or use-case where this is a must-have, so we can evaluate design decisions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsuereth

I'm not sure we can require description here, and including it may be problematic to implement.

As per my previous comment, I am not seeking that change but rather adding an optional property ie finalised (boolean) or status (enum), then renaming this event.

The use case I am thinking of is that we want to update the process entity with the exit time ie process.exit.time. With my suggestion the update event which sets the exit time also marks it as finalized/deleted, without it you would need to send 2 events and hope they are processed in the right order.

Copy link
Member Author

@dmitryax dmitryax Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my suggestion the update event which sets the exit time also marks it as finalized/deleted, without it you would need to send 2 events and hope they are processed in the right order

I don't see a big problem in sending 2 events. This use case seems to be pretty rare, and it's not a lot of duplication, just the entity ID to be sent twice. And even if they are handled out of order, it must not be an issue for backends:

  1. A delete event marks an entity as removed.
  2. An update event sets its latest state.

I'd rather keep the events separate for clarity than optimizing for this use case.


The Entity Delete Event indicates that a particular entity is gone.

**Event Name**: `entity.delete`

**Required Attributes**:

| Attribute | Type | Description |
| --------- | ---- | ----------- |
| `entity.type` | string | The type of the entity being deleted. |
| `entity.id` | map<string, string> | Attributes that identify the entity being deleted. |

**Optional Attributes**:

| Attribute | Type | Description |
| --------- | ---- | ----------- |
| `entity.delete.reason` | string | The reason for entity deletion. Examples: "terminated", "expired", "evicted", "user_requested", "scaled_down". |

**Timestamp Field**:

The `Timestamp` field of the LogRecord represents the time when the entity was deleted.

**Delivery Guarantees**:

Transmitting Entity Delete events is not guaranteed when an entity is gone. Recipients of
entity signals MUST be prepared to handle this situation by expiring entities that are no
longer seeing Entity State events reported. The expiration mechanism is based on the
previously reported `report.interval` field. Recipients can use this value to compute when
to expect the next Entity State event and, if the event does not arrive in a timely manner
(plus some slack), consider the entity to be gone even if the Entity Delete event was not observed.

Recipients MUST also be prepared to receive an Entity Delete event out of order, for example,
before the last Entity State event. In this case, recipients SHOULD apply state updates
regardless, as each Entity State event represents the full current state of the entity and
can be used to update a previously deleted entity record.

## Entity Relationships

Entity relationships describe how entities are connected to each other. Relationships are
embedded within Entity State events as an array of relationship descriptors.

### Relationship Structure

Each relationship in the `entity.relationships` array is a map containing:

**Required Fields**:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `relationship.type` | string | The type of relationship. Describes the semantic meaning of the relationship (e.g., "scheduled_on", "contains", "depends_on"). See [Standard Relationship Types](#standard-relationship-types). |
| `entity.type` | string | The type of the target entity. |
| `entity.id` | map<string, string> | The identifying attributes of the target entity. |

**Relationship Direction**:

Relationships have direction: `source --[type]--> target`, where:

- The **source** is the entity emitting the Entity State event
- The **target** is referenced in the relationship descriptor

### Standard Relationship Types

Relationship types form an open enumeration. Standard relationship types SHOULD be defined
in OpenTelemetry semantic conventions. Custom relationship types MAY be defined to represent
domain-specific relationships.

For example, a `scheduled_on` relationship type could be used to express that a workload
is scheduled on infrastructure (e.g., a Kubernetes Pod scheduled on a Node).

### Relationship Placement

When choosing which entity should contain a relationship in its `entity.relationships` array,
implementations SHOULD prefer placing relationships on the entity type with the **shorter
lifespan** or **higher churn rate**. This minimizes the total number of Entity State events
that need to be sent.

**Rationale**: Since relationships are embedded in Entity State events, every time an entity's
relationships change, a new state event must be emitted. Placing relationships on the more
stable entity would require frequent state event emissions whenever the shorter-lived entities
are created or destroyed.

**Examples**:

- **Prefer**: `k8s.pod -> part_of -> k8s.replicaset` (relationship on the pod)
- **Rather than**: `k8s.replicaset -> contains -> k8s.pod` (relationship on the replicaset)
- **Reason**: Pods churn frequently. With relationships on pods, only new pod state events
are sent when pods are created/destroyed. If relationships were on the replicaset, every
pod creation/destruction would require a new replicaset state event with an updated list
of all contained pods.

- **Prefer**: `container -> part_of -> k8s.pod` (relationship on the container)
- **Rather than**: `k8s.pod -> contains -> container` (relationship on the pod)
- **Reason**: Containers may restart independently, so placing the relationship on the
container reduces the number of pod state events.

- **Prefer**: `process -> runs_on -> host` (relationship on the process)
- **Rather than**: `host -> hosts -> process` (relationship on the host)
- **Reason**: Processes start and stop frequently, while hosts are long-lived.

When both entities have similar lifespans, either direction is acceptable. Semantic conventions
SHOULD provide guidance on relationship placement for common entity types.

### Relationship Lifecycle

**Creating Relationships**:
Emit an Entity State event with the new relationship included in the `entity.relationships` array.

**Updating Relationships**:
Emit a new Entity State event with the updated `entity.relationships` array reflecting the current state.

**Deleting Relationships**:
Emit a new Entity State event with the relationship removed from the `entity.relationships` array.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized one thing here. When we report entity state event of opposite entity B (the one not owning relationship) with empty entity.relationships or its own relationships, how do we know that relationship A -> B with owning entity A should not be deleted?


**Implicit Deletion**:
When an entity is deleted (Entity Delete event is emitted), all relationships where that
entity is involved are implicitly deleted. Backends SHOULD handle this accordingly.

## Examples

The following examples show the logical representation of entity events. These are NOT
actual OTLP wire format representations, but rather illustrate the semantic structure
of the events.

### Kubernetes Pod Entity State

When a Kubernetes Pod is created or its attributes change:

```
LogRecord:
Timestamp: 2026-01-12T10:30:00.000000000Z
EventName: entity.state
Resource:
k8s.cluster.name: prod-cluster
Attributes:
entity.type: k8s.pod
entity.id:
k8s.pod.uid: abc-123-def-456
entity.description:
k8s.pod.name: nginx-deployment-66b6c
k8s.pod.labels:
app: nginx
version: "1.21"
tier: frontend
k8s.pod.phase: Running
report.interval: 60
entity.relationships:
- relationship.type: scheduled_on
entity.type: k8s.node
entity.id:
k8s.node.uid: node-001
- relationship.type: part_of
entity.type: k8s.replicaset
entity.id:
k8s.replicaset.uid: rs-456
```

### Entity Delete

When the Pod is terminated:

```
LogRecord:
Timestamp: 2026-01-12T11:00:00.000000000Z
EventName: entity.delete
Resource:
k8s.cluster.name: prod-cluster
Attributes:
entity.type: k8s.pod
entity.id:
k8s.pod.uid: abc-123-def-456
entity.delete.reason: terminated
```
Loading