From 1133701752cd33e0d2c90b43af0e477b6f324ae0 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 24 Mar 2024 14:14:33 -0400 Subject: [PATCH 1/7] added handlers dir --- handlers/opentelemetry-structlog/README.md | 1 + .../opentelemetry-structlog/pyproject.toml | 45 +++++ .../src/opentelemetry-structlog/__init__.py | 0 .../src/opentelemetry-structlog/exporter.py | 161 ++++++++++++++++++ .../src/opentelemetry-structlog/version.py | 1 + 5 files changed, 208 insertions(+) create mode 100644 handlers/opentelemetry-structlog/README.md create mode 100644 handlers/opentelemetry-structlog/pyproject.toml create mode 100644 handlers/opentelemetry-structlog/src/opentelemetry-structlog/__init__.py create mode 100644 handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py create mode 100644 handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py diff --git a/handlers/opentelemetry-structlog/README.md b/handlers/opentelemetry-structlog/README.md new file mode 100644 index 0000000000..ac5652f1d3 --- /dev/null +++ b/handlers/opentelemetry-structlog/README.md @@ -0,0 +1 @@ +# Structlog handler for OpenTelemetry diff --git a/handlers/opentelemetry-structlog/pyproject.toml b/handlers/opentelemetry-structlog/pyproject.toml new file mode 100644 index 0000000000..241c46dfed --- /dev/null +++ b/handlers/opentelemetry-structlog/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = [ + "hatchling", +] +build-backend = "hatchling.build" + +[project] +name = "opentelemetry-structlog" +dynamic = [ + "version", +] +description = "Structlog handler for emitting logs to OpenTelemetry" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.7" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "opentelemetry-sdk ~= 1.22", + "structlog ~= 24.1", +] + +[tool.hatch.version] +path = "src/opentelemetry-structlog/version.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/src", +] + +[tool.hatch.build.targets.wheel] +packages = [ + "src/opentelemetry-structlog", +] diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/__init__.py b/handlers/opentelemetry-structlog/src/opentelemetry-structlog/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py b/handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py new file mode 100644 index 0000000000..356a00ab33 --- /dev/null +++ b/handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py @@ -0,0 +1,161 @@ +"""OpenTelemetry processor for structlog.""" + +import traceback +from datetime import datetime, timezone +from typing import Dict + +import structlog +from opentelemetry._logs import std_to_otel +from opentelemetry.sdk._logs._internal import LoggerProvider, LogRecord +from opentelemetry.sdk._logs._internal.export import BatchLogRecordProcessor, LogExporter +from opentelemetry.sdk.resources import Resource +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import get_current_span +from structlog._frames import _format_exception +from structlog._log_levels import NAME_TO_LEVEL +from structlog.processors import _figure_out_exc_info + +_EXCLUDE_ATTRS = {"exception", "timestamp"} + + +class OpenTelemetryExporter: + """A structlog processor that writes logs in OTLP format to a collector. + + Note: this will replace (or insert if not present) the `timestamp` key in the + `event_dict` to be in an ISO 8601 format that is more widely recognized. This + means that `structlog.processors.TimeStamper` is not required to be added to the + processors list if this processor is used. + + Note: this also performs the operations done by + `structlog.processors.ExceptionRenderer`. DO NOT use `ExceptionRenderer` in the + same processor pipeline as this processor. + """ + + # this was largely inspired by the OpenTelemetry handler for stdlib `logging`: + # https://github.com/open-telemetry/opentelemetry-python/blob/8f312c49a5c140c14d1829c66abfe4e859ad8fd7/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py#L318 + + def __init__( + self, + service_name: str, + server_hostname: str, + exporter: LogExporter, + ) -> None: + logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": service_name, + "service.instance.id": server_hostname, + } + ), + ) + + logger_provider.add_log_record_processor( + BatchLogRecordProcessor(exporter, max_export_batch_size=1) + ) + + self._logger_provider = logger_provider + self._logger = logger_provider.get_logger(__name__) + + def _pre_process( + self, event_dict: structlog.typing.EventDict + ) -> structlog.typing.EventDict: + event_dict["timestamp"] = datetime.now(timezone.utc) + + self._pre_process_exc_info(event_dict) + + return event_dict + + def _post_process( + self, event_dict: structlog.typing.EventDict + ) -> structlog.typing.EventDict: + event_dict["timestamp"] = event_dict["timestamp"].isoformat() + + self._post_process_exc_info(event_dict) + + return event_dict + + def _pre_process_exc_info( + self, event_dict: structlog.typing.EventDict + ) -> structlog.typing.EventDict: + exc_info = event_dict.pop("exc_info", None) + if exc_info is not None: + event_dict["exception"] = _figure_out_exc_info(exc_info) + + return event_dict + + def _post_process_exc_info( + self, event_dict: structlog.typing.EventDict + ) -> structlog.typing.EventDict: + exception = event_dict.pop("exception", None) + if exception is not None: + event_dict["exception"] = _format_exception(exception) + + return event_dict + + def _translate( + self, + timestamp: int, + extra_attrs: Dict[str, str], + event_dict: structlog.typing.EventDict, + ) -> LogRecord: + span_context = get_current_span().get_span_context() + # attributes = self._get_attributes(record) + severity_number = std_to_otel(NAME_TO_LEVEL[event_dict["level"]]) + + return LogRecord( + timestamp=timestamp, + trace_id=span_context.trace_id, + span_id=span_context.span_id, + trace_flags=span_context.trace_flags, + severity_text=event_dict["level"], + severity_number=severity_number, + body=event_dict["event"], + resource=self._logger.resource, + attributes={ + **{k: v for k, v in event_dict.items() if k not in _EXCLUDE_ATTRS}, + **extra_attrs, + }, + ) + + @staticmethod + def _parse_timestamp(event_dict: structlog.typing.EventDict) -> int: + return int(event_dict["timestamp"].timestamp() * 1e9) + + @staticmethod + def _parse_exception(event_dict: structlog.typing.EventDict) -> Dict[str, str]: + # taken from: https://github.com/open-telemetry/opentelemetry-python/blob/c4d17e9f14f3cafb6757b96eefabdc7ed4891306/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py#L458-L475 + attributes: Dict[str, str] = {} + exception = event_dict.get("exception", None) + if exception is not None: + exc_type = "" + message = "" + stack_trace = "" + exctype, value, tb = exception + if exctype is not None: + exc_type = exctype.__name__ + if value is not None and value.args: + message = value.args[0] + if tb is not None: + # https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation + stack_trace = "".join(traceback.format_exception(*exception)) + attributes[SpanAttributes.EXCEPTION_TYPE] = exc_type + attributes[SpanAttributes.EXCEPTION_MESSAGE] = message + attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stack_trace + + return attributes + + def __call__( + self, + logger: structlog.typing.WrappedLogger, + name: str, + event_dict: structlog.typing.EventDict, + ): + """Emit a record.""" + event_dict = self._pre_process(event_dict) + timestamp = self._parse_timestamp(event_dict) + extra_attrs = self._parse_exception(event_dict) + event_dict = self._post_process(event_dict) + + self._logger.emit(self._translate(timestamp, extra_attrs, event_dict)) + + return event_dict diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py b/handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" From 4e18784d5a4c43f757518bc35b8d6a3eaa23768a Mon Sep 17 00:00:00 2001 From: Miguel Castilho Date: Tue, 26 Mar 2024 23:36:59 -0400 Subject: [PATCH 2/7] Able to import structlog handler --- .../opentelemetry-structlog => }/__init__.py | 0 .../README.md | 0 handlers/opentelemetry_structlog/__init__.py | 0 .../pyproject.toml | 0 .../opentelemetry_structlog/src/__init__.py | 0 .../src}/exporter.py | 2 +- .../src}/version.py | 0 .../test-requirements.txt | 1 + .../tests/quickie.py | 18 ++++++++++++++++++ .../tests/test_logging.py | 5 +++++ tox.ini | 2 ++ 11 files changed, 27 insertions(+), 1 deletion(-) rename handlers/{opentelemetry-structlog/src/opentelemetry-structlog => }/__init__.py (100%) rename handlers/{opentelemetry-structlog => opentelemetry_structlog}/README.md (100%) create mode 100644 handlers/opentelemetry_structlog/__init__.py rename handlers/{opentelemetry-structlog => opentelemetry_structlog}/pyproject.toml (100%) create mode 100644 handlers/opentelemetry_structlog/src/__init__.py rename handlers/{opentelemetry-structlog/src/opentelemetry-structlog => opentelemetry_structlog/src}/exporter.py (99%) rename handlers/{opentelemetry-structlog/src/opentelemetry-structlog => opentelemetry_structlog/src}/version.py (100%) create mode 100644 instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/__init__.py b/handlers/__init__.py similarity index 100% rename from handlers/opentelemetry-structlog/src/opentelemetry-structlog/__init__.py rename to handlers/__init__.py diff --git a/handlers/opentelemetry-structlog/README.md b/handlers/opentelemetry_structlog/README.md similarity index 100% rename from handlers/opentelemetry-structlog/README.md rename to handlers/opentelemetry_structlog/README.md diff --git a/handlers/opentelemetry_structlog/__init__.py b/handlers/opentelemetry_structlog/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/handlers/opentelemetry-structlog/pyproject.toml b/handlers/opentelemetry_structlog/pyproject.toml similarity index 100% rename from handlers/opentelemetry-structlog/pyproject.toml rename to handlers/opentelemetry_structlog/pyproject.toml diff --git a/handlers/opentelemetry_structlog/src/__init__.py b/handlers/opentelemetry_structlog/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py b/handlers/opentelemetry_structlog/src/exporter.py similarity index 99% rename from handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py rename to handlers/opentelemetry_structlog/src/exporter.py index 356a00ab33..9300391184 100644 --- a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/exporter.py +++ b/handlers/opentelemetry_structlog/src/exporter.py @@ -18,7 +18,7 @@ _EXCLUDE_ATTRS = {"exception", "timestamp"} -class OpenTelemetryExporter: +class StructlogHandler: """A structlog processor that writes logs in OTLP format to a collector. Note: this will replace (or insert if not present) the `timestamp` key in the diff --git a/handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py b/handlers/opentelemetry_structlog/src/version.py similarity index 100% rename from handlers/opentelemetry-structlog/src/opentelemetry-structlog/version.py rename to handlers/opentelemetry_structlog/src/version.py diff --git a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt index f376796169..028045e72c 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt +++ b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt @@ -13,5 +13,6 @@ tomli==2.0.1 typing_extensions==4.9.0 wrapt==1.16.0 zipp==3.17.0 +structlog==24.1.0 -e opentelemetry-instrumentation -e instrumentation/opentelemetry-instrumentation-logging diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py b/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py new file mode 100644 index 0000000000..0043b79ee4 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py @@ -0,0 +1,18 @@ +import logging +from typing import Optional +from unittest import mock + +import pytest + +from opentelemetry.instrumentation.logging import ( # pylint: disable=no-name-in-module + DEFAULT_LOGGING_FORMAT, + LoggingInstrumentor, +) +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace import ProxyTracer, get_tracer + +import sys +sys.path.insert(0, "../../../") +from handlers.opentelemetry_structlog.src.exporter import StructlogHandler + + diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index a5a0d5adff..19e6be6006 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -25,6 +25,11 @@ from opentelemetry.test.test_base import TestBase from opentelemetry.trace import ProxyTracer, get_tracer +import sys +sys.path.insert(0, "../../../") +from handlers.opentelemetry_structlog.src.exporter import StructlogHandler + + class FakeTracerProvider: def get_tracer( # pylint: disable=no-self-use diff --git a/tox.ini b/tox.ini index 068b5d583e..94e4e208d6 100644 --- a/tox.ini +++ b/tox.ini @@ -313,6 +313,7 @@ setenv = ; i.e: CORE_REPO_SHA=dde62cebffe519c35875af6d06fae053b3be65ec tox -e CORE_REPO_SHA={env:CORE_REPO_SHA:main} CORE_REPO=git+https://github.com/open-telemetry/opentelemetry-python.git@{env:CORE_REPO_SHA} + PYTHONPATH={toxinidir} commands_pre = ; Install without -e to test the actual installation @@ -737,6 +738,7 @@ commands_pre = -e {toxinidir}/instrumentation/opentelemetry-instrumentation-redis \ -e {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade \ {env:CORE_REPO}\#egg=opentelemetry-exporter-opencensus&subdirectory=exporter/opentelemetry-exporter-opencensus + structlog docker-compose up -d python check_availability.py From 21e5c621dd82741c88c5c8bd88b57f07d062f330 Mon Sep 17 00:00:00 2001 From: Miguel Castilho Date: Wed, 27 Mar 2024 00:39:38 -0400 Subject: [PATCH 3/7] Minor changes: removed useless stuff --- .../opentelemetry-instrumentation-logging/tests/test_logging.py | 2 -- tox.ini | 1 - 2 files changed, 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index 19e6be6006..c352da78bb 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -25,8 +25,6 @@ from opentelemetry.test.test_base import TestBase from opentelemetry.trace import ProxyTracer, get_tracer -import sys -sys.path.insert(0, "../../../") from handlers.opentelemetry_structlog.src.exporter import StructlogHandler diff --git a/tox.ini b/tox.ini index 94e4e208d6..6d02aca61c 100644 --- a/tox.ini +++ b/tox.ini @@ -738,7 +738,6 @@ commands_pre = -e {toxinidir}/instrumentation/opentelemetry-instrumentation-redis \ -e {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade \ {env:CORE_REPO}\#egg=opentelemetry-exporter-opencensus&subdirectory=exporter/opentelemetry-exporter-opencensus - structlog docker-compose up -d python check_availability.py From 2e22c2e7da3bf2d93dd2747142adcba5731e5f5d Mon Sep 17 00:00:00 2001 From: doshi36 Date: Thu, 28 Mar 2024 21:49:51 -0400 Subject: [PATCH 4/7] Parth Doshi - Added Test Cases --- .../src/test_logging.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 handlers/opentelemetry_structlog/src/test_logging.py diff --git a/handlers/opentelemetry_structlog/src/test_logging.py b/handlers/opentelemetry_structlog/src/test_logging.py new file mode 100644 index 0000000000..cc4c0d14a0 --- /dev/null +++ b/handlers/opentelemetry_structlog/src/test_logging.py @@ -0,0 +1,55 @@ +import pytest +from unittest.mock import Mock +from exporter import OpenTelemetryExporter +from opentelemetry.sdk._logs._internal.export import LogExporter +from datetime import datetime, timezone + +# Test Initialization +@pytest.fixture +def otel_exporter(): + # Mock the LogExporter dependency + mock_exporter = Mock(spec=LogExporter) + # Instantiate the OpenTelemetryExporter with mock dependencies + exporter = OpenTelemetryExporter("test_service", "test_host", mock_exporter) + return exporter + +def test_initialization(otel_exporter): + assert otel_exporter._logger_provider is not None, "LoggerProvider should be initialized" + assert otel_exporter._logger is not None, "Logger should be initialized" + +def test_pre_process_adds_timestamp(otel_exporter): + event_dict = {"event": "test_event"} + processed_event = otel_exporter._pre_process(event_dict) + assert "timestamp" in processed_event, "Timestamp should be added in pre-processing" + +def test_post_process_formats_timestamp(otel_exporter): + # Assuming the pre_process method has added a datetime object + event_dict = {"timestamp": datetime.now(timezone.utc)} + processed_event = otel_exporter._post_process(event_dict) + assert isinstance(processed_event["timestamp"], str), "Timestamp should be formatted to string in ISO format" + +def test_parse_exception(otel_exporter): + # Mocking an exception event + exception = (ValueError, ValueError("mock error"), None) + event_dict = {"exception": exception} + parsed_exception = otel_exporter._parse_exception(event_dict) + assert parsed_exception["exception.type"] == "ValueError", "Exception type should be parsed" + assert parsed_exception["exception.message"] == "mock error", "Exception message should be parsed" + # Further assertions can be added for stack trace + +def test_parse_timestamp(otel_exporter): + # Assuming a specific datetime for consistency + fixed_datetime = datetime(2020, 1, 1, tzinfo=timezone.utc) + event_dict = {"timestamp": fixed_datetime} + timestamp = otel_exporter._parse_timestamp(event_dict) + expected_timestamp = 1577836800000000000 # Expected nanoseconds since epoch + assert timestamp == expected_timestamp, "Timestamp should be correctly parsed to nanoseconds" + +def test_call_method_processes_log_correctly(otel_exporter, mocker): + mocker.patch.object(otel_exporter._logger, 'emit') + event_dict = {"level": "info", "event": "test event", "timestamp": datetime.now(timezone.utc)} + processed_event = otel_exporter(logger=None, name=None, event_dict=event_dict) + + otel_exporter._logger.emit.assert_called_once() + assert "timestamp" in processed_event, "Processed event should contain a timestamp" + # Add more assertions based on expected transformations and processing outcomes \ No newline at end of file From 12a40358aa174aeaf37aaa8a650110d185178e79 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Fri, 29 Mar 2024 16:22:26 -0600 Subject: [PATCH 5/7] all structlog tests pass except test_call_method_processes_log_correctly - Caroline Gilbert --- .../tests/test_logging.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index c352da78bb..3e167d49b2 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -18,6 +18,13 @@ import pytest +# Imports for StructlogHandler tests +from unittest.mock import Mock +from handlers.opentelemetry_structlog.src.exporter import LogExporter +from datetime import datetime, timezone + + + from opentelemetry.instrumentation.logging import ( # pylint: disable=no-name-in-module DEFAULT_LOGGING_FORMAT, LoggingInstrumentor, @@ -210,3 +217,74 @@ def test_uninstrumented(self): self.assertFalse(hasattr(record, "otelTraceID")) self.assertFalse(hasattr(record, "otelServiceName")) self.assertFalse(hasattr(record, "otelTraceSampled")) + +# StructlogHandler Tests +# Test Initialization +class TestStructlogHandler(TestBase): + @pytest.fixture(autouse=True) + def inject_fixtures(self, caplog): + self.caplog = caplog # pylint: disable=attribute-defined-outside-init + + def setUp(self): + super().setUp() + LoggingInstrumentor().instrument() + self.tracer = get_tracer(__name__) + + def tearDown(self): + super().tearDown() + LoggingInstrumentor().uninstrument() + + def structlog_exporter(self): + with self.caplog.at_level(level=logging.INFO): + # Mock the LogExporter dependency + mock_exporter = Mock(spec=LogExporter) + # Instantiate the StructlogHandler with mock dependencies + exporter = StructlogHandler("test_service", "test_host", mock_exporter) + return exporter + + + def test_initialization(self): + exporter = self.structlog_exporter() + assert exporter._logger_provider is not None, "LoggerProvider should be initialized" + assert exporter._logger is not None, "Logger should be initialized" + + def test_pre_process_adds_timestamp(self): + event_dict = {"event": "test_event"} + processed_event = self.structlog_exporter()._pre_process(event_dict) + assert "timestamp" in processed_event, "Timestamp should be added in pre-processing" + + def test_post_process_formats_timestamp(self): + # Assuming the pre_process method has added a datetime object + event_dict = {"timestamp": datetime.now(timezone.utc)} + processed_event = self.structlog_exporter()._post_process(event_dict) + assert isinstance(processed_event["timestamp"], str), "Timestamp should be formatted to string in ISO format" + + def test_parse_exception(self): + # Mocking an exception event + exception = (ValueError, ValueError("mock error"), None) + event_dict = {"exception": exception} + parsed_exception = self.structlog_exporter()._parse_exception(event_dict) + assert parsed_exception["exception.type"] == "ValueError", "Exception type should be parsed" + assert parsed_exception["exception.message"] == "mock error", "Exception message should be parsed" + # Further assertions can be added for stack trace + + def test_parse_timestamp(self): + # Assuming a specific datetime for consistency + fixed_datetime = datetime(2020, 1, 1, tzinfo=timezone.utc) + event_dict = {"timestamp": fixed_datetime} + timestamp = self.structlog_exporter()._parse_timestamp(event_dict) + expected_timestamp = 1577836800000000000 # Expected nanoseconds since epoch + assert timestamp == expected_timestamp, "Timestamp should be correctly parsed to nanoseconds" + + def test_call_method_processes_log_correctly(self, mocker): + exporter_instance = self.structlog_exporter() + mocker.patch.object(exporter_instance._logger, 'emit') + event_dict = {"level": "info", "event": "test event", "timestamp": datetime.now(timezone.utc)} + processed_event = exporter_instance.emit(logger=None, name=None, event_dict=event_dict) + + exporter_instance._logger.emit.assert_called_once() + assert "timestamp" in processed_event, "Processed event should contain a timestamp" + # Add more assertions based on expected transformations and processing outcomes + + + From 097be29acc1fa492498b53c4db8703f2054b199b Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 31 Mar 2024 10:54:46 -0600 Subject: [PATCH 6/7] Caroline Gilbert: fixed tests - all pass --- .../tests/test_logging.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index 3e167d49b2..d425e0be27 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -22,6 +22,8 @@ from unittest.mock import Mock from handlers.opentelemetry_structlog.src.exporter import LogExporter from datetime import datetime, timezone +from unittest.mock import MagicMock + @@ -224,6 +226,9 @@ class TestStructlogHandler(TestBase): @pytest.fixture(autouse=True) def inject_fixtures(self, caplog): self.caplog = caplog # pylint: disable=attribute-defined-outside-init + + def mocker(self): + return MagicMock() def setUp(self): super().setUp() @@ -276,15 +281,22 @@ def test_parse_timestamp(self): expected_timestamp = 1577836800000000000 # Expected nanoseconds since epoch assert timestamp == expected_timestamp, "Timestamp should be correctly parsed to nanoseconds" - def test_call_method_processes_log_correctly(self, mocker): - exporter_instance = self.structlog_exporter() - mocker.patch.object(exporter_instance._logger, 'emit') + def test_call_method2(self): + # Mock the logger and exporter + exporter = MagicMock() + logger = MagicMock() + exporter_instance = StructlogHandler("test_service", "test_host", exporter) + exporter_instance._logger = logger + + # Define an event dictionary event_dict = {"level": "info", "event": "test event", "timestamp": datetime.now(timezone.utc)} - processed_event = exporter_instance.emit(logger=None, name=None, event_dict=event_dict) - exporter_instance._logger.emit.assert_called_once() - assert "timestamp" in processed_event, "Processed event should contain a timestamp" - # Add more assertions based on expected transformations and processing outcomes + # Call the __call__ method of StructlogHandler + processed_event = exporter_instance(logger=None, name=None, event_dict=event_dict) + + # Assert that the logger's emit method was called with the processed event + logger.emit.assert_called_once() + From 8bf5af44f8fb7d8fa82a264e694a704b4dbe071c Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 31 Mar 2024 10:59:53 -0600 Subject: [PATCH 7/7] Caroline Gilbert: fixed test name --- .../opentelemetry-instrumentation-logging/tests/test_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index d425e0be27..ecd96fd810 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -281,7 +281,7 @@ def test_parse_timestamp(self): expected_timestamp = 1577836800000000000 # Expected nanoseconds since epoch assert timestamp == expected_timestamp, "Timestamp should be correctly parsed to nanoseconds" - def test_call_method2(self): + def test_call_method_processes_log_correctly(self): # Mock the logger and exporter exporter = MagicMock() logger = MagicMock()