From 1133701752cd33e0d2c90b43af0e477b6f324ae0 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 24 Mar 2024 14:14:33 -0400 Subject: [PATCH 01/25] 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 02/25] 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 03/25] 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 04/25] 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 05/25] 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 06/25] 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 07/25] 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() From 39cde552779f00c65c516913b48b410d2c0552f9 Mon Sep 17 00:00:00 2001 From: Miguel Castilho Date: Wed, 10 Apr 2024 20:56:24 -0400 Subject: [PATCH 08/25] loguru handler --- handlers/opentelemetry_loguru/__init__.py | 0 handlers/opentelemetry_loguru/src/__init__.py | 0 handlers/opentelemetry_loguru/src/exporter.py | 184 ++++++++++++++++++ .../test-requirements.txt | 1 + .../tests/test_logging.py | 2 + 5 files changed, 187 insertions(+) create mode 100644 handlers/opentelemetry_loguru/__init__.py create mode 100644 handlers/opentelemetry_loguru/src/__init__.py create mode 100644 handlers/opentelemetry_loguru/src/exporter.py diff --git a/handlers/opentelemetry_loguru/__init__.py b/handlers/opentelemetry_loguru/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/handlers/opentelemetry_loguru/src/__init__.py b/handlers/opentelemetry_loguru/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/handlers/opentelemetry_loguru/src/exporter.py b/handlers/opentelemetry_loguru/src/exporter.py new file mode 100644 index 0000000000..79fcf9d2c5 --- /dev/null +++ b/handlers/opentelemetry_loguru/src/exporter.py @@ -0,0 +1,184 @@ +import traceback +from datetime import datetime, timezone +from typing import Dict + +import loguru +import traceback +from os import environ +from time import time_ns +from typing import Any, Callable, Optional, Tuple, Union # noqa +from opentelemetry._logs import ( + NoOpLogger, + SeverityNumber, + get_logger, + get_logger_provider, + std_to_otel, +) +from opentelemetry.attributes import BoundedAttributes +from opentelemetry.sdk.environment_variables import ( + OTEL_ATTRIBUTE_COUNT_LIMIT, + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, +) +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.util import ns_to_iso_str +from opentelemetry.sdk.util.instrumentation import InstrumentationScope +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import ( + format_span_id, + format_trace_id, + get_current_span, +) +from opentelemetry.trace.span import TraceFlags +from opentelemetry.util.types import Attributes + +from opentelemetry._logs import Logger as APILogger +from opentelemetry._logs import LoggerProvider as APILoggerProvider +from opentelemetry._logs import LogRecord as APILogRecord + +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 opentelemetry._logs.severity import SeverityNumber + +import sys +import json + +_STD_TO_OTEL = { + 10: SeverityNumber.DEBUG, + 11: SeverityNumber.DEBUG2, + 12: SeverityNumber.DEBUG3, + 13: SeverityNumber.DEBUG4, + 14: SeverityNumber.DEBUG4, + 15: SeverityNumber.DEBUG4, + 16: SeverityNumber.DEBUG4, + 17: SeverityNumber.DEBUG4, + 18: SeverityNumber.DEBUG4, + 19: SeverityNumber.DEBUG4, + 20: SeverityNumber.INFO, + 21: SeverityNumber.INFO2, + 22: SeverityNumber.INFO3, + 23: SeverityNumber.INFO4, + 24: SeverityNumber.INFO4, + 25: SeverityNumber.INFO4, + 26: SeverityNumber.INFO4, + 27: SeverityNumber.INFO4, + 28: SeverityNumber.INFO4, + 29: SeverityNumber.INFO4, + 30: SeverityNumber.WARN, + 31: SeverityNumber.WARN2, + 32: SeverityNumber.WARN3, + 33: SeverityNumber.WARN4, + 34: SeverityNumber.WARN4, + 35: SeverityNumber.WARN4, + 36: SeverityNumber.WARN4, + 37: SeverityNumber.WARN4, + 38: SeverityNumber.WARN4, + 39: SeverityNumber.WARN4, + 40: SeverityNumber.ERROR, + 41: SeverityNumber.ERROR2, + 42: SeverityNumber.ERROR3, + 43: SeverityNumber.ERROR4, + 44: SeverityNumber.ERROR4, + 45: SeverityNumber.ERROR4, + 46: SeverityNumber.ERROR4, + 47: SeverityNumber.ERROR4, + 48: SeverityNumber.ERROR4, + 49: SeverityNumber.ERROR4, + 50: SeverityNumber.FATAL, + 51: SeverityNumber.FATAL2, + 52: SeverityNumber.FATAL3, + 53: SeverityNumber.FATAL4, +} + + +class LoguruHandler: + + # 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, + logger_provider=None, + ) -> None: + + self._logger_provider = logger_provider or get_logger_provider() + self._logger = get_logger( + __name__, logger_provider=self._logger_provider + ) + + + def _get_attributes(record) -> Attributes: + attributes = {key:value for key, value in record.items()} + + # Add standard code attributes for logs. + attributes[SpanAttributes.CODE_FILEPATH] = record['file'] #This includes file and path -> (file, path) + attributes[SpanAttributes.CODE_FUNCTION] = record['function'] + attributes[SpanAttributes.CODE_LINENO] = record['line'] + + if record['exception'] is not None: + + attributes[SpanAttributes.EXCEPTION_TYPE] = record['exception'].type + + attributes[SpanAttributes.EXCEPTION_MESSAGE] = record['exception'].value + + attributes[SpanAttributes.EXCEPTION_STACKTRACE] = record['exception'].traceback + + return attributes + + def _loguru_to_otel(levelno: int) -> SeverityNumber: + if levelno < 10 or levelno == 25: + return SeverityNumber.UNSPECIFIED + + elif levelno > 53: + return SeverityNumber.FATAL4 + + return _STD_TO_OTEL[levelno] + + + def _translate(self, record) -> LogRecord: + + #Timestamp + timestamp = record["time"] + + #Observed timestamp + observedTimestamp = time_ns() + + #Span context + spanContext = get_current_span().get_span_context() + + #Setting the level name + if record['level'].name == 'WARNING': + levelName = 'WARN' + elif record['level'].name == 'TRACE' or record['level'].name == 'SUCCESS': + levelName = 'NOTSET' + else: + levelName = record['level'].name + + #Severity number + severityNumber = self._loguru_to_otel(int(record["level"].no)) + + #Getting attributes + attributes = self._get_attributes(record) + + + return LogRecord( + timestamp = timestamp, + observed_timestamp = observedTimestamp, + trace_id = spanContext.trace_id, + span_id = spanContext.span_id, + trace_flags = spanContext.trace_flags, + severity_text = levelName, + severity_number = severityNumber, + body=record['message'], + resource = self._logger.resource, + attributes=attributes + ) + + def sink(self, record) -> None: + + self._logger.emit(self._translate(record)) + \ No newline at end of file diff --git a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt index 028045e72c..1f015a3373 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt +++ b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt @@ -14,5 +14,6 @@ typing_extensions==4.9.0 wrapt==1.16.0 zipp==3.17.0 structlog==24.1.0 +loguru==0.7.2 -e opentelemetry-instrumentation -e instrumentation/opentelemetry-instrumentation-logging diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index ecd96fd810..8216ba7147 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -21,6 +21,7 @@ # Imports for StructlogHandler tests from unittest.mock import Mock from handlers.opentelemetry_structlog.src.exporter import LogExporter + from datetime import datetime, timezone from unittest.mock import MagicMock @@ -35,6 +36,7 @@ from opentelemetry.trace import ProxyTracer, get_tracer from handlers.opentelemetry_structlog.src.exporter import StructlogHandler +from handlers.opentelemetry_loguru.src.exporter import LoguruHandler From 3a648981140c8e52433f304370a005f9d9c5ac28 Mon Sep 17 00:00:00 2001 From: doshi36 Date: Mon, 15 Apr 2024 21:55:07 -0400 Subject: [PATCH 09/25] Buggy Test Cases --- .../tests/test_logging.py | 82 +++++++++++++++++-- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index ecd96fd810..f22289a710 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -22,7 +22,7 @@ from unittest.mock import Mock from handlers.opentelemetry_structlog.src.exporter import LogExporter from datetime import datetime, timezone -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch @@ -247,7 +247,6 @@ def structlog_exporter(self): 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" @@ -271,7 +270,6 @@ def test_parse_exception(self): 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 @@ -296,7 +294,77 @@ def test_call_method_processes_log_correctly(self): # Assert that the logger's emit method was called with the processed event logger.emit.assert_called_once() - - - - + + # Adding new test cases + def test_log_record_translation_attributes(self): + """Verify that event_dict translates correctly into a LogRecord with the correct attributes.""" + exporter = MagicMock() + logger = MagicMock() + exporter_instance = StructlogHandler("test_service", "test_host", exporter) + exporter_instance._logger = logger + + timestamp = datetime.now(timezone.utc) + event_dict = { + "level": "info", + "event": "test event", + "timestamp": timestamp + } + # Get the StructlogHandler instance + + # Assuming StructlogHandler has a method to process and possibly log the event_dict directly. + # Use the instance to process the event_dict. + # Mocking the internal logger's emit method to capture the log record + with patch.object(exporter_instance._logger, 'emit') as mock_emit: + exporter_instance(event_dict=event_dict, logger=None, name=None) + calls = mock_emit.call_args_list + assert len(calls) > 0, "Emit should be called" + log_record = calls[0][0][0] # First call, first arg + + # Assuming log_record is the structured log that would have been emitted, + # and you need to verify its contents. + # Need to adjust the assertion depending on how log records are structured. + # I am assuming log_record is a dictionary that was passed to logger.emit. + assert log_record["body"] == event_dict["event"], "LogRecord body should match event" + assert log_record["timestamp"] == timestamp, "LogRecord timestamp should match event" + assert log_record["level"] == event_dict["level"], "LogRecord level should match event" + + # def test_filtering_of_excluded_attributes(self): + # """Ensure specified attributes are not passed to the log record.""" + # event_dict = { + # "level": "error", + # "event": "Something happened!", + # "timestamp": datetime.now(timezone.utc), + # "exception": ValueError("An error occurred") + # } + + # # Get the StructlogHandler instance + # exporter_instance = self.structlog_exporter() + + # with patch.object(exporter_instance._logger, "_logger") as mocked_logger: + # exporter_instance(event_dict=event_dict, logger=None, name=None) + # calls = mocked_logger.emit.call_args_list + # log_record = calls[0][0][0] + # assert "exception" not in log_record.attributes, "Excluded attributes should not be in the log record" + + # def test_trace_context_propogation(self): + # """Ensure trace context is correctly propagated to the log record.""" + # with self.tracer.start_as_current_span("test_span") as span: + # span_id = format(span.get_span_context().span_id, "016x") + # trace_id = format(span.get_span_context().trace_id, "032x") + # trace_sampled = span.get_span_context().trace_flags.sampled + # event_dict = { + # "level": "info", + # "event": "test event", + # "timestamp": datetime.now(timezone.utc) + # } + + # # Get the StructlogHandler instance + # exporter_instance = self.structlog_exporter() + + # with patch.object(exporter_instance, "_logger") as mocked_logger: + # exporter_instance(event_dict=event_dict, logger=None, name=None) + # calls = mocked_logger.emit.call_args_list + # log_record = calls[0][0][0] + # assert log_record.attributes["otelSpanID"] == span_id, "Span ID should be propagated" + # assert log_record.attributes["otelTraceID"] == trace_id, "Trace ID should be propagated" + # assert log_record.attributes["otelTraceSampled"] == trace_sampled, "" From 45aa0a3a5b7c80816216a2daaeda95a2a73427f5 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Wed, 17 Apr 2024 10:55:20 -0400 Subject: [PATCH 10/25] Caroline Gilbert: new structlog tests pass --- .../src/test_logging.py | 55 --------- .../tests/test_logging.py | 106 ++++++++++-------- 2 files changed, 60 insertions(+), 101 deletions(-) delete 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 deleted file mode 100644 index cc4c0d14a0..0000000000 --- a/handlers/opentelemetry_structlog/src/test_logging.py +++ /dev/null @@ -1,55 +0,0 @@ -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 diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index f22289a710..399888769d 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -21,7 +21,7 @@ # Imports for StructlogHandler tests from unittest.mock import Mock from handlers.opentelemetry_structlog.src.exporter import LogExporter -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta from unittest.mock import MagicMock, patch @@ -295,7 +295,7 @@ def test_call_method_processes_log_correctly(self): # Assert that the logger's emit method was called with the processed event logger.emit.assert_called_once() - # Adding new test cases + def test_log_record_translation_attributes(self): """Verify that event_dict translates correctly into a LogRecord with the correct attributes.""" exporter = MagicMock() @@ -303,7 +303,7 @@ def test_log_record_translation_attributes(self): exporter_instance = StructlogHandler("test_service", "test_host", exporter) exporter_instance._logger = logger - timestamp = datetime.now(timezone.utc) + timestamp = datetime.now(timezone.utc).isoformat() event_dict = { "level": "info", "event": "test event", @@ -315,7 +315,7 @@ def test_log_record_translation_attributes(self): # Use the instance to process the event_dict. # Mocking the internal logger's emit method to capture the log record with patch.object(exporter_instance._logger, 'emit') as mock_emit: - exporter_instance(event_dict=event_dict, logger=None, name=None) + exporter_instance(event_dict=event_dict, logger=logger, name=None) calls = mock_emit.call_args_list assert len(calls) > 0, "Emit should be called" log_record = calls[0][0][0] # First call, first arg @@ -324,47 +324,61 @@ def test_log_record_translation_attributes(self): # and you need to verify its contents. # Need to adjust the assertion depending on how log records are structured. # I am assuming log_record is a dictionary that was passed to logger.emit. - assert log_record["body"] == event_dict["event"], "LogRecord body should match event" - assert log_record["timestamp"] == timestamp, "LogRecord timestamp should match event" - assert log_record["level"] == event_dict["level"], "LogRecord level should match event" + assert log_record.body == event_dict["event"], "LogRecord body should match event" + + assert log_record.attributes["level"] == event_dict["level"], "LogRecord level should match event" + + def test_filtering_of_excluded_attributes(self): + """Ensure specified attributes are not passed to the log record.""" + event_dict = { + "level": "error", + "event": "Something happened!", + "timestamp": datetime.now(timezone.utc), + "exception": (ValueError, ValueError("An error occurred"), None) + } + + # Get the StructlogHandler instance + exporter_instance = self.structlog_exporter() + + with patch.object(exporter_instance._logger, "emit") as mocked_emit: + # Call the exporter_instance with the event_dict + exporter_instance(event_dict=event_dict, logger=None, name=None) + + # Check if emit method was called + mocked_emit.assert_called_once() + + # Get the log record passed to emit method + log_record = mocked_emit.call_args.args[0] + + # Check if the exception attribute is not present in the log record + assert "exception" not in log_record.attributes, "Excluded attributes should not be in the log record" - # def test_filtering_of_excluded_attributes(self): - # """Ensure specified attributes are not passed to the log record.""" - # event_dict = { - # "level": "error", - # "event": "Something happened!", - # "timestamp": datetime.now(timezone.utc), - # "exception": ValueError("An error occurred") - # } - - # # Get the StructlogHandler instance - # exporter_instance = self.structlog_exporter() - - # with patch.object(exporter_instance._logger, "_logger") as mocked_logger: - # exporter_instance(event_dict=event_dict, logger=None, name=None) - # calls = mocked_logger.emit.call_args_list - # log_record = calls[0][0][0] - # assert "exception" not in log_record.attributes, "Excluded attributes should not be in the log record" - - # def test_trace_context_propogation(self): - # """Ensure trace context is correctly propagated to the log record.""" - # with self.tracer.start_as_current_span("test_span") as span: - # span_id = format(span.get_span_context().span_id, "016x") - # trace_id = format(span.get_span_context().trace_id, "032x") - # trace_sampled = span.get_span_context().trace_flags.sampled - # event_dict = { - # "level": "info", - # "event": "test event", - # "timestamp": datetime.now(timezone.utc) - # } + + def test_trace_context_propogation(self): + """Ensure trace context is correctly propagated to the log record.""" + with self.tracer.start_as_current_span("test_span") as span: + span_id = format(span.get_span_context().span_id, "016x") + trace_id = format(span.get_span_context().trace_id, "032x") + trace_sampled = span.get_span_context().trace_flags.sampled + event_dict = { + "level": "info", + "event": "test event", + "timestamp": datetime.now(timezone.utc) + } - # # Get the StructlogHandler instance - # exporter_instance = self.structlog_exporter() - - # with patch.object(exporter_instance, "_logger") as mocked_logger: - # exporter_instance(event_dict=event_dict, logger=None, name=None) - # calls = mocked_logger.emit.call_args_list - # log_record = calls[0][0][0] - # assert log_record.attributes["otelSpanID"] == span_id, "Span ID should be propagated" - # assert log_record.attributes["otelTraceID"] == trace_id, "Trace ID should be propagated" - # assert log_record.attributes["otelTraceSampled"] == trace_sampled, "" + # Get the StructlogHandler instance + exporter_instance = self.structlog_exporter() + + with patch.object(exporter_instance, "_logger") as mocked_logger: + exporter_instance(event_dict=event_dict, logger=None, name=None) + calls = mocked_logger.emit.call_args_list + log_record = calls[0][0][0] + + # Assert that the log record has the correct trace context + actual_span_id = format(log_record.span_id, "016x") + assert actual_span_id == span_id, "Span ID should be propagated" + + actual_trace_id = format(log_record.trace_id, "032x") + assert actual_trace_id == trace_id, "Trace ID should be propagated" + + assert log_record.trace_flags == trace_sampled, "Trace flags should be propagated" From 804b5795739d69ffbd35b5e8a72e10391c149b79 Mon Sep 17 00:00:00 2001 From: Miguel Castilho Date: Sat, 27 Apr 2024 16:07:47 -0400 Subject: [PATCH 11/25] All but one tests passing --- handlers/opentelemetry_loguru/src/exporter.py | 4 +- .../tests/test_logging.py | 113 ++++++++- out.txt | 223 ++++++++++++++++++ 3 files changed, 334 insertions(+), 6 deletions(-) create mode 100644 out.txt diff --git a/handlers/opentelemetry_loguru/src/exporter.py b/handlers/opentelemetry_loguru/src/exporter.py index 79fcf9d2c5..92014ce65f 100644 --- a/handlers/opentelemetry_loguru/src/exporter.py +++ b/handlers/opentelemetry_loguru/src/exporter.py @@ -111,7 +111,7 @@ def __init__( ) - def _get_attributes(record) -> Attributes: + def _get_attributes(self, record) -> Attributes: attributes = {key:value for key, value in record.items()} # Add standard code attributes for logs. @@ -129,7 +129,7 @@ def _get_attributes(record) -> Attributes: return attributes - def _loguru_to_otel(levelno: int) -> SeverityNumber: + def _loguru_to_otel(self, levelno: int) -> SeverityNumber: if levelno < 10 or levelno == 25: return SeverityNumber.UNSPECIFIED diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index 8216ba7147..aa7e337c15 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -15,6 +15,8 @@ import logging from typing import Optional from unittest import mock +import unittest +from collections import namedtuple import pytest @@ -23,22 +25,24 @@ from handlers.opentelemetry_structlog.src.exporter import LogExporter from datetime import datetime, timezone -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch +from opentelemetry.semconv.trace import SpanAttributes 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 +from opentelemetry.trace import ProxyTracer, get_tracer, get_current_span, SpanContext, TraceFlags from handlers.opentelemetry_structlog.src.exporter import StructlogHandler -from handlers.opentelemetry_loguru.src.exporter import LoguruHandler - +from handlers.opentelemetry_loguru.src.exporter import LoguruHandler, _STD_TO_OTEL +from opentelemetry._logs import get_logger_provider, get_logger +import time class FakeTracerProvider: def get_tracer( # pylint: disable=no-self-use @@ -302,3 +306,104 @@ def test_call_method_processes_log_correctly(self): + +class TestLoguruHandler(TestBase): + def setUp(self): + self.default_provider = get_logger_provider() + self.custom_provider = MagicMock() + self.record = { + "time": 1581000000.000123, + "level": MagicMock(name="ERROR", no=40), + "message": "Test message", + "file": "test_file.py", + "function": "test_function", + "line": 123, + "exception": None + } + # self.span_context = SpanContext( + # trace_id=1234, + # span_id=5678, + # trace_flags=TraceFlags(1), + # is_remote=False + # ) + self.span_context = get_current_span().get_span_context() + self.current_span = MagicMock() + self.current_span.get_span_context.return_value = self.span_context + + def test_initialization_with_default_provider(self): + handler = LoguruHandler() + self.assertEqual(handler._logger_provider, self.default_provider) + + def test_initialization_with_custom_provider(self): + handler = LoguruHandler(logger_provider=self.custom_provider) + self.assertEqual(handler._logger_provider, self.custom_provider) + + def test_attributes_extraction_without_exception(self): + attrs = LoguruHandler()._get_attributes(self.record) + expected_attrs = { + SpanAttributes.CODE_FILEPATH: 'test_file.py', + SpanAttributes.CODE_FUNCTION: 'test_function', + SpanAttributes.CODE_LINENO: 123 + } + + self.assertEqual(attrs[SpanAttributes.CODE_FILEPATH], expected_attrs[SpanAttributes.CODE_FILEPATH]) + self.assertEqual(attrs[SpanAttributes.CODE_FUNCTION], expected_attrs[SpanAttributes.CODE_FUNCTION]) + self.assertEqual(attrs[SpanAttributes.CODE_LINENO], expected_attrs[SpanAttributes.CODE_LINENO]) + + @patch('traceback.format_exception') + def test_attributes_extraction_with_exception(self, mock_format_exception): + mock_format_exception.return_value = 'Exception traceback' + exception = Exception("Test exception") + + ExceptionRecord = namedtuple('ExceptionRecord', ['type', 'value', 'traceback']) + +# Example usage: + exception_record = ExceptionRecord( + type=type(exception).__name__, + value=str(exception), + traceback=mock_format_exception(exception) + ) + self.record['exception'] = exception_record + # self.record['exception'].type = type(exception).__name__ + # self.record['exception'].value = str(exception) + # self.record['exception'].traceback = mock_format_exception(exception) + + attrs = LoguruHandler()._get_attributes(self.record) + + expected_attrs = { + SpanAttributes.CODE_FILEPATH: 'test_file.py', + SpanAttributes.CODE_FUNCTION: 'test_function', + SpanAttributes.CODE_LINENO: 123, + SpanAttributes.EXCEPTION_TYPE: 'Exception', + SpanAttributes.EXCEPTION_MESSAGE: 'Test exception', + SpanAttributes.EXCEPTION_STACKTRACE: 'Exception traceback' + } + + self.assertEqual(attrs[SpanAttributes.EXCEPTION_TYPE], expected_attrs[SpanAttributes.EXCEPTION_TYPE]) + self.assertEqual(attrs[SpanAttributes.EXCEPTION_MESSAGE], expected_attrs[SpanAttributes.EXCEPTION_MESSAGE]) + self.assertEqual(attrs[SpanAttributes.EXCEPTION_STACKTRACE], expected_attrs[SpanAttributes.EXCEPTION_STACKTRACE]) + + @patch('opentelemetry.trace.get_current_span') + def test_translation(self, mock_get_current_span): + mock_get_current_span.return_value = self.current_span + handler = LoguruHandler(logger_provider=self.custom_provider) + log_record = handler._translate(self.record) + self.assertEqual(log_record.trace_id, self.span_context.trace_id) + self.assertEqual(log_record.span_id, self.span_context.span_id) + self.assertEqual(log_record.trace_flags, self.span_context.trace_flags) + self.assertEqual(log_record.severity_number, _STD_TO_OTEL[self.record["level"].no]) + self.assertEqual(log_record.body, self.record["message"]) + +# @patch('opentelemetry._logs.Logger.emit') +# @patch('opentelemetry.trace.get_current_span') +# def test_sink(self, mock_get_current_span, mock_emit): +# mock_get_current_span.return_value = self.current_span +# handler = LoguruHandler() +# handler.sink(self.record) +# mock_emit.assert_called_once() + +# # Running the tests +# if __name__ == '__main__': +# unittest.main() + + diff --git a/out.txt b/out.txt new file mode 100644 index 0000000000..2411b8bb72 --- /dev/null +++ b/out.txt @@ -0,0 +1,223 @@ +test-instrumentation-logging: commands_pre[0]> pip install 'opentelemetry-api@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api' +Collecting opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api + Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-1knewf_o/opentelemetry-api_282d8d799fb24ab896e904b085f29109 + Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Getting requirements to build wheel: started + Getting requirements to build wheel: finished with status 'done' + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (1.2.14) +Requirement already satisfied: importlib-metadata<=7.1,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (6.11.0) +Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (1.16.0) +Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.1,>=6.0->opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (3.17.0) +test-instrumentation-logging: commands_pre[1]> pip install 'opentelemetry-semantic-conventions@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions' +Collecting opentelemetry-semantic-conventions@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions + Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-cvfubdbv/opentelemetry-semantic-conventions_7e56a1d44143473d9348c6b1f84e3046 + Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Getting requirements to build wheel: started + Getting requirements to build wheel: finished with status 'done' + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +test-instrumentation-logging: commands_pre[2]> pip install 'opentelemetry-sdk@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk' +Collecting opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk + Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-lk1c77a2/opentelemetry-sdk_9c8115670b894894a9c0ecb14b38aaf8 + Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Getting requirements to build wheel: started + Getting requirements to build wheel: finished with status 'done' + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +Requirement already satisfied: opentelemetry-api==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.25.0.dev0) +Requirement already satisfied: opentelemetry-semantic-conventions==0.46b0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (0.46b0.dev0) +Requirement already satisfied: typing-extensions>=3.7.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (4.9.0) +Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.2.14) +Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (6.11.0) +Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.16.0) +Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (3.17.0) +test-instrumentation-logging: commands_pre[3]> pip install 'opentelemetry-test-utils@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils' +Collecting opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils + Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-jjc0xm3t/opentelemetry-test-utils_b7d3f4b0c60a4857a3f5b4a8c2d32b34 + Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Getting requirements to build wheel: started + Getting requirements to build wheel: finished with status 'done' + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +Requirement already satisfied: asgiref~=3.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (3.7.2) +Requirement already satisfied: opentelemetry-api==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.25.0.dev0) +Requirement already satisfied: opentelemetry-sdk==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.25.0.dev0) +Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.2.14) +Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (6.11.0) +Requirement already satisfied: opentelemetry-semantic-conventions==0.46b0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (0.46b0.dev0) +Requirement already satisfied: typing-extensions>=3.7.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (4.9.0) +Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.16.0) +Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (3.17.0) +test-instrumentation-logging: commands_pre[4]> pip install /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation +Processing ./opentelemetry-instrumentation + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Getting requirements to build wheel: started + Getting requirements to build wheel: finished with status 'done' + Preparing metadata (pyproject.toml): started + Preparing metadata (pyproject.toml): finished with status 'done' +Requirement already satisfied: opentelemetry-api~=1.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (1.25.0.dev0) +Requirement already satisfied: setuptools>=16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (69.1.1) +Requirement already satisfied: wrapt<2.0.0,>=1.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (1.16.0) +Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (1.2.14) +Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (6.11.0) +Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (3.17.0) +Building wheels for collected packages: opentelemetry-instrumentation + Building wheel for opentelemetry-instrumentation (pyproject.toml): started + Building wheel for opentelemetry-instrumentation (pyproject.toml): finished with status 'done' + Created wheel for opentelemetry-instrumentation: filename=opentelemetry_instrumentation-0.45b0.dev0-py3-none-any.whl size=28475 sha256=9ba0c4bbb5acb27d366f629b255b37c78443be9e8cc7930d86aad9740e6103e7 + Stored in directory: /home/mcastilh/.cache/pip/wheels/53/1e/17/449f4361c03c07bdeee2403350a3749480b9e78fb831ca633a +Successfully built opentelemetry-instrumentation +Installing collected packages: opentelemetry-instrumentation + Attempting uninstall: opentelemetry-instrumentation + Found existing installation: opentelemetry-instrumentation 0.45b0.dev0 + Uninstalling opentelemetry-instrumentation-0.45b0.dev0: + Successfully uninstalled opentelemetry-instrumentation-0.45b0.dev0 +Successfully installed opentelemetry-instrumentation-0.45b0.dev0 +test-instrumentation-logging: commands_pre[5]> pip install -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt +Obtaining file:///mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Checking if build backend supports build_editable: started + Checking if build backend supports build_editable: finished with status 'done' + Getting requirements to build editable: started + Getting requirements to build editable: finished with status 'done' + Installing backend dependencies: started + Installing backend dependencies: finished with status 'done' + Preparing editable metadata (pyproject.toml): started + Preparing editable metadata (pyproject.toml): finished with status 'done' +Obtaining file:///mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 19)) + Installing build dependencies: started + Installing build dependencies: finished with status 'done' + Checking if build backend supports build_editable: started + Checking if build backend supports build_editable: finished with status 'done' + Getting requirements to build editable: started + Getting requirements to build editable: finished with status 'done' + Installing backend dependencies: started + Installing backend dependencies: finished with status 'done' + Preparing editable metadata (pyproject.toml): started + Preparing editable metadata (pyproject.toml): finished with status 'done' +Requirement already satisfied: asgiref==3.7.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 1)) (3.7.2) +Requirement already satisfied: attrs==23.2.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 2)) (23.2.0) +Requirement already satisfied: Deprecated==1.2.14 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 3)) (1.2.14) +Requirement already satisfied: importlib-metadata==6.11.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 4)) (6.11.0) +Requirement already satisfied: iniconfig==2.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 5)) (2.0.0) +Requirement already satisfied: packaging==23.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 6)) (23.2) +Requirement already satisfied: pluggy==1.4.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 7)) (1.4.0) +Requirement already satisfied: py==1.11.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 8)) (1.11.0) +Requirement already satisfied: py-cpuinfo==9.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 9)) (9.0.0) +Requirement already satisfied: pytest==7.1.3 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 10)) (7.1.3) +Requirement already satisfied: pytest-benchmark==4.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 11)) (4.0.0) +Requirement already satisfied: tomli==2.0.1 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 12)) (2.0.1) +Requirement already satisfied: typing_extensions==4.9.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 13)) (4.9.0) +Requirement already satisfied: wrapt==1.16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 14)) (1.16.0) +Requirement already satisfied: zipp==3.17.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 15)) (3.17.0) +Requirement already satisfied: structlog==24.1.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 16)) (24.1.0) +Requirement already satisfied: loguru==0.7.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 17)) (0.7.2) +Requirement already satisfied: opentelemetry-api~=1.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0->-r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) (1.25.0.dev0) +Requirement already satisfied: setuptools>=16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0->-r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) (69.1.1) +Building wheels for collected packages: opentelemetry-instrumentation, opentelemetry-instrumentation-logging + Building editable for opentelemetry-instrumentation (pyproject.toml): started + Building editable for opentelemetry-instrumentation (pyproject.toml): finished with status 'done' + Created wheel for opentelemetry-instrumentation: filename=opentelemetry_instrumentation-0.45b0.dev0-py3-none-any.whl size=7991 sha256=97b3a648568fd6f2e0e9f7b24eb07089b4abcb7e4fef063a4490ec8c7bc7bd79 + Stored in directory: /tmp/pip-ephem-wheel-cache-u7iyg0ou/wheels/53/1e/17/449f4361c03c07bdeee2403350a3749480b9e78fb831ca633a + Building editable for opentelemetry-instrumentation-logging (pyproject.toml): started + Building editable for opentelemetry-instrumentation-logging (pyproject.toml): finished with status 'done' + Created wheel for opentelemetry-instrumentation-logging: filename=opentelemetry_instrumentation_logging-0.45b0.dev0-py3-none-any.whl size=6424 sha256=19f0ee6f119b532ac641035008cfdd1d313741dce3d8f9923ea9d4440fdbd9e0 + Stored in directory: /tmp/pip-ephem-wheel-cache-u7iyg0ou/wheels/88/de/f3/36847e89208d714c671b6408e927acddfcb3b5f0c8ef00f1d7 +Successfully built opentelemetry-instrumentation opentelemetry-instrumentation-logging +Installing collected packages: opentelemetry-instrumentation, opentelemetry-instrumentation-logging + Attempting uninstall: opentelemetry-instrumentation + Found existing installation: opentelemetry-instrumentation 0.45b0.dev0 + Uninstalling opentelemetry-instrumentation-0.45b0.dev0: + Successfully uninstalled opentelemetry-instrumentation-0.45b0.dev0 + Attempting uninstall: opentelemetry-instrumentation-logging + Found existing installation: opentelemetry-instrumentation-logging 0.45b0.dev0 + Uninstalling opentelemetry-instrumentation-logging-0.45b0.dev0: + Successfully uninstalled opentelemetry-instrumentation-logging-0.45b0.dev0 +Successfully installed opentelemetry-instrumentation-0.45b0.dev0 opentelemetry-instrumentation-logging-0.45b0.dev0 +test-instrumentation-logging: commands[0]> pytest /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/tests +============================= test session starts ============================== +platform linux -- Python 3.10.12, pytest-7.1.3, pluggy-1.4.0 -- /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/.tox/test-instrumentation-logging/bin/python +cachedir: .tox/test-instrumentation-logging/.pytest_cache +benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) +rootdir: /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib, configfile: pytest.ini +plugins: benchmark-4.0.0 +collecting ... collected 17 items + +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentorProxyTracerProvider::test_trace_context_injection PASSED [ 5%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_basic_config_called PASSED [ 11%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_custom_format_and_level_api PASSED [ 17%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_custom_format_and_level_env PASSED [ 23%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_log_hook PASSED [ 29%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_trace_context_injection PASSED [ 35%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_trace_context_injection_without_span PASSED [ 41%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_uninstrumented +-------------------------------- live log call --------------------------------- +WARNING opentelemetry.instrumentation.instrumentor:instrumentor.py:132 Attempting to uninstrument while already uninstrumented +PASSED [ 47%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_call_method_processes_log_correctly PASSED [ 52%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_initialization PASSED [ 58%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_parse_exception PASSED [ 64%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_parse_timestamp PASSED [ 70%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_post_process_formats_timestamp PASSED [ 76%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_pre_process_adds_timestamp PASSED [ 82%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_attributes_extraction_without_exception FAILED [ 88%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_initialization_with_custom_provider PASSED [ 94%] +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_initialization_with_default_provider PASSED [100%] + +=================================== FAILURES =================================== +________ TestLoguruHandler.test_attributes_extraction_without_exception ________ + +self = + + def test_attributes_extraction_without_exception(self): + attrs = LoguruHandler._get_attributes(self.record) + expected_attrs = { + SpanAttributes.CODE_FILEPATH: 'test_file.py', + SpanAttributes.CODE_FUNCTION: 'test_function', + SpanAttributes.CODE_LINENO: 123 + } + print("Get attributes: ", attrs) + +> self.assertEqual(attrs, expected_attrs) +E AssertionError: {'time': 1581000000.000123, 'level': , +E - 'line': 123, +E - 'message': 'Test message', +E - 'time': 1581000000.000123} + +instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py:348: AssertionError +----------------------------- Captured stdout call ----------------------------- +Get attributes: {'time': 1581000000.000123, 'level': , 'message': 'Test message', 'file': 'test_file.py', 'function': 'test_function', 'line': 123, 'exception': None, 'code.filepath': 'test_file.py', 'code.function': 'test_function', 'code.lineno': 123} +=============================== warnings summary =============================== +opentelemetry-instrumentation/src/opentelemetry/instrumentation/dependencies.py:4 + /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation/src/opentelemetry/instrumentation/dependencies.py:4: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html + from pkg_resources import ( + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +=================== 1 failed, 16 passed, 1 warning in 17.81s =================== +test-instrumentation-logging: exit 1 (51.41 seconds) /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib> pytest /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/tests pid=209543 + test-instrumentation-logging: FAIL code 1 (534.73=setup[0.34]+cmd[60.77,59.86,52.61,56.45,72.26,181.03,51.41] seconds) + evaluation failed :( (534.91 seconds) From 2a84a0862a677cd7c97371fcd98bb3f0828a59c8 Mon Sep 17 00:00:00 2001 From: Miguel Castilho Date: Sun, 28 Apr 2024 14:33:54 -0400 Subject: [PATCH 12/25] Final test not passing for loguru --- .../tests/test_logging.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index aa7e337c15..150e4dc7a5 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -394,13 +394,13 @@ def test_translation(self, mock_get_current_span): self.assertEqual(log_record.severity_number, _STD_TO_OTEL[self.record["level"].no]) self.assertEqual(log_record.body, self.record["message"]) -# @patch('opentelemetry._logs.Logger.emit') -# @patch('opentelemetry.trace.get_current_span') -# def test_sink(self, mock_get_current_span, mock_emit): -# mock_get_current_span.return_value = self.current_span -# handler = LoguruHandler() -# handler.sink(self.record) -# mock_emit.assert_called_once() + @patch('opentelemetry._logs.Logger.emit') + @patch('opentelemetry.trace.get_current_span') + def test_sink(self, mock_get_current_span, mock_emit): + mock_get_current_span.return_value = self.current_span + handler = LoguruHandler(logger_provider=self.custom_provider) + handler.sink(self.record) + mock_emit.assert_called_once() # # Running the tests # if __name__ == '__main__': From 952c00377fa871bb08faa684cd9729866c412af1 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 28 Apr 2024 17:06:08 -0400 Subject: [PATCH 13/25] CG: loguru tests pass 100% --- .../tests/test_logging.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index 150e4dc7a5..99cf452abe 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -400,7 +400,8 @@ def test_sink(self, mock_get_current_span, mock_emit): mock_get_current_span.return_value = self.current_span handler = LoguruHandler(logger_provider=self.custom_provider) handler.sink(self.record) - mock_emit.assert_called_once() + #mock_emit.assert_called_once() + handler._logger.emit.assert_called_once() # # Running the tests # if __name__ == '__main__': From 170ba0203afcc2ce89ad6ca5abb5a256b240f618 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Sun, 28 Apr 2024 18:16:33 -0400 Subject: [PATCH 14/25] removed old import file --- .../tests/quickie.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py b/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py deleted file mode 100644 index 0043b79ee4..0000000000 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/quickie.py +++ /dev/null @@ -1,18 +0,0 @@ -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 - - From a097455030ec8f08169ae3715c1e114c91bf2960 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Wed, 1 May 2024 13:26:43 -0400 Subject: [PATCH 15/25] CG: added structlog example --- .../handlers/opentelemetry_loguru/README.md | 138 ++++++++++++++++ examples/handlers/opentelemetry_loguru/app.py | 46 ++++++ .../otel-collector-config.yaml | 19 +++ .../opentelemetry_structlog/README.md | 156 ++++++++++++++++++ .../handlers/opentelemetry_structlog/app.py | 47 ++++++ .../otel-collector-config.yaml | 19 +++ 6 files changed, 425 insertions(+) create mode 100644 examples/handlers/opentelemetry_loguru/README.md create mode 100644 examples/handlers/opentelemetry_loguru/app.py create mode 100644 examples/handlers/opentelemetry_loguru/otel-collector-config.yaml create mode 100644 examples/handlers/opentelemetry_structlog/README.md create mode 100644 examples/handlers/opentelemetry_structlog/app.py create mode 100644 examples/handlers/opentelemetry_structlog/otel-collector-config.yaml diff --git a/examples/handlers/opentelemetry_loguru/README.md b/examples/handlers/opentelemetry_loguru/README.md new file mode 100644 index 0000000000..ba6340b6a9 --- /dev/null +++ b/examples/handlers/opentelemetry_loguru/README.md @@ -0,0 +1,138 @@ +# OpenTelemetry Python `loguru` Handler Example with Docker +This is a demo for the custom loguru handler implemented for OpenTelemetry. Overall, this example runs a basic Flask application with Docker to demonstrate an example application that uses OpenTelemetry logging with Python's logging library loguru. This example is scalable to other software systems that require the use of the loguru library for logging. + +Note: This example is adapted from OpenTelemetry's [Getting Started Tutorial for Python](https://opentelemetry.io/docs/languages/python/getting-started/) guide and OpenTelemetry's [example for logs](https://github.com/open-telemetry/opentelemetry-python/blob/main/docs/examples/logs/README.rst) code. + +## Prerequisites +Python 3 + +## Installation +Prior to building the example application, set up the directory and virtual environment: +``` +mkdir otel-loguru-example +cd otel-loguru-example +python3 -m venv venv +source ./venv/bin/activate +``` + +After activating the virtual environment `venv`, install flask and loguru. +``` +pip install flask +pip install loguru +pip install opentelemetry-exporter-otlp +``` + +### Create and Launch HTTP Server +Now that the environment is set up, create an `app.py` flask application. This is a basic example that uses the loguru Python logging library for OpenTelemetry logging instead of the standard Python logging library. + +Notice the importance of the following imports for using the loguru handler: `import loguru` and `from handlers.opentelemetry_loguru.src.exporter import LoguruHandler`. + +``` +from random import randint +from flask import Flask, request +from loguru import logger as loguru_logger +import sys +sys.path.insert(0, '../../..') +from handlers.opentelemetry_loguru.src.exporter import LoguruHandler + +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk.resources import Resource + + + +logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": "shoppingcart", + "service.instance.id": "instance-12", + } + ), +) +set_logger_provider(logger_provider) + +# Replace the standard logging configuration with Loguru +loguru_handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) +loguru_logger.add(loguru_handler.sink) # Add LoguruHandler to the logger + +app = Flask(__name__) + +@app.route("/rolldice") +def roll_dice(): + player = request.args.get('player', default=None, type=str) + result = str(roll()) + if player: + loguru_logger.warning("Player is rolling the dice: num") + else: + loguru_logger.warning("Anonymous player is rolling the dice: num") + return result + + +def roll(): + return randint(1, 6) + +``` + +Run the application on port 8080 with the following flask command and open [http://localhost:8080/rolldice](http://localhost:8080/rolldice) in your web browser to ensure it is working. + +``` +flask run -p 8080 +``` + +However, do not be alarmed if you receive these errors since Docker is not yet set up to export the logs: +``` +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 1s. +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 2s. +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 4s. +... +``` + +## Run with Docker + +To serve the application on Docker, first create the `otel-collector-config.yaml` file locally in the application's repository. +``` +# otel-collector-config.yaml +receivers: + otlp: + protocols: + grpc: + +processors: + batch: + +exporters: + logging: + verbosity: detailed + +service: + pipelines: + logs: + receivers: [otlp] + processors: [batch] + exporters: [logging] +``` + +Next, start the Docker container: +``` +docker run \ + -p 4317:4317 \ + -v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \ + otel/opentelemetry-collector-contrib:latest +``` + +And lastly, run the basic application with flask: +``` +flask run -p 8080 +``` + +Here is some example output: +``` + +``` + + +## Contributors +Caroline Gilbert: [carolincgilbert](https://github.com/carolinecgilbert) diff --git a/examples/handlers/opentelemetry_loguru/app.py b/examples/handlers/opentelemetry_loguru/app.py new file mode 100644 index 0000000000..7f54ba6d9e --- /dev/null +++ b/examples/handlers/opentelemetry_loguru/app.py @@ -0,0 +1,46 @@ +from random import randint +from flask import Flask, request +from loguru import logger as loguru_logger +import sys +sys.path.insert(0, '../../..') +from handlers.opentelemetry_loguru.src.exporter import LoguruHandler + +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk.resources import Resource + + + +logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": "shoppingcart", + "service.instance.id": "instance-12", + } + ), +) +set_logger_provider(logger_provider) + +# Replace the standard logging configuration with Loguru +loguru_handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) +loguru_logger.add(loguru_handler.sink) # Add LoguruHandler to the logger + +app = Flask(__name__) + +@app.route("/rolldice") +def roll_dice(): + player = request.args.get('player', default=None, type=str) + result = str(roll()) + if player: + loguru_logger.warning("Player is rolling the dice: num") + else: + loguru_logger.warning("Anonymous player is rolling the dice: num") + return result + + +def roll(): + return randint(1, 6) + diff --git a/examples/handlers/opentelemetry_loguru/otel-collector-config.yaml b/examples/handlers/opentelemetry_loguru/otel-collector-config.yaml new file mode 100644 index 0000000000..5525cdd849 --- /dev/null +++ b/examples/handlers/opentelemetry_loguru/otel-collector-config.yaml @@ -0,0 +1,19 @@ +# otel-collector-config.yaml +receivers: + otlp: + protocols: + grpc: + +processors: + batch: + +exporters: + logging: + verbosity: detailed + +service: + pipelines: + logs: + receivers: [otlp] + processors: [batch] + exporters: [logging] \ No newline at end of file diff --git a/examples/handlers/opentelemetry_structlog/README.md b/examples/handlers/opentelemetry_structlog/README.md new file mode 100644 index 0000000000..214fd32210 --- /dev/null +++ b/examples/handlers/opentelemetry_structlog/README.md @@ -0,0 +1,156 @@ +# OpenTelemetry Python `structlog` Handler Example with Docker +This is a demo for the custom structlog handler implemented for OpenTelemetry. Overall, this example runs a basic Flask application with Docker to demonstrate an example application that uses OpenTelemetry logging with Python's logging library structlog. This example is scalable to other software systems that require the use of the structlog library for logging. + +Note: This example is adapted from OpenTelemetry's [Getting Started Tutorial for Python](https://opentelemetry.io/docs/languages/python/getting-started/) guide and OpenTelemetry's [example for logs](https://github.com/open-telemetry/opentelemetry-python/blob/main/docs/examples/logs/README.rst) code. + +## Prerequisites +Python 3 + +## Installation +Prior to building the example application, set up the directory and virtual environment: +``` +mkdir otel-structlog-example +cd otel-structlog-example +python3 -m venv venv +source ./venv/bin/activate +``` + +After activating the virtual environment `venv`, install flask and structlog. +``` +pip install flask +pip install structlog +pip install opentelemetry-exporter-otlp +``` + +### Create and Launch HTTP Server +Now that the environment is set up, create an `app.py` flask application. This is a basic example that uses the structlog Python logging library for OpenTelemetry logging instead of the standard Python logging library. + +Notice the importance of the following imports for using the structlog handler: `import structlog` and `from handlers.opentelemetry_structlog.src.exporter import StructlogHandler`. + +``` +from random import randint +from flask import Flask, request +import structlog +import sys +sys.path.insert(0, '../../..') +from handlers.opentelemetry_structlog.src.exporter import StructlogHandler +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk.resources import Resource +from opentelemetry._logs import set_logger_provider +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.resources import Resource + +logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": "shoppingcart", + "service.instance.id": "instance-12", + } + ), +) +set_logger_provider(logger_provider) + +# Replace the standard logging configuration with Loguru +structlog_handler = StructlogHandler(service_name="flask-structlog-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) +structlog_handler._logger_provider = logger_provider +structlog_logger = structlog.wrap_logger(structlog.get_logger(), processors=[structlog_handler]) # Add StructlogHandler to the logger + +app = Flask(__name__) + +@app.route("/rolldice") +def roll_dice(): + player = request.args.get('player', default=None, type=str) + result = str(roll()) + if player: + structlog_logger.warning("Player %s is rolling the dice: %s", player, result, level="warning") + else: + structlog_logger.warning("Anonymous player is rolling the dice: %s", result, level="warning") + return result + + +def roll(): + return randint(1, 6) +``` + +Run the application on port 8080 with the following flask command and open [http://localhost:8080/rolldice](http://localhost:8080/rolldice) in your web browser to ensure it is working. + +``` +flask run -p 8080 +``` + +However, do not be alarmed if you receive these errors since Docker is not yet set up to export the logs: +``` +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 1s. +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 2s. +Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 4s. +... +``` + +## Run with Docker + +To serve the application on Docker, first create the `otel-collector-config.yaml` file locally in the application's repository. +``` +# otel-collector-config.yaml +receivers: + otlp: + protocols: + grpc: + +processors: + batch: + +exporters: + logging: + verbosity: detailed + +service: + pipelines: + logs: + receivers: [otlp] + processors: [batch] + exporters: [logging] +``` + +Next, start the Docker container: +``` +docker run \ + -p 4317:4317 \ + -v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \ + otel/opentelemetry-collector-contrib:latest +``` + +And lastly, run the basic application with flask: +``` +flask run -p 8080 +``` + +Here is some example output: +``` + * Debug mode: off +WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. + * Running on http://127.0.0.1:8080 +Press CTRL+C to quit +2024-04-28 23:15:22 [warning ] Anonymous player is rolling the dice: 1 +127.0.0.1 - - [28/Apr/2024 23:15:22] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:27 [warning ] Anonymous player is rolling the dice: 6 +127.0.0.1 - - [28/Apr/2024 23:15:27] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:28 [warning ] Anonymous player is rolling the dice: 3 +127.0.0.1 - - [28/Apr/2024 23:15:28] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:29 [warning ] Anonymous player is rolling the dice: 4 +127.0.0.1 - - [28/Apr/2024 23:15:29] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:29 [warning ] Anonymous player is rolling the dice: 1 +127.0.0.1 - - [28/Apr/2024 23:15:29] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:30 [warning ] Anonymous player is rolling the dice: 2 +127.0.0.1 - - [28/Apr/2024 23:15:30] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:15:31 [warning ] Anonymous player is rolling the dice: 3 +127.0.0.1 - - [28/Apr/2024 23:15:31] "GET /rolldice HTTP/1.1" 200 - +2024-04-28 23:16:14 [warning ] Anonymous player is rolling the dice: 4 +127.0.0.1 - - [28/Apr/2024 23:16:14] "GET /rolldice HTTP/1.1" 200 - +``` + + +## Contributors +Caroline Gilbert: [carolincgilbert](https://github.com/carolinecgilbert) diff --git a/examples/handlers/opentelemetry_structlog/app.py b/examples/handlers/opentelemetry_structlog/app.py new file mode 100644 index 0000000000..82745b48bc --- /dev/null +++ b/examples/handlers/opentelemetry_structlog/app.py @@ -0,0 +1,47 @@ +from random import randint +from flask import Flask, request +import structlog +import sys +sys.path.insert(0, '../../..') +from handlers.opentelemetry_structlog.src.exporter import StructlogHandler +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.sdk._logs import LoggerProvider +from opentelemetry.sdk.resources import Resource +from opentelemetry._logs import set_logger_provider +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.resources import Resource + +logger_provider = LoggerProvider( + resource=Resource.create( + { + "service.name": "shoppingcart", + "service.instance.id": "instance-12", + } + ), +) +set_logger_provider(logger_provider) + +# Replace the standard logging configuration with Loguru +structlog_handler = StructlogHandler(service_name="flask-structlog-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) +structlog_handler._logger_provider = logger_provider +structlog_logger = structlog.wrap_logger(structlog.get_logger(), processors=[structlog_handler]) # Add StructlogHandler to the logger + + + +app = Flask(__name__) + +@app.route("/rolldice") +def roll_dice(): + player = request.args.get('player', default=None, type=str) + result = str(roll()) + if player: + structlog_logger.warning("Player %s is rolling the dice: %s", player, result, level="warning") + else: + structlog_logger.warning("Anonymous player is rolling the dice: %s", result, level="warning") + return result + + +def roll(): + return randint(1, 6) diff --git a/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml b/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml new file mode 100644 index 0000000000..5525cdd849 --- /dev/null +++ b/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml @@ -0,0 +1,19 @@ +# otel-collector-config.yaml +receivers: + otlp: + protocols: + grpc: + +processors: + batch: + +exporters: + logging: + verbosity: detailed + +service: + pipelines: + logs: + receivers: [otlp] + processors: [batch] + exporters: [logging] \ No newline at end of file From e08356566435b7ee0014b57a45ad5ce9bf7bc9c4 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Fri, 3 May 2024 00:56:19 -0400 Subject: [PATCH 16/25] CG: loguru finalized with documentation --- handlers/opentelemetry_loguru/src/README.md | 53 ++++++++++++ handlers/opentelemetry_loguru/src/exporter.py | 42 +++++++--- .../opentelemetry_structlog/src/README.md | 60 ++++++++++++++ .../test-requirements.txt | 1 + .../tests/test_logging.py | 82 ++++++++++++------- 5 files changed, 199 insertions(+), 39 deletions(-) create mode 100644 handlers/opentelemetry_loguru/src/README.md create mode 100644 handlers/opentelemetry_structlog/src/README.md diff --git a/handlers/opentelemetry_loguru/src/README.md b/handlers/opentelemetry_loguru/src/README.md new file mode 100644 index 0000000000..e06194f51a --- /dev/null +++ b/handlers/opentelemetry_loguru/src/README.md @@ -0,0 +1,53 @@ +# Loguru Handler for OpenTelemetry + +This project provides a Loguru handler for OpenTelemetry applications. The handler converts Loguru logs into the OpenTelemetry Logs Protocol (OTLP) format for export to a collector. + +## Usage + +To use the Loguru handler in your OpenTelemetry application, follow these steps: + +1. Import the necessary modules: + +```python +import loguru +from handlers.opentelemetry_loguru.src.exporter import LoguruHandler +from opentelemetry.sdk._logs._internal.export import LogExporter +from opentelemetry.sdk.resources import Resource +``` + +2. Initialize the LoguruHandler with your service name, server hostname, and LogExporter instance: + +```python +service_name = "my_service" +server_hostname = "my_server" +exporter = LogExporter() # Initialize your LogExporter instance +handler = LoguruHandler(service_name, server_hostname, exporter) +``` + +3. Add the handler to your Loguru logger: + +```python +logger = loguru.logger +logger.add(handler.sink) +``` + +4. Use the logger as usual with Loguru: + +```python +logger.warning("This is a test log message.") +``` +## OpenTelemetry Application Example with Handler +See the loguru handler demo in the examples directory of this repository for a step-by-step guide on using the handler in an OpenTelemetry application. + +## Customization + +The LoguruHandler supports customization through its constructor parameters: + +- `service_name`: The name of your service. +- `server_hostname`: The hostname of the server where the logs originate. +- `exporter`: An instance of your LogExporter for exporting logs to a collector. + +## Notes + +- This handler automatically converts Loguru logs into the OTLP format for compatibility with OpenTelemetry. +- It extracts attributes from Loguru logs and maps them to OpenTelemetry attributes for log records. diff --git a/handlers/opentelemetry_loguru/src/exporter.py b/handlers/opentelemetry_loguru/src/exporter.py index 92014ce65f..d18eea28b3 100644 --- a/handlers/opentelemetry_loguru/src/exporter.py +++ b/handlers/opentelemetry_loguru/src/exporter.py @@ -94,7 +94,7 @@ 53: SeverityNumber.FATAL4, } - +EXCLUDE_ATTR = ("elapsed", "exception", "extra", "file", "level", "process", "thread", "time") class LoguruHandler: # this was largely inspired by the OpenTelemetry handler for stdlib `logging`: @@ -102,22 +102,40 @@ class LoguruHandler: def __init__( self, - logger_provider=None, + service_name: str, + server_hostname: str, + exporter: LogExporter, ) -> None: - - self._logger_provider = logger_provider or get_logger_provider() - self._logger = get_logger( - __name__, logger_provider=self._logger_provider + 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 _get_attributes(self, record) -> Attributes: - attributes = {key:value for key, value in record.items()} + attributes = {key:value for key, value in record.items() if key not in EXCLUDE_ATTR} # Add standard code attributes for logs. - attributes[SpanAttributes.CODE_FILEPATH] = record['file'] #This includes file and path -> (file, path) + attributes[SpanAttributes.CODE_FILEPATH] = record['file'].path #This includes file and path -> (file, path) attributes[SpanAttributes.CODE_FUNCTION] = record['function'] attributes[SpanAttributes.CODE_LINENO] = record['line'] + + attributes['process_name'] = (record['process']).name + attributes['process_id'] = (record['process']).id + attributes['thread_name'] = (record['thread']).name + attributes['thread_id'] = (record['thread']).id + attributes['file'] = record['file'].name if record['exception'] is not None: @@ -138,11 +156,13 @@ def _loguru_to_otel(self, levelno: int) -> SeverityNumber: return _STD_TO_OTEL[levelno] + + def _translate(self, record) -> LogRecord: #Timestamp - timestamp = record["time"] + timestamp = int((record["time"].timestamp()) * 1e9) #Observed timestamp observedTimestamp = time_ns() @@ -179,6 +199,6 @@ def _translate(self, record) -> LogRecord: ) def sink(self, record) -> None: - - self._logger.emit(self._translate(record)) + print("\n BALLIN HERE\n") + self._logger.emit(self._translate(record.record)) \ No newline at end of file diff --git a/handlers/opentelemetry_structlog/src/README.md b/handlers/opentelemetry_structlog/src/README.md new file mode 100644 index 0000000000..a61fef5e81 --- /dev/null +++ b/handlers/opentelemetry_structlog/src/README.md @@ -0,0 +1,60 @@ +# Structlog Handler for OpenTelemetry +This project provides a Structlog handler for OpenTelemetry applications. The handler converts Structlog logs into the OpenTelemetry Logs Protocol (OTLP) format for export to a collector. + +## Usage + +To use the Structlog handler in your OpenTelemetry application, follow these steps: + +1. Import the necessary modules: + +```python +import structlog +from opentelemetry.sdk._logs._internal.export import LogExporter +from opentelemetry.sdk.resources import Resource +from handlers.opentelemetry_structlog.src.exporter import StructlogHandler +``` + +2. Initialize the StructlogHandler with your service name, server hostname, and LogExporter instance: + +```python +service_name = "my_service" +server_hostname = "my_server" +exporter = LogExporter() # Initialize your LogExporter instance +handler = StructlogHandler(service_name, server_hostname, exporter) +``` + +3. Add the handler to your Structlog logger: + +```python +structlog.configure( + processors=[structlog.processors.JSONRenderer()], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + cache_logger_on_first_use=True, + context_class=dict, + **handler.wrap_for_structlog(), +) +``` + +4. Use the logger as usual with Structlog: + +```python +logger = structlog.get_logger() +logger.info("This is a test log message.") +``` +## OpenTelemetry Application Example with Handler +See the structlog handler demo in the examples directory of this repository for a step-by-step guide on using the handler in an OpenTelemetry application. + +## Customization + +The StructlogHandler supports customization through its constructor parameters: + +- `service_name`: The name of your service. +- `server_hostname`: The hostname of the server where the logs originate. +- `exporter`: An instance of your LogExporter for exporting logs to a collector. + +## Notes + +- This handler automatically converts the `timestamp` key in the `event_dict` to ISO 8601 format for better compatibility. +- It performs operations similar to `structlog.processors.ExceptionRenderer`, so avoid using `ExceptionRenderer` in the same pipeline. +``` diff --git a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt index 1f015a3373..ad7b238d7c 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt +++ b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt @@ -15,5 +15,6 @@ wrapt==1.16.0 zipp==3.17.0 structlog==24.1.0 loguru==0.7.2 +opentelemetry-exporter-otlp==1.24.0 -e opentelemetry-instrumentation -e instrumentation/opentelemetry-instrumentation-logging diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index cfcf581945..0f5c6f23ec 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -27,7 +27,9 @@ from datetime import datetime, timezone from unittest.mock import MagicMock, patch - +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) from opentelemetry.semconv.trace import SpanAttributes @@ -389,41 +391,58 @@ def test_trace_context_propogation(self): assert log_record.trace_flags == trace_sampled, "Trace flags should be propagated" - +class TimestampRecord: + def __init__(self, data): + self.timestam = data + def timestamp(self): + return self.timestam class TestLoguruHandler(TestBase): def setUp(self): self.default_provider = get_logger_provider() self.custom_provider = MagicMock() + + RecordFile = namedtuple('RecordFile', ['path', 'name']) + file_record = RecordFile( + path="test_file.py", + name = "test_file.py" + ) + + RecordProcess = namedtuple('RecordProcess', ['name', 'id']) + process_record = RecordProcess( + name = "MainProcess", + id = 1 + ) + + RecordThread = namedtuple('RecordThread', ['name', 'id']) + thread_record = RecordThread( + name = "MainThread", + id = 1 + ) + + timeRec = TimestampRecord(data=2.38763786) + self.record = { - "time": 1581000000.000123, + "time": timeRec, "level": MagicMock(name="ERROR", no=40), "message": "Test message", - "file": "test_file.py", + "file": file_record, + "process": process_record, + "thread": thread_record, "function": "test_function", "line": 123, "exception": None } - # self.span_context = SpanContext( - # trace_id=1234, - # span_id=5678, - # trace_flags=TraceFlags(1), - # is_remote=False - # ) + self.span_context = get_current_span().get_span_context() self.current_span = MagicMock() - self.current_span.get_span_context.return_value = self.span_context + self.current_span.get_span_context.return_value = self.span_context - def test_initialization_with_default_provider(self): - handler = LoguruHandler() - self.assertEqual(handler._logger_provider, self.default_provider) + def test_attributes_extraction_without_exception(self): + handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) - def test_initialization_with_custom_provider(self): - handler = LoguruHandler(logger_provider=self.custom_provider) - self.assertEqual(handler._logger_provider, self.custom_provider) + attrs = handler._get_attributes(self.record) - def test_attributes_extraction_without_exception(self): - attrs = LoguruHandler()._get_attributes(self.record) expected_attrs = { SpanAttributes.CODE_FILEPATH: 'test_file.py', SpanAttributes.CODE_FUNCTION: 'test_function', @@ -448,11 +467,11 @@ def test_attributes_extraction_with_exception(self, mock_format_exception): traceback=mock_format_exception(exception) ) self.record['exception'] = exception_record - # self.record['exception'].type = type(exception).__name__ - # self.record['exception'].value = str(exception) - # self.record['exception'].traceback = mock_format_exception(exception) + - attrs = LoguruHandler()._get_attributes(self.record) + handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) + + attrs = handler._get_attributes(self.record) expected_attrs = { SpanAttributes.CODE_FILEPATH: 'test_file.py', @@ -470,7 +489,9 @@ def test_attributes_extraction_with_exception(self, mock_format_exception): @patch('opentelemetry.trace.get_current_span') def test_translation(self, mock_get_current_span): mock_get_current_span.return_value = self.current_span - handler = LoguruHandler(logger_provider=self.custom_provider) + + handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) + log_record = handler._translate(self.record) self.assertEqual(log_record.trace_id, self.span_context.trace_id) self.assertEqual(log_record.span_id, self.span_context.span_id) @@ -482,9 +503,14 @@ def test_translation(self, mock_get_current_span): @patch('opentelemetry.trace.get_current_span') def test_sink(self, mock_get_current_span, mock_emit): mock_get_current_span.return_value = self.current_span - handler = LoguruHandler(logger_provider=self.custom_provider) - handler.sink(self.record) - #mock_emit.assert_called_once() - handler._logger.emit.assert_called_once() + + handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) + + MessageRecord = namedtuple('MessageRecord', ['record']) + message = MessageRecord( + record=self.record + ) + + handler.sink(message) From 4561989ba47824f2b5374c0b79de9c4459f1f581 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Fri, 3 May 2024 01:22:11 -0400 Subject: [PATCH 17/25] removed print statement --- handlers/opentelemetry_loguru/src/exporter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/handlers/opentelemetry_loguru/src/exporter.py b/handlers/opentelemetry_loguru/src/exporter.py index d18eea28b3..4b3e475ab5 100644 --- a/handlers/opentelemetry_loguru/src/exporter.py +++ b/handlers/opentelemetry_loguru/src/exporter.py @@ -199,6 +199,5 @@ def _translate(self, record) -> LogRecord: ) def sink(self, record) -> None: - print("\n BALLIN HERE\n") self._logger.emit(self._translate(record.record)) \ No newline at end of file From 37ef8f784bcc593465b514133b0dadc916d53c24 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Fri, 3 May 2024 01:23:37 -0400 Subject: [PATCH 18/25] updated app --- examples/handlers/opentelemetry_loguru/app.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/handlers/opentelemetry_loguru/app.py b/examples/handlers/opentelemetry_loguru/app.py index 7f54ba6d9e..bff776c5fe 100644 --- a/examples/handlers/opentelemetry_loguru/app.py +++ b/examples/handlers/opentelemetry_loguru/app.py @@ -14,20 +14,12 @@ -logger_provider = LoggerProvider( - resource=Resource.create( - { - "service.name": "shoppingcart", - "service.instance.id": "instance-12", - } - ), -) -set_logger_provider(logger_provider) # Replace the standard logging configuration with Loguru loguru_handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) loguru_logger.add(loguru_handler.sink) # Add LoguruHandler to the logger + app = Flask(__name__) @app.route("/rolldice") @@ -43,4 +35,3 @@ def roll_dice(): def roll(): return randint(1, 6) - From 27bf6d93b4e478e98bc49f08014d38150acfa7f6 Mon Sep 17 00:00:00 2001 From: Caroline Gilbert Date: Fri, 3 May 2024 01:48:15 -0400 Subject: [PATCH 19/25] updated loguru app --- examples/handlers/opentelemetry_loguru/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/handlers/opentelemetry_loguru/app.py b/examples/handlers/opentelemetry_loguru/app.py index bff776c5fe..2a23dab402 100644 --- a/examples/handlers/opentelemetry_loguru/app.py +++ b/examples/handlers/opentelemetry_loguru/app.py @@ -27,9 +27,9 @@ def roll_dice(): player = request.args.get('player', default=None, type=str) result = str(roll()) if player: - loguru_logger.warning("Player is rolling the dice: num") + loguru_logger.warning(f"Player {player} is rolling the dice: {result}") else: - loguru_logger.warning("Anonymous player is rolling the dice: num") + loguru_logger.warning(f"Anonymous player is rolling the dice: {result}") return result From 32881cefe476f532b8ba77a18e0cda3f19898339 Mon Sep 17 00:00:00 2001 From: Miguel Castilho <78663730+MigCast9@users.noreply.github.com> Date: Fri, 3 May 2024 01:59:25 -0400 Subject: [PATCH 20/25] Update README.md Updated tox documentation --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c315b4a876..446b0c7623 100644 --- a/README.md +++ b/README.md @@ -138,8 +138,9 @@ Emeritus Maintainers: 1. Go to your Contrib repo directory. `cd ~/git/opentelemetry-python-contrib`. 2. Create a virtual env in your Contrib repo directory. `python3 -m venv my_test_venv`. 3. Activate your virtual env. `source my_test_venv/bin/activate`. -4. Make sure you have `tox` installed. `pip install tox`. -5. Run tests for a package. (e.g. `tox -e test-instrumentation-flask`.) +4. Make sure you have `tox` installed. `pip install tox==4.13.0`. +5. Change Python version to 3.11 +6. Run tests for a package. (e.g. `tox -e test-instrumentation-flask`.) ### Thanks to all the people who already contributed From 1c92c51815aa3acfe31850ea055712bfbfc1f92c Mon Sep 17 00:00:00 2001 From: Miguel Castilho <78663730+MigCast9@users.noreply.github.com> Date: Fri, 3 May 2024 02:02:12 -0400 Subject: [PATCH 21/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 446b0c7623..97c41298f6 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Emeritus Maintainers: 4. Make sure you have `tox` installed. `pip install tox==4.13.0`. 5. Change Python version to 3.11 6. Run tests for a package. (e.g. `tox -e test-instrumentation-flask`.) - +7. To install new packages when running Tox, add the package with the desired version to the test-requirements.txt file associated with the instrumentation being tested. ### Thanks to all the people who already contributed From 1606722d6a9321d44632817cb80553db72fd9b12 Mon Sep 17 00:00:00 2001 From: Zane Dufour Date: Tue, 15 Oct 2024 15:35:27 -0400 Subject: [PATCH 22/25] remove structlog --- .../opentelemetry_structlog/README.md | 156 ----------------- .../handlers/opentelemetry_structlog/app.py | 47 ----- .../otel-collector-config.yaml | 19 --- handlers/opentelemetry_structlog/README.md | 1 - handlers/opentelemetry_structlog/__init__.py | 0 .../opentelemetry_structlog/pyproject.toml | 45 ----- .../opentelemetry_structlog/src/README.md | 60 ------- .../opentelemetry_structlog/src/__init__.py | 0 .../opentelemetry_structlog/src/exporter.py | 161 ------------------ .../opentelemetry_structlog/src/version.py | 1 - 10 files changed, 490 deletions(-) delete mode 100644 examples/handlers/opentelemetry_structlog/README.md delete mode 100644 examples/handlers/opentelemetry_structlog/app.py delete mode 100644 examples/handlers/opentelemetry_structlog/otel-collector-config.yaml delete mode 100644 handlers/opentelemetry_structlog/README.md delete mode 100644 handlers/opentelemetry_structlog/__init__.py delete mode 100644 handlers/opentelemetry_structlog/pyproject.toml delete mode 100644 handlers/opentelemetry_structlog/src/README.md delete mode 100644 handlers/opentelemetry_structlog/src/__init__.py delete mode 100644 handlers/opentelemetry_structlog/src/exporter.py delete mode 100644 handlers/opentelemetry_structlog/src/version.py diff --git a/examples/handlers/opentelemetry_structlog/README.md b/examples/handlers/opentelemetry_structlog/README.md deleted file mode 100644 index 214fd32210..0000000000 --- a/examples/handlers/opentelemetry_structlog/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# OpenTelemetry Python `structlog` Handler Example with Docker -This is a demo for the custom structlog handler implemented for OpenTelemetry. Overall, this example runs a basic Flask application with Docker to demonstrate an example application that uses OpenTelemetry logging with Python's logging library structlog. This example is scalable to other software systems that require the use of the structlog library for logging. - -Note: This example is adapted from OpenTelemetry's [Getting Started Tutorial for Python](https://opentelemetry.io/docs/languages/python/getting-started/) guide and OpenTelemetry's [example for logs](https://github.com/open-telemetry/opentelemetry-python/blob/main/docs/examples/logs/README.rst) code. - -## Prerequisites -Python 3 - -## Installation -Prior to building the example application, set up the directory and virtual environment: -``` -mkdir otel-structlog-example -cd otel-structlog-example -python3 -m venv venv -source ./venv/bin/activate -``` - -After activating the virtual environment `venv`, install flask and structlog. -``` -pip install flask -pip install structlog -pip install opentelemetry-exporter-otlp -``` - -### Create and Launch HTTP Server -Now that the environment is set up, create an `app.py` flask application. This is a basic example that uses the structlog Python logging library for OpenTelemetry logging instead of the standard Python logging library. - -Notice the importance of the following imports for using the structlog handler: `import structlog` and `from handlers.opentelemetry_structlog.src.exporter import StructlogHandler`. - -``` -from random import randint -from flask import Flask, request -import structlog -import sys -sys.path.insert(0, '../../..') -from handlers.opentelemetry_structlog.src.exporter import StructlogHandler -from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( - OTLPLogExporter, -) -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk.resources import Resource -from opentelemetry._logs import set_logger_provider -from opentelemetry.sdk._logs.export import BatchLogRecordProcessor -from opentelemetry.sdk.resources import Resource - -logger_provider = LoggerProvider( - resource=Resource.create( - { - "service.name": "shoppingcart", - "service.instance.id": "instance-12", - } - ), -) -set_logger_provider(logger_provider) - -# Replace the standard logging configuration with Loguru -structlog_handler = StructlogHandler(service_name="flask-structlog-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) -structlog_handler._logger_provider = logger_provider -structlog_logger = structlog.wrap_logger(structlog.get_logger(), processors=[structlog_handler]) # Add StructlogHandler to the logger - -app = Flask(__name__) - -@app.route("/rolldice") -def roll_dice(): - player = request.args.get('player', default=None, type=str) - result = str(roll()) - if player: - structlog_logger.warning("Player %s is rolling the dice: %s", player, result, level="warning") - else: - structlog_logger.warning("Anonymous player is rolling the dice: %s", result, level="warning") - return result - - -def roll(): - return randint(1, 6) -``` - -Run the application on port 8080 with the following flask command and open [http://localhost:8080/rolldice](http://localhost:8080/rolldice) in your web browser to ensure it is working. - -``` -flask run -p 8080 -``` - -However, do not be alarmed if you receive these errors since Docker is not yet set up to export the logs: -``` -Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 1s. -Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 2s. -Transient error StatusCode.UNAVAILABLE encountered while exporting logs to localhost:4317, retrying in 4s. -... -``` - -## Run with Docker - -To serve the application on Docker, first create the `otel-collector-config.yaml` file locally in the application's repository. -``` -# otel-collector-config.yaml -receivers: - otlp: - protocols: - grpc: - -processors: - batch: - -exporters: - logging: - verbosity: detailed - -service: - pipelines: - logs: - receivers: [otlp] - processors: [batch] - exporters: [logging] -``` - -Next, start the Docker container: -``` -docker run \ - -p 4317:4317 \ - -v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \ - otel/opentelemetry-collector-contrib:latest -``` - -And lastly, run the basic application with flask: -``` -flask run -p 8080 -``` - -Here is some example output: -``` - * Debug mode: off -WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. - * Running on http://127.0.0.1:8080 -Press CTRL+C to quit -2024-04-28 23:15:22 [warning ] Anonymous player is rolling the dice: 1 -127.0.0.1 - - [28/Apr/2024 23:15:22] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:27 [warning ] Anonymous player is rolling the dice: 6 -127.0.0.1 - - [28/Apr/2024 23:15:27] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:28 [warning ] Anonymous player is rolling the dice: 3 -127.0.0.1 - - [28/Apr/2024 23:15:28] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:29 [warning ] Anonymous player is rolling the dice: 4 -127.0.0.1 - - [28/Apr/2024 23:15:29] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:29 [warning ] Anonymous player is rolling the dice: 1 -127.0.0.1 - - [28/Apr/2024 23:15:29] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:30 [warning ] Anonymous player is rolling the dice: 2 -127.0.0.1 - - [28/Apr/2024 23:15:30] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:15:31 [warning ] Anonymous player is rolling the dice: 3 -127.0.0.1 - - [28/Apr/2024 23:15:31] "GET /rolldice HTTP/1.1" 200 - -2024-04-28 23:16:14 [warning ] Anonymous player is rolling the dice: 4 -127.0.0.1 - - [28/Apr/2024 23:16:14] "GET /rolldice HTTP/1.1" 200 - -``` - - -## Contributors -Caroline Gilbert: [carolincgilbert](https://github.com/carolinecgilbert) diff --git a/examples/handlers/opentelemetry_structlog/app.py b/examples/handlers/opentelemetry_structlog/app.py deleted file mode 100644 index 82745b48bc..0000000000 --- a/examples/handlers/opentelemetry_structlog/app.py +++ /dev/null @@ -1,47 +0,0 @@ -from random import randint -from flask import Flask, request -import structlog -import sys -sys.path.insert(0, '../../..') -from handlers.opentelemetry_structlog.src.exporter import StructlogHandler -from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( - OTLPLogExporter, -) -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk.resources import Resource -from opentelemetry._logs import set_logger_provider -from opentelemetry.sdk._logs.export import BatchLogRecordProcessor -from opentelemetry.sdk.resources import Resource - -logger_provider = LoggerProvider( - resource=Resource.create( - { - "service.name": "shoppingcart", - "service.instance.id": "instance-12", - } - ), -) -set_logger_provider(logger_provider) - -# Replace the standard logging configuration with Loguru -structlog_handler = StructlogHandler(service_name="flask-structlog-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) -structlog_handler._logger_provider = logger_provider -structlog_logger = structlog.wrap_logger(structlog.get_logger(), processors=[structlog_handler]) # Add StructlogHandler to the logger - - - -app = Flask(__name__) - -@app.route("/rolldice") -def roll_dice(): - player = request.args.get('player', default=None, type=str) - result = str(roll()) - if player: - structlog_logger.warning("Player %s is rolling the dice: %s", player, result, level="warning") - else: - structlog_logger.warning("Anonymous player is rolling the dice: %s", result, level="warning") - return result - - -def roll(): - return randint(1, 6) diff --git a/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml b/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml deleted file mode 100644 index 5525cdd849..0000000000 --- a/examples/handlers/opentelemetry_structlog/otel-collector-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# otel-collector-config.yaml -receivers: - otlp: - protocols: - grpc: - -processors: - batch: - -exporters: - logging: - verbosity: detailed - -service: - pipelines: - logs: - receivers: [otlp] - processors: [batch] - exporters: [logging] \ No newline at end of file diff --git a/handlers/opentelemetry_structlog/README.md b/handlers/opentelemetry_structlog/README.md deleted file mode 100644 index ac5652f1d3..0000000000 --- a/handlers/opentelemetry_structlog/README.md +++ /dev/null @@ -1 +0,0 @@ -# Structlog handler for OpenTelemetry diff --git a/handlers/opentelemetry_structlog/__init__.py b/handlers/opentelemetry_structlog/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/handlers/opentelemetry_structlog/pyproject.toml b/handlers/opentelemetry_structlog/pyproject.toml deleted file mode 100644 index 241c46dfed..0000000000 --- a/handlers/opentelemetry_structlog/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[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/README.md b/handlers/opentelemetry_structlog/src/README.md deleted file mode 100644 index a61fef5e81..0000000000 --- a/handlers/opentelemetry_structlog/src/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Structlog Handler for OpenTelemetry -This project provides a Structlog handler for OpenTelemetry applications. The handler converts Structlog logs into the OpenTelemetry Logs Protocol (OTLP) format for export to a collector. - -## Usage - -To use the Structlog handler in your OpenTelemetry application, follow these steps: - -1. Import the necessary modules: - -```python -import structlog -from opentelemetry.sdk._logs._internal.export import LogExporter -from opentelemetry.sdk.resources import Resource -from handlers.opentelemetry_structlog.src.exporter import StructlogHandler -``` - -2. Initialize the StructlogHandler with your service name, server hostname, and LogExporter instance: - -```python -service_name = "my_service" -server_hostname = "my_server" -exporter = LogExporter() # Initialize your LogExporter instance -handler = StructlogHandler(service_name, server_hostname, exporter) -``` - -3. Add the handler to your Structlog logger: - -```python -structlog.configure( - processors=[structlog.processors.JSONRenderer()], - logger_factory=structlog.stdlib.LoggerFactory(), - wrapper_class=structlog.stdlib.BoundLogger, - cache_logger_on_first_use=True, - context_class=dict, - **handler.wrap_for_structlog(), -) -``` - -4. Use the logger as usual with Structlog: - -```python -logger = structlog.get_logger() -logger.info("This is a test log message.") -``` -## OpenTelemetry Application Example with Handler -See the structlog handler demo in the examples directory of this repository for a step-by-step guide on using the handler in an OpenTelemetry application. - -## Customization - -The StructlogHandler supports customization through its constructor parameters: - -- `service_name`: The name of your service. -- `server_hostname`: The hostname of the server where the logs originate. -- `exporter`: An instance of your LogExporter for exporting logs to a collector. - -## Notes - -- This handler automatically converts the `timestamp` key in the `event_dict` to ISO 8601 format for better compatibility. -- It performs operations similar to `structlog.processors.ExceptionRenderer`, so avoid using `ExceptionRenderer` in the same pipeline. -``` diff --git a/handlers/opentelemetry_structlog/src/__init__.py b/handlers/opentelemetry_structlog/src/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/handlers/opentelemetry_structlog/src/exporter.py b/handlers/opentelemetry_structlog/src/exporter.py deleted file mode 100644 index 9300391184..0000000000 --- a/handlers/opentelemetry_structlog/src/exporter.py +++ /dev/null @@ -1,161 +0,0 @@ -"""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 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 - `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/version.py b/handlers/opentelemetry_structlog/src/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/handlers/opentelemetry_structlog/src/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From fb1f63fbc0fb9de9d78b60455c87b370626590c1 Mon Sep 17 00:00:00 2001 From: Zane Dufour Date: Tue, 15 Oct 2024 15:35:38 -0400 Subject: [PATCH 23/25] remove structlog from test requirements --- .../opentelemetry-instrumentation-logging/test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt index 0e484f0340..d629dae5b9 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt +++ b/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt @@ -10,7 +10,6 @@ tomli==2.0.1 typing_extensions==4.12.2 wrapt==1.16.0 zipp==3.19.2 -structlog==24.1.0 loguru==0.7.2 opentelemetry-exporter-otlp==1.24.0 -e opentelemetry-instrumentation From 9ec303c9288c90c71f64a25f69dd8f1b1fdf73fc Mon Sep 17 00:00:00 2001 From: Zane Dufour Date: Tue, 15 Oct 2024 15:38:59 -0400 Subject: [PATCH 24/25] remove out.txt --- out.txt | 223 -------------------------------------------------------- 1 file changed, 223 deletions(-) delete mode 100644 out.txt diff --git a/out.txt b/out.txt deleted file mode 100644 index 2411b8bb72..0000000000 --- a/out.txt +++ /dev/null @@ -1,223 +0,0 @@ -test-instrumentation-logging: commands_pre[0]> pip install 'opentelemetry-api@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api' -Collecting opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api - Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-1knewf_o/opentelemetry-api_282d8d799fb24ab896e904b085f29109 - Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Getting requirements to build wheel: started - Getting requirements to build wheel: finished with status 'done' - Preparing metadata (pyproject.toml): started - Preparing metadata (pyproject.toml): finished with status 'done' -Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (1.2.14) -Requirement already satisfied: importlib-metadata<=7.1,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (6.11.0) -Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (1.16.0) -Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.1,>=6.0->opentelemetry-api@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-api&subdirectory=opentelemetry-api) (3.17.0) -test-instrumentation-logging: commands_pre[1]> pip install 'opentelemetry-semantic-conventions@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions' -Collecting opentelemetry-semantic-conventions@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-semantic-conventions&subdirectory=opentelemetry-semantic-conventions - Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-cvfubdbv/opentelemetry-semantic-conventions_7e56a1d44143473d9348c6b1f84e3046 - Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Getting requirements to build wheel: started - Getting requirements to build wheel: finished with status 'done' - Preparing metadata (pyproject.toml): started - Preparing metadata (pyproject.toml): finished with status 'done' -test-instrumentation-logging: commands_pre[2]> pip install 'opentelemetry-sdk@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk' -Collecting opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk - Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-lk1c77a2/opentelemetry-sdk_9c8115670b894894a9c0ecb14b38aaf8 - Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Getting requirements to build wheel: started - Getting requirements to build wheel: finished with status 'done' - Preparing metadata (pyproject.toml): started - Preparing metadata (pyproject.toml): finished with status 'done' -Requirement already satisfied: opentelemetry-api==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.25.0.dev0) -Requirement already satisfied: opentelemetry-semantic-conventions==0.46b0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (0.46b0.dev0) -Requirement already satisfied: typing-extensions>=3.7.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (4.9.0) -Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.2.14) -Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (6.11.0) -Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (1.16.0) -Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api==1.25.0.dev->opentelemetry-sdk@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-sdk&subdirectory=opentelemetry-sdk) (3.17.0) -test-instrumentation-logging: commands_pre[3]> pip install 'opentelemetry-test-utils@git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils' -Collecting opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils - Cloning https://github.com/open-telemetry/opentelemetry-python.git (to revision main) to /tmp/pip-install-jjc0xm3t/opentelemetry-test-utils_b7d3f4b0c60a4857a3f5b4a8c2d32b34 - Resolved https://github.com/open-telemetry/opentelemetry-python.git to commit 634c9f4095166a410350ecc73975b6e6fb360cd4 - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Getting requirements to build wheel: started - Getting requirements to build wheel: finished with status 'done' - Preparing metadata (pyproject.toml): started - Preparing metadata (pyproject.toml): finished with status 'done' -Requirement already satisfied: asgiref~=3.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (3.7.2) -Requirement already satisfied: opentelemetry-api==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.25.0.dev0) -Requirement already satisfied: opentelemetry-sdk==1.25.0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.25.0.dev0) -Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.2.14) -Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (6.11.0) -Requirement already satisfied: opentelemetry-semantic-conventions==0.46b0.dev in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (0.46b0.dev0) -Requirement already satisfied: typing-extensions>=3.7.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-sdk==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (4.9.0) -Requirement already satisfied: wrapt<2,>=1.10 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from deprecated>=1.2.6->opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (1.16.0) -Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api==1.25.0.dev->opentelemetry-test-utils@ git+https://github.com/open-telemetry/opentelemetry-python.git@main#egg=opentelemetry-test-utils&subdirectory=tests/opentelemetry-test-utils) (3.17.0) -test-instrumentation-logging: commands_pre[4]> pip install /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation -Processing ./opentelemetry-instrumentation - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Getting requirements to build wheel: started - Getting requirements to build wheel: finished with status 'done' - Preparing metadata (pyproject.toml): started - Preparing metadata (pyproject.toml): finished with status 'done' -Requirement already satisfied: opentelemetry-api~=1.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (1.25.0.dev0) -Requirement already satisfied: setuptools>=16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (69.1.1) -Requirement already satisfied: wrapt<2.0.0,>=1.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0) (1.16.0) -Requirement already satisfied: deprecated>=1.2.6 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (1.2.14) -Requirement already satisfied: importlib-metadata<=7.0,>=6.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (6.11.0) -Requirement already satisfied: zipp>=0.5 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from importlib-metadata<=7.0,>=6.0->opentelemetry-api~=1.4->opentelemetry-instrumentation==0.45b0.dev0) (3.17.0) -Building wheels for collected packages: opentelemetry-instrumentation - Building wheel for opentelemetry-instrumentation (pyproject.toml): started - Building wheel for opentelemetry-instrumentation (pyproject.toml): finished with status 'done' - Created wheel for opentelemetry-instrumentation: filename=opentelemetry_instrumentation-0.45b0.dev0-py3-none-any.whl size=28475 sha256=9ba0c4bbb5acb27d366f629b255b37c78443be9e8cc7930d86aad9740e6103e7 - Stored in directory: /home/mcastilh/.cache/pip/wheels/53/1e/17/449f4361c03c07bdeee2403350a3749480b9e78fb831ca633a -Successfully built opentelemetry-instrumentation -Installing collected packages: opentelemetry-instrumentation - Attempting uninstall: opentelemetry-instrumentation - Found existing installation: opentelemetry-instrumentation 0.45b0.dev0 - Uninstalling opentelemetry-instrumentation-0.45b0.dev0: - Successfully uninstalled opentelemetry-instrumentation-0.45b0.dev0 -Successfully installed opentelemetry-instrumentation-0.45b0.dev0 -test-instrumentation-logging: commands_pre[5]> pip install -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt -Obtaining file:///mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Checking if build backend supports build_editable: started - Checking if build backend supports build_editable: finished with status 'done' - Getting requirements to build editable: started - Getting requirements to build editable: finished with status 'done' - Installing backend dependencies: started - Installing backend dependencies: finished with status 'done' - Preparing editable metadata (pyproject.toml): started - Preparing editable metadata (pyproject.toml): finished with status 'done' -Obtaining file:///mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 19)) - Installing build dependencies: started - Installing build dependencies: finished with status 'done' - Checking if build backend supports build_editable: started - Checking if build backend supports build_editable: finished with status 'done' - Getting requirements to build editable: started - Getting requirements to build editable: finished with status 'done' - Installing backend dependencies: started - Installing backend dependencies: finished with status 'done' - Preparing editable metadata (pyproject.toml): started - Preparing editable metadata (pyproject.toml): finished with status 'done' -Requirement already satisfied: asgiref==3.7.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 1)) (3.7.2) -Requirement already satisfied: attrs==23.2.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 2)) (23.2.0) -Requirement already satisfied: Deprecated==1.2.14 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 3)) (1.2.14) -Requirement already satisfied: importlib-metadata==6.11.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 4)) (6.11.0) -Requirement already satisfied: iniconfig==2.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 5)) (2.0.0) -Requirement already satisfied: packaging==23.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 6)) (23.2) -Requirement already satisfied: pluggy==1.4.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 7)) (1.4.0) -Requirement already satisfied: py==1.11.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 8)) (1.11.0) -Requirement already satisfied: py-cpuinfo==9.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 9)) (9.0.0) -Requirement already satisfied: pytest==7.1.3 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 10)) (7.1.3) -Requirement already satisfied: pytest-benchmark==4.0.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 11)) (4.0.0) -Requirement already satisfied: tomli==2.0.1 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 12)) (2.0.1) -Requirement already satisfied: typing_extensions==4.9.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 13)) (4.9.0) -Requirement already satisfied: wrapt==1.16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 14)) (1.16.0) -Requirement already satisfied: zipp==3.17.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 15)) (3.17.0) -Requirement already satisfied: structlog==24.1.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 16)) (24.1.0) -Requirement already satisfied: loguru==0.7.2 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from -r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 17)) (0.7.2) -Requirement already satisfied: opentelemetry-api~=1.4 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0->-r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) (1.25.0.dev0) -Requirement already satisfied: setuptools>=16.0 in ./.tox/test-instrumentation-logging/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.45b0.dev0->-r /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/test-requirements.txt (line 18)) (69.1.1) -Building wheels for collected packages: opentelemetry-instrumentation, opentelemetry-instrumentation-logging - Building editable for opentelemetry-instrumentation (pyproject.toml): started - Building editable for opentelemetry-instrumentation (pyproject.toml): finished with status 'done' - Created wheel for opentelemetry-instrumentation: filename=opentelemetry_instrumentation-0.45b0.dev0-py3-none-any.whl size=7991 sha256=97b3a648568fd6f2e0e9f7b24eb07089b4abcb7e4fef063a4490ec8c7bc7bd79 - Stored in directory: /tmp/pip-ephem-wheel-cache-u7iyg0ou/wheels/53/1e/17/449f4361c03c07bdeee2403350a3749480b9e78fb831ca633a - Building editable for opentelemetry-instrumentation-logging (pyproject.toml): started - Building editable for opentelemetry-instrumentation-logging (pyproject.toml): finished with status 'done' - Created wheel for opentelemetry-instrumentation-logging: filename=opentelemetry_instrumentation_logging-0.45b0.dev0-py3-none-any.whl size=6424 sha256=19f0ee6f119b532ac641035008cfdd1d313741dce3d8f9923ea9d4440fdbd9e0 - Stored in directory: /tmp/pip-ephem-wheel-cache-u7iyg0ou/wheels/88/de/f3/36847e89208d714c671b6408e927acddfcb3b5f0c8ef00f1d7 -Successfully built opentelemetry-instrumentation opentelemetry-instrumentation-logging -Installing collected packages: opentelemetry-instrumentation, opentelemetry-instrumentation-logging - Attempting uninstall: opentelemetry-instrumentation - Found existing installation: opentelemetry-instrumentation 0.45b0.dev0 - Uninstalling opentelemetry-instrumentation-0.45b0.dev0: - Successfully uninstalled opentelemetry-instrumentation-0.45b0.dev0 - Attempting uninstall: opentelemetry-instrumentation-logging - Found existing installation: opentelemetry-instrumentation-logging 0.45b0.dev0 - Uninstalling opentelemetry-instrumentation-logging-0.45b0.dev0: - Successfully uninstalled opentelemetry-instrumentation-logging-0.45b0.dev0 -Successfully installed opentelemetry-instrumentation-0.45b0.dev0 opentelemetry-instrumentation-logging-0.45b0.dev0 -test-instrumentation-logging: commands[0]> pytest /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/tests -============================= test session starts ============================== -platform linux -- Python 3.10.12, pytest-7.1.3, pluggy-1.4.0 -- /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/.tox/test-instrumentation-logging/bin/python -cachedir: .tox/test-instrumentation-logging/.pytest_cache -benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) -rootdir: /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib, configfile: pytest.ini -plugins: benchmark-4.0.0 -collecting ... collected 17 items - -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentorProxyTracerProvider::test_trace_context_injection PASSED [ 5%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_basic_config_called PASSED [ 11%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_custom_format_and_level_api PASSED [ 17%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_custom_format_and_level_env PASSED [ 23%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_log_hook PASSED [ 29%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_trace_context_injection PASSED [ 35%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_trace_context_injection_without_span PASSED [ 41%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoggingInstrumentor::test_uninstrumented --------------------------------- live log call --------------------------------- -WARNING opentelemetry.instrumentation.instrumentor:instrumentor.py:132 Attempting to uninstrument while already uninstrumented -PASSED [ 47%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_call_method_processes_log_correctly PASSED [ 52%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_initialization PASSED [ 58%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_parse_exception PASSED [ 64%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_parse_timestamp PASSED [ 70%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_post_process_formats_timestamp PASSED [ 76%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestStructlogHandler::test_pre_process_adds_timestamp PASSED [ 82%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_attributes_extraction_without_exception FAILED [ 88%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_initialization_with_custom_provider PASSED [ 94%] -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py::TestLoguruHandler::test_initialization_with_default_provider PASSED [100%] - -=================================== FAILURES =================================== -________ TestLoguruHandler.test_attributes_extraction_without_exception ________ - -self = - - def test_attributes_extraction_without_exception(self): - attrs = LoguruHandler._get_attributes(self.record) - expected_attrs = { - SpanAttributes.CODE_FILEPATH: 'test_file.py', - SpanAttributes.CODE_FUNCTION: 'test_function', - SpanAttributes.CODE_LINENO: 123 - } - print("Get attributes: ", attrs) - -> self.assertEqual(attrs, expected_attrs) -E AssertionError: {'time': 1581000000.000123, 'level': , -E - 'line': 123, -E - 'message': 'Test message', -E - 'time': 1581000000.000123} - -instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py:348: AssertionError ------------------------------ Captured stdout call ----------------------------- -Get attributes: {'time': 1581000000.000123, 'level': , 'message': 'Test message', 'file': 'test_file.py', 'function': 'test_function', 'line': 123, 'exception': None, 'code.filepath': 'test_file.py', 'code.function': 'test_function', 'code.lineno': 123} -=============================== warnings summary =============================== -opentelemetry-instrumentation/src/opentelemetry/instrumentation/dependencies.py:4 - /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/opentelemetry-instrumentation/src/opentelemetry/instrumentation/dependencies.py:4: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html - from pkg_resources import ( - --- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html -=================== 1 failed, 16 passed, 1 warning in 17.81s =================== -test-instrumentation-logging: exit 1 (51.41 seconds) /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib> pytest /mnt/c/Users/migca/OneDrive/Documents/Purdue_Files/ECE_595_SWE/project/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-logging/tests pid=209543 - test-instrumentation-logging: FAIL code 1 (534.73=setup[0.34]+cmd[60.77,59.86,52.61,56.45,72.26,181.03,51.41] seconds) - evaluation failed :( (534.91 seconds) From dfb9cdcf8ee1bf1ec30dee589e319a86c8335017 Mon Sep 17 00:00:00 2001 From: Zane Dufour Date: Tue, 15 Oct 2024 15:39:23 -0400 Subject: [PATCH 25/25] remove structlog from test_logging.py --- .../tests/test_logging.py | 329 +++++------------- 1 file changed, 92 insertions(+), 237 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py index cdbccc1201..d876148ed0 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py +++ b/instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py @@ -15,16 +15,10 @@ import logging from typing import Optional from unittest import mock -import unittest from collections import namedtuple 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 unittest.mock import MagicMock, patch from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( @@ -39,13 +33,17 @@ LoggingInstrumentor, ) from opentelemetry.test.test_base import TestBase -from opentelemetry.trace import NoOpTracerProvider, ProxyTracer, get_tracer, get_current_span, SpanContext, TraceFlags +from opentelemetry.trace import ( + NoOpTracerProvider, + ProxyTracer, + get_tracer, + get_current_span, +) -from handlers.opentelemetry_structlog.src.exporter import StructlogHandler from handlers.opentelemetry_loguru.src.exporter import LoguruHandler, _STD_TO_OTEL -from opentelemetry._logs import get_logger_provider, get_logger -import time +from opentelemetry._logs import get_logger_provider + class FakeTracerProvider: def get_tracer( # pylint: disable=no-self-use @@ -120,9 +118,7 @@ def test_trace_context_injection(self): span_id = format(span.get_span_context().span_id, "016x") trace_id = format(span.get_span_context().trace_id, "032x") trace_sampled = span.get_span_context().trace_flags.sampled - self.assert_trace_context_injected( - span_id, trace_id, trace_sampled - ) + self.assert_trace_context_injected(span_id, trace_id, trace_sampled) def test_trace_context_injection_without_span(self): self.assert_trace_context_injected("0", "0", False) @@ -167,9 +163,7 @@ def test_custom_format_and_level_env(self, basic_config_mock): env_patch.stop() @mock.patch("logging.basicConfig") - def test_custom_format_and_level_api( - self, basic_config_mock - ): # pylint: disable=no-self-use + def test_custom_format_and_level_api(self, basic_config_mock): # pylint: disable=no-self-use LoggingInstrumentor().uninstrument() LoggingInstrumentor().instrument( set_logging_format=True, @@ -208,9 +202,7 @@ def test_uninstrumented(self): span_id = format(span.get_span_context().span_id, "016x") trace_id = format(span.get_span_context().trace_id, "032x") trace_sampled = span.get_span_context().trace_flags.sampled - self.assert_trace_context_injected( - span_id, trace_id, trace_sampled - ) + self.assert_trace_context_injected(span_id, trace_id, trace_sampled) LoggingInstrumentor().uninstrument() @@ -229,196 +221,28 @@ def test_uninstrumented(self): 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 mocker(self): - return MagicMock() - - 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" - - 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): - # 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)} - - # 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() - - def test_log_record_translation_attributes(self): - """Verify that event_dict translates correctly into a LogRecord with the correct attributes.""" - exporter = MagicMock() - logger = MagicMock() - exporter_instance = StructlogHandler("test_service", "test_host", exporter) - exporter_instance._logger = logger - - timestamp = datetime.now(timezone.utc).isoformat() - event_dict = { - "level": "info", - "event": "test event", - "timestamp": timestamp - } - # Get the StructlogHandler instance - - # Assuming StructlogHandler has a method to process and possibly log the event_dict directly. - # Use the instance to process the event_dict. - # Mocking the internal logger's emit method to capture the log record - with patch.object(exporter_instance._logger, 'emit') as mock_emit: - exporter_instance(event_dict=event_dict, logger=logger, name=None) - calls = mock_emit.call_args_list - assert len(calls) > 0, "Emit should be called" - log_record = calls[0][0][0] # First call, first arg - - # Assuming log_record is the structured log that would have been emitted, - # and you need to verify its contents. - # Need to adjust the assertion depending on how log records are structured. - # I am assuming log_record is a dictionary that was passed to logger.emit. - assert log_record.body == event_dict["event"], "LogRecord body should match event" - - assert log_record.attributes["level"] == event_dict["level"], "LogRecord level should match event" - - def test_filtering_of_excluded_attributes(self): - """Ensure specified attributes are not passed to the log record.""" - event_dict = { - "level": "error", - "event": "Something happened!", - "timestamp": datetime.now(timezone.utc), - "exception": (ValueError, ValueError("An error occurred"), None) - } - - # Get the StructlogHandler instance - exporter_instance = self.structlog_exporter() - - with patch.object(exporter_instance._logger, "emit") as mocked_emit: - # Call the exporter_instance with the event_dict - exporter_instance(event_dict=event_dict, logger=None, name=None) - - # Check if emit method was called - mocked_emit.assert_called_once() - - # Get the log record passed to emit method - log_record = mocked_emit.call_args.args[0] - - # Check if the exception attribute is not present in the log record - assert "exception" not in log_record.attributes, "Excluded attributes should not be in the log record" - - - def test_trace_context_propogation(self): - """Ensure trace context is correctly propagated to the log record.""" - with self.tracer.start_as_current_span("test_span") as span: - span_id = format(span.get_span_context().span_id, "016x") - trace_id = format(span.get_span_context().trace_id, "032x") - trace_sampled = span.get_span_context().trace_flags.sampled - event_dict = { - "level": "info", - "event": "test event", - "timestamp": datetime.now(timezone.utc) - } - - # Get the StructlogHandler instance - exporter_instance = self.structlog_exporter() - - with patch.object(exporter_instance, "_logger") as mocked_logger: - exporter_instance(event_dict=event_dict, logger=None, name=None) - calls = mocked_logger.emit.call_args_list - log_record = calls[0][0][0] - - # Assert that the log record has the correct trace context - actual_span_id = format(log_record.span_id, "016x") - assert actual_span_id == span_id, "Span ID should be propagated" - - actual_trace_id = format(log_record.trace_id, "032x") - assert actual_trace_id == trace_id, "Trace ID should be propagated" - - assert log_record.trace_flags == trace_sampled, "Trace flags should be propagated" - class TimestampRecord: def __init__(self, data): self.timestam = data + def timestamp(self): return self.timestam + class TestLoguruHandler(TestBase): def setUp(self): self.default_provider = get_logger_provider() self.custom_provider = MagicMock() - RecordFile = namedtuple('RecordFile', ['path', 'name']) - file_record = RecordFile( - path="test_file.py", - name = "test_file.py" - ) + RecordFile = namedtuple("RecordFile", ["path", "name"]) + file_record = RecordFile(path="test_file.py", name="test_file.py") - RecordProcess = namedtuple('RecordProcess', ['name', 'id']) - process_record = RecordProcess( - name = "MainProcess", - id = 1 - ) + RecordProcess = namedtuple("RecordProcess", ["name", "id"]) + process_record = RecordProcess(name="MainProcess", id=1) - RecordThread = namedtuple('RecordThread', ['name', 'id']) - thread_record = RecordThread( - name = "MainThread", - id = 1 - ) + RecordThread = namedtuple("RecordThread", ["name", "id"]) + thread_record = RecordThread(name="MainThread", id=1) timeRec = TimestampRecord(data=2.38763786) @@ -431,89 +255,120 @@ def setUp(self): "thread": thread_record, "function": "test_function", "line": 123, - "exception": None + "exception": None, } self.span_context = get_current_span().get_span_context() self.current_span = MagicMock() - self.current_span.get_span_context.return_value = self.span_context + self.current_span.get_span_context.return_value = self.span_context def test_attributes_extraction_without_exception(self): - handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) + handler = LoguruHandler( + service_name="flask-loguru-demo", + server_hostname="instance-1", + exporter=OTLPLogExporter(insecure=True), + ) attrs = handler._get_attributes(self.record) expected_attrs = { - SpanAttributes.CODE_FILEPATH: 'test_file.py', - SpanAttributes.CODE_FUNCTION: 'test_function', - SpanAttributes.CODE_LINENO: 123 + SpanAttributes.CODE_FILEPATH: "test_file.py", + SpanAttributes.CODE_FUNCTION: "test_function", + SpanAttributes.CODE_LINENO: 123, } - - self.assertEqual(attrs[SpanAttributes.CODE_FILEPATH], expected_attrs[SpanAttributes.CODE_FILEPATH]) - self.assertEqual(attrs[SpanAttributes.CODE_FUNCTION], expected_attrs[SpanAttributes.CODE_FUNCTION]) - self.assertEqual(attrs[SpanAttributes.CODE_LINENO], expected_attrs[SpanAttributes.CODE_LINENO]) - @patch('traceback.format_exception') + self.assertEqual( + attrs[SpanAttributes.CODE_FILEPATH], + expected_attrs[SpanAttributes.CODE_FILEPATH], + ) + self.assertEqual( + attrs[SpanAttributes.CODE_FUNCTION], + expected_attrs[SpanAttributes.CODE_FUNCTION], + ) + self.assertEqual( + attrs[SpanAttributes.CODE_LINENO], + expected_attrs[SpanAttributes.CODE_LINENO], + ) + + @patch("traceback.format_exception") def test_attributes_extraction_with_exception(self, mock_format_exception): - mock_format_exception.return_value = 'Exception traceback' + mock_format_exception.return_value = "Exception traceback" exception = Exception("Test exception") - - ExceptionRecord = namedtuple('ExceptionRecord', ['type', 'value', 'traceback']) -# Example usage: + ExceptionRecord = namedtuple("ExceptionRecord", ["type", "value", "traceback"]) + + # Example usage: exception_record = ExceptionRecord( type=type(exception).__name__, value=str(exception), - traceback=mock_format_exception(exception) + traceback=mock_format_exception(exception), ) - self.record['exception'] = exception_record + self.record["exception"] = exception_record - - handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) + handler = LoguruHandler( + service_name="flask-loguru-demo", + server_hostname="instance-1", + exporter=OTLPLogExporter(insecure=True), + ) attrs = handler._get_attributes(self.record) - + expected_attrs = { - SpanAttributes.CODE_FILEPATH: 'test_file.py', - SpanAttributes.CODE_FUNCTION: 'test_function', + SpanAttributes.CODE_FILEPATH: "test_file.py", + SpanAttributes.CODE_FUNCTION: "test_function", SpanAttributes.CODE_LINENO: 123, - SpanAttributes.EXCEPTION_TYPE: 'Exception', - SpanAttributes.EXCEPTION_MESSAGE: 'Test exception', - SpanAttributes.EXCEPTION_STACKTRACE: 'Exception traceback' + SpanAttributes.EXCEPTION_TYPE: "Exception", + SpanAttributes.EXCEPTION_MESSAGE: "Test exception", + SpanAttributes.EXCEPTION_STACKTRACE: "Exception traceback", } - - self.assertEqual(attrs[SpanAttributes.EXCEPTION_TYPE], expected_attrs[SpanAttributes.EXCEPTION_TYPE]) - self.assertEqual(attrs[SpanAttributes.EXCEPTION_MESSAGE], expected_attrs[SpanAttributes.EXCEPTION_MESSAGE]) - self.assertEqual(attrs[SpanAttributes.EXCEPTION_STACKTRACE], expected_attrs[SpanAttributes.EXCEPTION_STACKTRACE]) - @patch('opentelemetry.trace.get_current_span') + self.assertEqual( + attrs[SpanAttributes.EXCEPTION_TYPE], + expected_attrs[SpanAttributes.EXCEPTION_TYPE], + ) + self.assertEqual( + attrs[SpanAttributes.EXCEPTION_MESSAGE], + expected_attrs[SpanAttributes.EXCEPTION_MESSAGE], + ) + self.assertEqual( + attrs[SpanAttributes.EXCEPTION_STACKTRACE], + expected_attrs[SpanAttributes.EXCEPTION_STACKTRACE], + ) + + @patch("opentelemetry.trace.get_current_span") def test_translation(self, mock_get_current_span): mock_get_current_span.return_value = self.current_span - handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) - + handler = LoguruHandler( + service_name="flask-loguru-demo", + server_hostname="instance-1", + exporter=OTLPLogExporter(insecure=True), + ) + log_record = handler._translate(self.record) self.assertEqual(log_record.trace_id, self.span_context.trace_id) self.assertEqual(log_record.span_id, self.span_context.span_id) self.assertEqual(log_record.trace_flags, self.span_context.trace_flags) - self.assertEqual(log_record.severity_number, _STD_TO_OTEL[self.record["level"].no]) + self.assertEqual( + log_record.severity_number, _STD_TO_OTEL[self.record["level"].no] + ) self.assertEqual(log_record.body, self.record["message"]) - @patch('opentelemetry._logs.Logger.emit') - @patch('opentelemetry.trace.get_current_span') + @patch("opentelemetry._logs.Logger.emit") + @patch("opentelemetry.trace.get_current_span") def test_sink(self, mock_get_current_span, mock_emit): mock_get_current_span.return_value = self.current_span - - handler = LoguruHandler(service_name="flask-loguru-demo", server_hostname="instance-1", exporter=OTLPLogExporter(insecure=True)) - MessageRecord = namedtuple('MessageRecord', ['record']) - message = MessageRecord( - record=self.record + handler = LoguruHandler( + service_name="flask-loguru-demo", + server_hostname="instance-1", + exporter=OTLPLogExporter(insecure=True), ) - handler.sink(message) - + MessageRecord = namedtuple("MessageRecord", ["record"]) + message = MessageRecord(record=self.record) + handler.sink(message) def test_no_op_tracer_provider(self): LoggingInstrumentor().uninstrument()