forked from open-telemetry/opentelemetry-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Events API implementation (open-telemetry#4054)
- Loading branch information
1 parent
6e1429e
commit b380e53
Showing
7 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
opentelemetry-api/src/opentelemetry/_events/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
# Copyright The OpenTelemetry Authors | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from abc import ABC, abstractmethod | ||
from logging import getLogger | ||
from os import environ | ||
from typing import Any, Optional, cast | ||
|
||
from opentelemetry._logs import LogRecord | ||
from opentelemetry._logs.severity import SeverityNumber | ||
from opentelemetry.environment_variables import ( | ||
_OTEL_PYTHON_EVENT_LOGGER_PROVIDER, | ||
) | ||
from opentelemetry.trace.span import TraceFlags | ||
from opentelemetry.util._once import Once | ||
from opentelemetry.util._providers import _load_provider | ||
from opentelemetry.util.types import Attributes | ||
|
||
_logger = getLogger(__name__) | ||
|
||
|
||
class Event(LogRecord): | ||
|
||
def __init__( | ||
self, | ||
name: str, | ||
timestamp: Optional[int] = None, | ||
trace_id: Optional[int] = None, | ||
span_id: Optional[int] = None, | ||
trace_flags: Optional["TraceFlags"] = None, | ||
body: Optional[Any] = None, | ||
severity_number: Optional[SeverityNumber] = None, | ||
attributes: Optional[Attributes] = None, | ||
): | ||
attributes = attributes or {} | ||
event_attributes = {**attributes, "event.name": name} | ||
super().__init__( | ||
timestamp=timestamp, | ||
trace_id=trace_id, | ||
span_id=span_id, | ||
trace_flags=trace_flags, | ||
body=body, # type: ignore | ||
severity_number=severity_number, | ||
attributes=event_attributes, | ||
) | ||
self.name = name | ||
|
||
|
||
class EventLogger(ABC): | ||
|
||
def __init__( | ||
self, | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
): | ||
self._name = name | ||
self._version = version | ||
self._schema_url = schema_url | ||
self._attributes = attributes | ||
|
||
@abstractmethod | ||
def emit(self, event: "Event") -> None: | ||
"""Emits a :class:`Event` representing an event.""" | ||
|
||
|
||
class NoOpEventLogger(EventLogger): | ||
|
||
def emit(self, event: Event) -> None: | ||
pass | ||
|
||
|
||
class ProxyEventLogger(EventLogger): | ||
def __init__( | ||
self, | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
): | ||
super().__init__( | ||
name=name, | ||
version=version, | ||
schema_url=schema_url, | ||
attributes=attributes, | ||
) | ||
self._real_event_logger: Optional[EventLogger] = None | ||
self._noop_event_logger = NoOpEventLogger(name) | ||
|
||
@property | ||
def _event_logger(self) -> EventLogger: | ||
if self._real_event_logger: | ||
return self._real_event_logger | ||
|
||
if _EVENT_LOGGER_PROVIDER: | ||
self._real_event_logger = _EVENT_LOGGER_PROVIDER.get_event_logger( | ||
self._name, | ||
self._version, | ||
self._schema_url, | ||
self._attributes, | ||
) | ||
return self._real_event_logger | ||
return self._noop_event_logger | ||
|
||
def emit(self, event: Event) -> None: | ||
self._event_logger.emit(event) | ||
|
||
|
||
class EventLoggerProvider(ABC): | ||
|
||
@abstractmethod | ||
def get_event_logger( | ||
self, | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
) -> EventLogger: | ||
"""Returns an EventLoggerProvider for use.""" | ||
|
||
|
||
class NoOpEventLoggerProvider(EventLoggerProvider): | ||
|
||
def get_event_logger( | ||
self, | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
) -> EventLogger: | ||
return NoOpEventLogger( | ||
name, version=version, schema_url=schema_url, attributes=attributes | ||
) | ||
|
||
|
||
class ProxyEventLoggerProvider(EventLoggerProvider): | ||
|
||
def get_event_logger( | ||
self, | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
) -> EventLogger: | ||
if _EVENT_LOGGER_PROVIDER: | ||
return _EVENT_LOGGER_PROVIDER.get_event_logger( | ||
name, | ||
version=version, | ||
schema_url=schema_url, | ||
attributes=attributes, | ||
) | ||
return ProxyEventLogger( | ||
name, | ||
version=version, | ||
schema_url=schema_url, | ||
attributes=attributes, | ||
) | ||
|
||
|
||
_EVENT_LOGGER_PROVIDER_SET_ONCE = Once() | ||
_EVENT_LOGGER_PROVIDER: Optional[EventLoggerProvider] = None | ||
_PROXY_EVENT_LOGGER_PROVIDER = ProxyEventLoggerProvider() | ||
|
||
|
||
def get_event_logger_provider() -> EventLoggerProvider: | ||
|
||
global _EVENT_LOGGER_PROVIDER # pylint: disable=global-variable-not-assigned | ||
if _EVENT_LOGGER_PROVIDER is None: | ||
if _OTEL_PYTHON_EVENT_LOGGER_PROVIDER not in environ: | ||
return _PROXY_EVENT_LOGGER_PROVIDER | ||
|
||
event_logger_provider: EventLoggerProvider = _load_provider( # type: ignore | ||
_OTEL_PYTHON_EVENT_LOGGER_PROVIDER, "event_logger_provider" | ||
) | ||
|
||
_set_event_logger_provider(event_logger_provider, log=False) | ||
|
||
return cast("EventLoggerProvider", _EVENT_LOGGER_PROVIDER) | ||
|
||
|
||
def _set_event_logger_provider( | ||
event_logger_provider: EventLoggerProvider, log: bool | ||
) -> None: | ||
def set_elp() -> None: | ||
global _EVENT_LOGGER_PROVIDER # pylint: disable=global-statement | ||
_EVENT_LOGGER_PROVIDER = event_logger_provider | ||
|
||
did_set = _EVENT_LOGGER_PROVIDER_SET_ONCE.do_once(set_elp) | ||
|
||
if log and did_set: | ||
_logger.warning( | ||
"Overriding of current EventLoggerProvider is not allowed" | ||
) | ||
|
||
|
||
def set_event_logger_provider( | ||
event_logger_provider: EventLoggerProvider, | ||
) -> None: | ||
|
||
_set_event_logger_provider(event_logger_provider, log=True) | ||
|
||
|
||
def get_event_logger( | ||
name: str, | ||
version: Optional[str] = None, | ||
schema_url: Optional[str] = None, | ||
attributes: Optional[Attributes] = None, | ||
event_logger_provider: Optional[EventLoggerProvider] = None, | ||
) -> "EventLogger": | ||
if event_logger_provider is None: | ||
event_logger_provider = get_event_logger_provider() | ||
return event_logger_provider.get_event_logger( | ||
name, | ||
version, | ||
schema_url, | ||
attributes, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import unittest | ||
|
||
from opentelemetry._events import Event | ||
|
||
|
||
class TestEvent(unittest.TestCase): | ||
def test_event(self): | ||
event = Event("example", 123, attributes={"key": "value"}) | ||
self.assertEqual(event.name, "example") | ||
self.assertEqual(event.timestamp, 123) | ||
self.assertEqual( | ||
event.attributes, {"key": "value", "event.name": "example"} | ||
) |
47 changes: 47 additions & 0 deletions
47
opentelemetry-api/tests/events/test_event_logger_provider.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# type:ignore | ||
import unittest | ||
from unittest.mock import Mock, patch | ||
|
||
import opentelemetry._events as events | ||
from opentelemetry._events import ( | ||
get_event_logger_provider, | ||
set_event_logger_provider, | ||
) | ||
from opentelemetry.test.globals_test import EventsGlobalsTest | ||
|
||
|
||
class TestGlobals(EventsGlobalsTest, unittest.TestCase): | ||
def test_set_event_logger_provider(self): | ||
elp_mock = Mock() | ||
# pylint: disable=protected-access | ||
self.assertIsNone(events._EVENT_LOGGER_PROVIDER) | ||
set_event_logger_provider(elp_mock) | ||
self.assertIs(events._EVENT_LOGGER_PROVIDER, elp_mock) | ||
self.assertIs(get_event_logger_provider(), elp_mock) | ||
|
||
def test_get_event_logger_provider(self): | ||
# pylint: disable=protected-access | ||
self.assertIsNone(events._EVENT_LOGGER_PROVIDER) | ||
|
||
self.assertIsInstance( | ||
get_event_logger_provider(), events.ProxyEventLoggerProvider | ||
) | ||
|
||
events._EVENT_LOGGER_PROVIDER = None | ||
|
||
with patch.dict( | ||
"os.environ", | ||
{ | ||
"OTEL_PYTHON_EVENT_LOGGER_PROVIDER": "test_event_logger_provider" | ||
}, | ||
): | ||
|
||
with patch("opentelemetry._events._load_provider", Mock()): | ||
with patch( | ||
"opentelemetry._events.cast", | ||
Mock(**{"return_value": "test_event_logger_provider"}), | ||
): | ||
self.assertEqual( | ||
get_event_logger_provider(), | ||
"test_event_logger_provider", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# pylint: disable=W0212,W0222,W0221 | ||
import typing | ||
import unittest | ||
|
||
import opentelemetry._events as events | ||
from opentelemetry.test.globals_test import EventsGlobalsTest | ||
from opentelemetry.util.types import Attributes | ||
|
||
|
||
class TestProvider(events.NoOpEventLoggerProvider): | ||
def get_event_logger( | ||
self, | ||
name: str, | ||
version: typing.Optional[str] = None, | ||
schema_url: typing.Optional[str] = None, | ||
attributes: typing.Optional[Attributes] = None, | ||
) -> events.EventLogger: | ||
return LoggerTest(name) | ||
|
||
|
||
class LoggerTest(events.NoOpEventLogger): | ||
def emit(self, event: events.Event) -> None: | ||
pass | ||
|
||
|
||
class TestProxy(EventsGlobalsTest, unittest.TestCase): | ||
def test_proxy_logger(self): | ||
provider = events.get_event_logger_provider() | ||
# proxy provider | ||
self.assertIsInstance(provider, events.ProxyEventLoggerProvider) | ||
|
||
# provider returns proxy logger | ||
event_logger = provider.get_event_logger("proxy-test") | ||
self.assertIsInstance(event_logger, events.ProxyEventLogger) | ||
|
||
# set a real provider | ||
events.set_event_logger_provider(TestProvider()) | ||
|
||
# get_logger_provider() now returns the real provider | ||
self.assertIsInstance(events.get_event_logger_provider(), TestProvider) | ||
|
||
# logger provider now returns real instance | ||
self.assertIsInstance( | ||
events.get_event_logger_provider().get_event_logger("fresh"), | ||
LoggerTest, | ||
) | ||
|
||
# references to the old provider still work but return real logger now | ||
real_logger = provider.get_event_logger("proxy-test") | ||
self.assertIsInstance(real_logger, LoggerTest) |
Oops, something went wrong.