Skip to content

Commit

Permalink
Implement events sdk (open-telemetry#4176)
Browse files Browse the repository at this point in the history
  • Loading branch information
lzchen authored Sep 10, 2024
1 parent 9697f80 commit bd51fcb
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#4154](https://github.com/open-telemetry/opentelemetry-python/pull/4154))
- sdk: Add support for log formatting
([#4137](https://github.com/open-telemetry/opentelemetry-python/pull/4166))
- Implement events sdk
([#4176](https://github.com/open-telemetry/opentelemetry-python/pull/4176))

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

Expand Down
89 changes: 89 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/_events/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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.
import logging
from time import time_ns
from typing import Optional

from opentelemetry import trace
from opentelemetry._events import Event
from opentelemetry._events import EventLogger as APIEventLogger
from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider
from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider
from opentelemetry.sdk._logs import Logger, LoggerProvider, LogRecord
from opentelemetry.util.types import Attributes

_logger = logging.getLogger(__name__)


class EventLogger(APIEventLogger):
def __init__(
self,
logger_provider: LoggerProvider,
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._logger: Logger = logger_provider.get_logger(
name, version, schema_url, attributes
)

def emit(self, event: Event) -> None:
if isinstance(self._logger, NoOpLogger):
# Do nothing if SDK is disabled
return
span_context = trace.get_current_span().get_span_context()
log_record = LogRecord(
timestamp=event.timestamp or time_ns(),
observed_timestamp=None,
trace_id=event.trace_id or span_context.trace_id,
span_id=event.span_id or span_context.span_id,
trace_flags=event.trace_flags or span_context.trace_flags,
severity_text=None,
severity_number=event.severity_number or SeverityNumber.INFO,
body=event.body,
resource=getattr(self._logger, "resource", None),
attributes=event.attributes,
)
self._logger.emit(log_record)


class EventLoggerProvider(APIEventLoggerProvider):
def __init__(self, logger_provider: Optional[LoggerProvider] = None):
self._logger_provider = logger_provider or get_logger_provider()

def get_event_logger(
self,
name: str,
version: Optional[str] = None,
schema_url: Optional[str] = None,
attributes: Optional[Attributes] = None,
) -> EventLogger:
if not name:
_logger.warning("EventLogger created with invalid name: %s", name)
return EventLogger(
self._logger_provider, name, version, schema_url, attributes
)

def shutdown(self):
self._logger_provider.shutdown()

def force_flush(self, timeout_millis: int = 30000) -> bool:
self._logger_provider.force_flush(timeout_millis)
13 changes: 13 additions & 0 deletions opentelemetry-sdk/tests/events/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 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.
202 changes: 202 additions & 0 deletions opentelemetry-sdk/tests/events/test_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# 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.

# pylint: disable=protected-access,no-self-use

import unittest
from unittest.mock import Mock, patch

from opentelemetry._events import Event
from opentelemetry._logs import SeverityNumber, set_logger_provider
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs._internal import Logger, NoOpLogger
from opentelemetry.sdk.environment_variables import OTEL_SDK_DISABLED


class TestEventLoggerProvider(unittest.TestCase):
def test_event_logger_provider(self):
logger_provider = LoggerProvider()
event_logger_provider = EventLoggerProvider(
logger_provider=logger_provider
)

self.assertEqual(
event_logger_provider._logger_provider,
logger_provider,
)

def test_event_logger_provider_default(self):
logger_provider = LoggerProvider()
set_logger_provider(logger_provider)
event_logger_provider = EventLoggerProvider()

self.assertEqual(
event_logger_provider._logger_provider,
logger_provider,
)

def test_get_event_logger(self):
logger_provider = LoggerProvider()
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
"name",
version="version",
schema_url="schema_url",
attributes={"key": "value"},
)
self.assertTrue(
event_logger._logger,
Logger,
)
logger = event_logger._logger
self.assertEqual(logger._instrumentation_scope.name, "name")
self.assertEqual(logger._instrumentation_scope.version, "version")
self.assertEqual(
logger._instrumentation_scope.schema_url, "schema_url"
)
self.assertEqual(
logger._instrumentation_scope.attributes, {"key": "value"}
)

@patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"})
def test_get_event_logger_with_sdk_disabled(self):
logger_provider = LoggerProvider()
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
"name",
version="version",
schema_url="schema_url",
attributes={"key": "value"},
)
self.assertIsInstance(event_logger._logger, NoOpLogger)

def test_force_flush(self):
logger_provider = Mock()
event_logger = EventLoggerProvider(logger_provider)
event_logger.force_flush(1000)
logger_provider.force_flush.assert_called_once_with(1000)

def test_shutdown(self):
logger_provider = Mock()
event_logger = EventLoggerProvider(logger_provider)
event_logger.shutdown()
logger_provider.shutdown.assert_called_once()

@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
def test_event_logger(self, logger_mock):
logger_provider = LoggerProvider()
logger_mock_inst = Mock()
logger_mock.return_value = logger_mock_inst
EventLoggerProvider(logger_provider).get_event_logger(
"name",
version="version",
schema_url="schema_url",
attributes={"key": "value"},
)
logger_mock.assert_called_once_with(
"name", "version", "schema_url", {"key": "value"}
)

@patch("opentelemetry.sdk._events.LogRecord")
@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
def test_event_logger_emit(self, logger_mock, log_record_mock):
logger_provider = LoggerProvider()
logger_mock_inst = Mock()
logger_mock.return_value = logger_mock_inst
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
"name",
version="version",
schema_url="schema_url",
attributes={"key": "value"},
)
logger_mock.assert_called_once_with(
"name", "version", "schema_url", {"key": "value"}
)
now = Mock()
trace_id = Mock()
span_id = Mock()
trace_flags = Mock()
event = Event(
name="test_event",
timestamp=now,
trace_id=trace_id,
span_id=span_id,
trace_flags=trace_flags,
body="test body",
severity_number=SeverityNumber.ERROR,
attributes={
"key": "val",
"foo": "bar",
"event.name": "not this one",
},
)
log_record_mock_inst = Mock()
log_record_mock.return_value = log_record_mock_inst
event_logger.emit(event)
log_record_mock.assert_called_once_with(
timestamp=now,
observed_timestamp=None,
trace_id=trace_id,
span_id=span_id,
trace_flags=trace_flags,
severity_text=None,
severity_number=SeverityNumber.ERROR,
body="test body",
resource=event_logger._logger.resource,
attributes={
"key": "val",
"foo": "bar",
"event.name": "test_event",
},
)
logger_mock_inst.emit.assert_called_once_with(log_record_mock_inst)

@patch("opentelemetry.sdk._events.LogRecord")
@patch("opentelemetry.sdk._logs._internal.LoggerProvider.get_logger")
def test_event_logger_emit_sdk_disabled(
self, logger_mock, log_record_mock
):
logger_provider = LoggerProvider()
logger_mock_inst = Mock(spec=NoOpLogger)
logger_mock.return_value = logger_mock_inst
event_logger = EventLoggerProvider(logger_provider).get_event_logger(
"name",
version="version",
schema_url="schema_url",
attributes={"key": "value"},
)
logger_mock.assert_called_once_with(
"name", "version", "schema_url", {"key": "value"}
)
now = Mock()
trace_id = Mock()
span_id = Mock()
trace_flags = Mock()
event = Event(
name="test_event",
timestamp=now,
trace_id=trace_id,
span_id=span_id,
trace_flags=trace_flags,
body="test body",
severity_number=SeverityNumber.ERROR,
attributes={
"key": "val",
"foo": "bar",
"event.name": "not this one",
},
)
log_record_mock_inst = Mock()
log_record_mock.return_value = log_record_mock_inst
event_logger.emit(event)
logger_mock_inst.emit.assert_not_called()

0 comments on commit bd51fcb

Please sign in to comment.