Skip to content

Commit 25c4e72

Browse files
authored
Merge branch 'main' into pytyped
2 parents cfe6d5a + a8aacb0 commit 25c4e72

File tree

6 files changed

+323
-6
lines changed

6 files changed

+323
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#4154](https://github.com/open-telemetry/opentelemetry-python/pull/4154))
1414
- sdk: Add support for log formatting
1515
([#4137](https://github.com/open-telemetry/opentelemetry-python/pull/4166))
16+
- Implement events sdk
17+
([#4176](https://github.com/open-telemetry/opentelemetry-python/pull/4176))
1618

1719
## Version 1.27.0/0.48b0 (2024-08-28)
1820

opentelemetry-api/src/opentelemetry/util/_importlib_metadata.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@
1515
# FIXME: Use importlib.metadata when support for 3.11 is dropped if the rest of
1616
# the supported versions at that time have the same API.
1717
from importlib_metadata import ( # type: ignore
18+
Distribution,
1819
EntryPoint,
1920
EntryPoints,
21+
PackageNotFoundError,
22+
distributions,
2023
entry_points,
24+
requires,
2125
version,
2226
)
2327

24-
# The importlib-metadata library has introduced breaking changes before to its
25-
# API, this module is kept just to act as a layer between the
26-
# importlib-metadata library and our project if in any case it is necessary to
27-
# do so.
28-
29-
__all__ = ["entry_points", "version", "EntryPoint", "EntryPoints"]
28+
__all__ = [
29+
"entry_points",
30+
"version",
31+
"EntryPoint",
32+
"EntryPoints",
33+
"requires",
34+
"Distribution",
35+
"distributions",
36+
"PackageNotFoundError",
37+
]

opentelemetry-api/tests/util/test__importlib_metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opentelemetry.util._importlib_metadata import (
2020
entry_points as importlib_metadata_entry_points,
2121
)
22+
from opentelemetry.util._importlib_metadata import version
2223

2324

2425
class TestEntryPoints(TestCase):
@@ -106,3 +107,5 @@ def test_uniform_behavior(self):
106107
entry_points = importlib_metadata_entry_points(group="abc", name="abc")
107108
self.assertIsInstance(entry_points, EntryPoints)
108109
self.assertEqual(len(entry_points), 0)
110+
111+
self.assertIsInstance(version("opentelemetry-api"), str)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import logging
15+
from time import time_ns
16+
from typing import Optional
17+
18+
from opentelemetry import trace
19+
from opentelemetry._events import Event
20+
from opentelemetry._events import EventLogger as APIEventLogger
21+
from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider
22+
from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider
23+
from opentelemetry.sdk._logs import Logger, LoggerProvider, LogRecord
24+
from opentelemetry.util.types import Attributes
25+
26+
_logger = logging.getLogger(__name__)
27+
28+
29+
class EventLogger(APIEventLogger):
30+
def __init__(
31+
self,
32+
logger_provider: LoggerProvider,
33+
name: str,
34+
version: Optional[str] = None,
35+
schema_url: Optional[str] = None,
36+
attributes: Optional[Attributes] = None,
37+
):
38+
super().__init__(
39+
name=name,
40+
version=version,
41+
schema_url=schema_url,
42+
attributes=attributes,
43+
)
44+
self._logger: Logger = logger_provider.get_logger(
45+
name, version, schema_url, attributes
46+
)
47+
48+
def emit(self, event: Event) -> None:
49+
if isinstance(self._logger, NoOpLogger):
50+
# Do nothing if SDK is disabled
51+
return
52+
span_context = trace.get_current_span().get_span_context()
53+
log_record = LogRecord(
54+
timestamp=event.timestamp or time_ns(),
55+
observed_timestamp=None,
56+
trace_id=event.trace_id or span_context.trace_id,
57+
span_id=event.span_id or span_context.span_id,
58+
trace_flags=event.trace_flags or span_context.trace_flags,
59+
severity_text=None,
60+
severity_number=event.severity_number or SeverityNumber.INFO,
61+
body=event.body,
62+
resource=getattr(self._logger, "resource", None),
63+
attributes=event.attributes,
64+
)
65+
self._logger.emit(log_record)
66+
67+
68+
class EventLoggerProvider(APIEventLoggerProvider):
69+
def __init__(self, logger_provider: Optional[LoggerProvider] = None):
70+
self._logger_provider = logger_provider or get_logger_provider()
71+
72+
def get_event_logger(
73+
self,
74+
name: str,
75+
version: Optional[str] = None,
76+
schema_url: Optional[str] = None,
77+
attributes: Optional[Attributes] = None,
78+
) -> EventLogger:
79+
if not name:
80+
_logger.warning("EventLogger created with invalid name: %s", name)
81+
return EventLogger(
82+
self._logger_provider, name, version, schema_url, attributes
83+
)
84+
85+
def shutdown(self):
86+
self._logger_provider.shutdown()
87+
88+
def force_flush(self, timeout_millis: int = 30000) -> bool:
89+
self._logger_provider.force_flush(timeout_millis)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# pylint: disable=protected-access,no-self-use
16+
17+
import unittest
18+
from unittest.mock import Mock, patch
19+
20+
from opentelemetry._events import Event
21+
from opentelemetry._logs import SeverityNumber, set_logger_provider
22+
from opentelemetry.sdk._events import EventLoggerProvider
23+
from opentelemetry.sdk._logs import LoggerProvider
24+
from opentelemetry.sdk._logs._internal import Logger, NoOpLogger
25+
from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED
26+
27+
28+
class TestEventLoggerProvider(unittest.TestCase):
29+
def test_event_logger_provider(self):
30+
logger_provider = LoggerProvider()
31+
event_logger_provider = EventLoggerProvider(
32+
logger_provider=logger_provider
33+
)
34+
35+
self.assertEqual(
36+
event_logger_provider._logger_provider,
37+
logger_provider,
38+
)
39+
40+
def test_event_logger_provider_default(self):
41+
logger_provider = LoggerProvider()
42+
set_logger_provider(logger_provider)
43+
event_logger_provider = EventLoggerProvider()
44+
45+
self.assertEqual(
46+
event_logger_provider._logger_provider,
47+
logger_provider,
48+
)
49+
50+
def test_get_event_logger(self):
51+
logger_provider = LoggerProvider()
52+
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
53+
"name",
54+
version="version",
55+
schema_url="schema_url",
56+
attributes={"key": "value"},
57+
)
58+
self.assertTrue(
59+
event_logger._logger,
60+
Logger,
61+
)
62+
logger = event_logger._logger
63+
self.assertEqual(logger._instrumentation_scope.name, "name")
64+
self.assertEqual(logger._instrumentation_scope.version, "version")
65+
self.assertEqual(
66+
logger._instrumentation_scope.schema_url, "schema_url"
67+
)
68+
self.assertEqual(
69+
logger._instrumentation_scope.attributes, {"key": "value"}
70+
)
71+
72+
@patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"})
73+
def test_get_event_logger_with_sdk_disabled(self):
74+
logger_provider = LoggerProvider()
75+
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
76+
"name",
77+
version="version",
78+
schema_url="schema_url",
79+
attributes={"key": "value"},
80+
)
81+
self.assertIsInstance(event_logger._logger, NoOpLogger)
82+
83+
def test_force_flush(self):
84+
logger_provider = Mock()
85+
event_logger = EventLoggerProvider(logger_provider)
86+
event_logger.force_flush(1000)
87+
logger_provider.force_flush.assert_called_once_with(1000)
88+
89+
def test_shutdown(self):
90+
logger_provider = Mock()
91+
event_logger = EventLoggerProvider(logger_provider)
92+
event_logger.shutdown()
93+
logger_provider.shutdown.assert_called_once()
94+
95+
@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
96+
def test_event_logger(self, logger_mock):
97+
logger_provider = LoggerProvider()
98+
logger_mock_inst = Mock()
99+
logger_mock.return_value = logger_mock_inst
100+
EventLoggerProvider(logger_provider).get_event_logger(
101+
"name",
102+
version="version",
103+
schema_url="schema_url",
104+
attributes={"key": "value"},
105+
)
106+
logger_mock.assert_called_once_with(
107+
"name", "version", "schema_url", {"key": "value"}
108+
)
109+
110+
@patch("opentelemetry.sdk._events.LogRecord")
111+
@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
112+
def test_event_logger_emit(self, logger_mock, log_record_mock):
113+
logger_provider = LoggerProvider()
114+
logger_mock_inst = Mock()
115+
logger_mock.return_value = logger_mock_inst
116+
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
117+
"name",
118+
version="version",
119+
schema_url="schema_url",
120+
attributes={"key": "value"},
121+
)
122+
logger_mock.assert_called_once_with(
123+
"name", "version", "schema_url", {"key": "value"}
124+
)
125+
now = Mock()
126+
trace_id = Mock()
127+
span_id = Mock()
128+
trace_flags = Mock()
129+
event = Event(
130+
name="test_event",
131+
timestamp=now,
132+
trace_id=trace_id,
133+
span_id=span_id,
134+
trace_flags=trace_flags,
135+
body="test body",
136+
severity_number=SeverityNumber.ERROR,
137+
attributes={
138+
"key": "val",
139+
"foo": "bar",
140+
"event.name": "not this one",
141+
},
142+
)
143+
log_record_mock_inst = Mock()
144+
log_record_mock.return_value = log_record_mock_inst
145+
event_logger.emit(event)
146+
log_record_mock.assert_called_once_with(
147+
timestamp=now,
148+
observed_timestamp=None,
149+
trace_id=trace_id,
150+
span_id=span_id,
151+
trace_flags=trace_flags,
152+
severity_text=None,
153+
severity_number=SeverityNumber.ERROR,
154+
body="test body",
155+
resource=event_logger._logger.resource,
156+
attributes={
157+
"key": "val",
158+
"foo": "bar",
159+
"event.name": "test_event",
160+
},
161+
)
162+
logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst)
163+
164+
@patch("opentelemetry.sdk._events.LogRecord")
165+
@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
166+
def test_event_logger_emit_sdk_disabled(
167+
self, logger_mock, log_record_mock
168+
):
169+
logger_provider = LoggerProvider()
170+
logger_mock_inst = Mock(spec=NoOpLogger)
171+
logger_mock.return_value = logger_mock_inst
172+
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
173+
"name",
174+
version="version",
175+
schema_url="schema_url",
176+
attributes={"key": "value"},
177+
)
178+
logger_mock.assert_called_once_with(
179+
"name", "version", "schema_url", {"key": "value"}
180+
)
181+
now = Mock()
182+
trace_id = Mock()
183+
span_id = Mock()
184+
trace_flags = Mock()
185+
event = Event(
186+
name="test_event",
187+
timestamp=now,
188+
trace_id=trace_id,
189+
span_id=span_id,
190+
trace_flags=trace_flags,
191+
body="test body",
192+
severity_number=SeverityNumber.ERROR,
193+
attributes={
194+
"key": "val",
195+
"foo": "bar",
196+
"event.name": "not this one",
197+
},
198+
)
199+
log_record_mock_inst = Mock()
200+
log_record_mock.return_value = log_record_mock_inst
201+
event_logger.emit(event)
202+
logger_mock_inst.emit.assert_not_called()

0 commit comments

Comments
 (0)