Repository: open-telemetry/opentelemetry-python

Issue:
Add EventName to LogRecord
The event.name attribute has been deprecated in favor of a top level EventName string field in the LogRecord.

See https://github.com/open-telemetry/opentelemetry-specification/issues/4260#issuecomment-2505119306

It no longer makes sense to have an Event class at all now.. An event is just a LogRecord with the event name field set..

I think we should revisit events, and look at the approach that they took in go lang (https://github.com/open-telemetry/opentelemetry-go/pull/6018), where the only change they made to support events was to add the EventName string field to the LogRecord.. In order to emit events, a user simply calls `LoggerProvider.emit` with a `LogRecord` that has the event name set.. That's it.

Wondering what others think of doing it that way. Given events are still in alpha, i don't think it's too late to make this change.


Discussion:
> It no longer makes sense to have an Event class at all now.. An event is just a LogRecord with the event name field set..

That's right, the Events API was deprecated and there is just Logs API now. This will be a bit disruptive to remove since some contrib instrumentations and users depend on the events API. A good first step is to mark the main events functions as deprecated (e.g. `EventLoggerProvider`, `get_event_logger`, ...)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8097b699f2..4ec786f19df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## Unreleased
 
 - Update OTLP proto to v1.7 [#4645](https://github.com/open-telemetry/opentelemetry-python/pull/4645).
+- Add `event_name` as a top level field in the `LogRecord`. Events are now simply logs with the
+`event_name` field set, the logs SDK should be used to emit events ([#4652](https://github.com/open-telemetry/opentelemetry-python/pull/4652)).
 - Update OTLP gRPC/HTTP exporters: the export timeout is now inclusive of all retries and backoffs.
   A +/-20% jitter was added to all backoffs. A pointless 32 second sleep that occurred after all retries
   had completed/failed was removed.
diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py
index 9d713cb7ff0..000e56ed8bf 100644
--- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py
+++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py
@@ -62,6 +62,7 @@ def _encode_log(log_data: LogData) -> PB2LogRecord:
         ),
         dropped_attributes_count=log_data.log_record.dropped_attributes,
         severity_number=log_data.log_record.severity_number.value,
+        event_name=log_data.log_record.event_name,
     )
 
 
diff --git a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py
index e2d416d5992..d1da2e99956 100644
--- a/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py
@@ -69,6 +69,7 @@ def __init__(
         severity_number: Optional[SeverityNumber] = None,
         body: AnyValue = None,
         attributes: Optional[_ExtendedAttributes] = None,
+        event_name: Optional[str] = None,
     ) -> None: ...
 
     @overload
@@ -99,6 +100,7 @@ def __init__(
         severity_number: Optional[SeverityNumber] = None,
         body: AnyValue = None,
         attributes: Optional[_ExtendedAttributes] = None,
+        event_name: Optional[str] = None,
     ) -> None:
         self.timestamp = timestamp
         if observed_timestamp is None:
@@ -112,6 +114,7 @@ def __init__(
         self.severity_number = severity_number
         self.body = body
         self.attributes = attributes
+        self.event_name = event_name
 
 
 class Logger(ABC):
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py
index 8201b6c78f0..d3e6079c560 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py
@@ -206,6 +206,7 @@ def __init__(
         resource: Resource | None = None,
         attributes: _ExtendedAttributes | None = None,
         limits: LogLimits | None = _UnsetLogLimits,
+        event_name: str | None = None,
     ): ...
 
     @overload
@@ -224,7 +225,7 @@ def __init__(
         limits: LogLimits | None = _UnsetLogLimits,
     ): ...
 
-    def __init__(
+    def __init__(  # pylint:disable=too-many-locals
         self,
         timestamp: int | None = None,
         observed_timestamp: int | None = None,
@@ -238,6 +239,7 @@ def __init__(
         resource: Resource | None = None,
         attributes: _ExtendedAttributes | None = None,
         limits: LogLimits | None = _UnsetLogLimits,
+        event_name: str | None = None,
     ):
         if trace_id or span_id or trace_flags:
             warnings.warn(
@@ -275,6 +277,7 @@ def __init__(
                     max_value_len=limits.max_attribute_length,
                     extended_attributes=True,
                 ),
+                "event_name": event_name,
             }
         )
         self.resource = (
@@ -318,6 +321,7 @@ def to_json(self, indent: int | None = 4) -> str:
                 ),
                 "trace_flags": self.trace_flags,
                 "resource": json.loads(self.resource.to_json()),
+                "event_name": self.event_name if self.event_name else "",
             },
             indent=indent,
             cls=BytesEncoder,


Test patch:
diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py
index d7aa0faa649..5175cb8c860 100644
--- a/opentelemetry-sdk/tests/logs/test_log_record.py
+++ b/opentelemetry-sdk/tests/logs/test_log_record.py
@@ -42,11 +42,12 @@ def test_log_record_to_json(self):
                 "sequence": [1, 2],
                 "str": "string",
             },
+            event_name="a.event",
         )
 
         self.assertEqual(
             log_record.to_json(indent=None),
-            '{"body": {"key": "logLine", "bytes": "MTIz"}, "severity_number": null, "severity_text": null, "attributes": {"mapping": {"key": "value"}, "none": null, "sequence": [1, 2], "str": "string"}, "dropped_attributes": 0, "timestamp": "1970-01-01T00:00:00.000000Z", "observed_timestamp": "1970-01-01T00:00:00.000000Z", "trace_id": "", "span_id": "", "trace_flags": null, "resource": {"attributes": {"service.name": "foo"}, "schema_url": ""}}',
+            '{"body": {"key": "logLine", "bytes": "MTIz"}, "severity_number": null, "severity_text": null, "attributes": {"mapping": {"key": "value"}, "none": null, "sequence": [1, 2], "str": "string"}, "dropped_attributes": 0, "timestamp": "1970-01-01T00:00:00.000000Z", "observed_timestamp": "1970-01-01T00:00:00.000000Z", "trace_id": "", "span_id": "", "trace_flags": null, "resource": {"attributes": {"service.name": "foo"}, "schema_url": ""}, "event_name": "a.event"}',
         )
 
     def test_log_record_to_json_serializes_severity_number_as_int(self):