diff --git a/.changelog/5732.changed b/.changelog/5732.changed new file mode 100644 index 00000000000..442bf294375 --- /dev/null +++ b/.changelog/5732.changed @@ -0,0 +1 @@ +`opentelemetry-sdk`: stabilize the logs SDK as `opentelemetry.sdk.logs` diff --git a/.changelog/5732.deprecated b/.changelog/5732.deprecated new file mode 100644 index 00000000000..8a2b004a282 --- /dev/null +++ b/.changelog/5732.deprecated @@ -0,0 +1 @@ +`opentelemetry-sdk`: deprecate `opentelemetry.sdk._logs` in favor of `opentelemetry.sdk.logs` diff --git a/.changelog/5732.removed b/.changelog/5732.removed new file mode 100644 index 00000000000..0fbc88a3fe2 --- /dev/null +++ b/.changelog/5732.removed @@ -0,0 +1 @@ +`opentelemetry-sdk`: remove deprecated `LoggingHandler` and `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` diff --git a/.changelog/5732.removed.1 b/.changelog/5732.removed.1 new file mode 100644 index 00000000000..35caa21c727 --- /dev/null +++ b/.changelog/5732.removed.1 @@ -0,0 +1 @@ +`opentelemetry-sdk`: remove deprecated `Log*` aliases of the `LogRecord*` logs classes diff --git a/docs/conf.py b/docs/conf.py index 20d53cd2231..63d322410a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -153,10 +153,10 @@ "py:class", "opentelemetry.proto.collector.metrics.v1.metrics_service_pb2.ExportMetricsServiceRequest", ), - ("py:class", "opentelemetry.sdk._logs._internal.export.LogRecordExporter"), + ("py:class", "opentelemetry.sdk.logs._internal.export.LogRecordExporter"), ( "py:class", - "opentelemetry.sdk._logs._internal.export.LogRecordExportResult", + "opentelemetry.sdk.logs._internal.export.LogRecordExportResult", ), ( "py:class", diff --git a/docs/examples/logs/README.rst b/docs/examples/logs/README.rst index 2e3c749c7ba..680d3092d24 100644 --- a/docs/examples/logs/README.rst +++ b/docs/examples/logs/README.rst @@ -1,11 +1,6 @@ OpenTelemetry Logs SDK ====================== -.. warning:: - OpenTelemetry Python logs are in an experimental state. The APIs within - :mod:`opentelemetry.sdk._logs` are subject to change in minor/patch releases and make no - backward compatibility guarantees at this time. - The source files of these examples are available :scm_web:`here `. .. note:: diff --git a/docs/examples/logs/example.py b/docs/examples/logs/example.py index 8024018dd1d..20a5f2175a3 100644 --- a/docs/examples/logs/example.py +++ b/docs/examples/logs/example.py @@ -11,8 +11,8 @@ # this is available in the opentelemetry-instrumentation-logging package from opentelemetry.instrumentation.logging.handler import LoggingHandler from opentelemetry.logs import set_logger_provider -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import BatchLogRecordProcessor from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( diff --git a/docs/examples/metrics/sdk-metrics/sdk_metrics.py b/docs/examples/metrics/sdk-metrics/sdk_metrics.py index 9c65fe19bea..72dede5df93 100644 --- a/docs/examples/metrics/sdk-metrics/sdk_metrics.py +++ b/docs/examples/metrics/sdk-metrics/sdk_metrics.py @@ -14,8 +14,8 @@ from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( OTLPSpanExporter, ) -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import SimpleLogRecordProcessor +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import SimpleLogRecordProcessor from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk.resources import Resource diff --git a/docs/examples/multi-destination-exporting/README.rst b/docs/examples/multi-destination-exporting/README.rst index 0f23dc7dea8..8f74316d8d8 100644 --- a/docs/examples/multi-destination-exporting/README.rst +++ b/docs/examples/multi-destination-exporting/README.rst @@ -20,7 +20,7 @@ or reader per destination: ``metric_readers`` parameter. * **Logs**: Use one ``BatchLogRecordProcessor`` per destination, each - wrapping its own ``LogExporter``. Add each processor to the + wrapping its own ``LogRecordExporter``. Add each processor to the ``LoggerProvider`` via ``add_log_record_processor()``. .. note:: diff --git a/docs/examples/multi-destination-exporting/multi_destination_logs.py b/docs/examples/multi-destination-exporting/multi_destination_logs.py index 6b7e4281b7f..0e4c2e81239 100644 --- a/docs/examples/multi-destination-exporting/multi_destination_logs.py +++ b/docs/examples/multi-destination-exporting/multi_destination_logs.py @@ -19,8 +19,8 @@ # this is available in the opentelemetry-instrumentation-logging package from opentelemetry.instrumentation.logging.handler import LoggingHandler from opentelemetry.logs import set_logger_provider -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, ) diff --git a/docs/sdk/_logs.export.rst b/docs/sdk/_logs.export.rst deleted file mode 100644 index 19a40237424..00000000000 --- a/docs/sdk/_logs.export.rst +++ /dev/null @@ -1,7 +0,0 @@ -opentelemetry.sdk._logs.export -============================== - -.. automodule:: opentelemetry.sdk._logs.export - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/sdk/configuration.rst b/docs/sdk/configuration.rst index 3a5eb530217..7b90a52994e 100644 --- a/docs/sdk/configuration.rst +++ b/docs/sdk/configuration.rst @@ -144,7 +144,6 @@ Behavior notes components the file enables (for example resource detectors) and via ``${env:VAR}`` substitution. * Python-implementation extensions (``OTEL_PYTHON_*`` variables such as - ``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED`` or ``OTEL_PYTHON_TRACER_CONFIGURATOR``) are **not** applied when ``OTEL_CONFIG_FILE`` is set: the env-var initialisation path is skipped entirely. If your app currently relies on one of these and you are diff --git a/docs/sdk/index.rst b/docs/sdk/index.rst index 48c3568eab6..cb6de88c825 100644 --- a/docs/sdk/index.rst +++ b/docs/sdk/index.rst @@ -14,7 +14,7 @@ processed, and exported. .. toctree:: :maxdepth: 1 - _logs + logs resources trace metrics diff --git a/docs/sdk/logs.export.rst b/docs/sdk/logs.export.rst new file mode 100644 index 00000000000..00f6fa51e9c --- /dev/null +++ b/docs/sdk/logs.export.rst @@ -0,0 +1,7 @@ +opentelemetry.sdk.logs.export +============================= + +.. automodule:: opentelemetry.sdk.logs.export + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/sdk/_logs.rst b/docs/sdk/logs.rst similarity index 51% rename from docs/sdk/_logs.rst rename to docs/sdk/logs.rst index fb69caeda79..64e41fbd4cf 100644 --- a/docs/sdk/_logs.rst +++ b/docs/sdk/logs.rst @@ -1,17 +1,17 @@ -opentelemetry.sdk._logs package -=============================== +opentelemetry.sdk.logs package +============================== Submodules ---------- .. toctree:: - _logs.export + logs.export Module contents --------------- -.. automodule:: opentelemetry.sdk._logs +.. automodule:: opentelemetry.sdk.logs :members: :undoc-members: :show-inheritance: diff --git a/exporter/opentelemetry-exporter-otlp-json-common/src/opentelemetry/exporter/otlp/json/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-json-common/src/opentelemetry/exporter/otlp/json/common/_internal/_log_encoder/__init__.py index 1dee399db77..b8e294d29e7 100644 --- a/exporter/opentelemetry-exporter-otlp-json-common/src/opentelemetry/exporter/otlp/json/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-json-common/src/opentelemetry/exporter/otlp/json/common/_internal/_log_encoder/__init__.py @@ -27,7 +27,7 @@ from opentelemetry.proto_json.logs.v1.logs import ( ScopeLogs as JSONScopeLogs, ) -from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk.logs import ReadableLogRecord from opentelemetry.util.types import Attributes diff --git a/exporter/opentelemetry-exporter-otlp-json-common/tests/__init__.py b/exporter/opentelemetry-exporter-otlp-json-common/tests/__init__.py index 495eeb356f0..b2b055648fa 100644 --- a/exporter/opentelemetry-exporter-otlp-json-common/tests/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-json-common/tests/__init__.py @@ -5,7 +5,7 @@ import unittest from opentelemetry.logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk.logs import ReadableLogRecord from opentelemetry.sdk.metrics.export import ( AggregationTemporality, Buckets, diff --git a/exporter/opentelemetry-exporter-otlp-json-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-json-common/tests/test_log_encoder.py index 20ba8612869..a6cc81e86e9 100644 --- a/exporter/opentelemetry-exporter-otlp-json-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-json-common/tests/test_log_encoder.py @@ -27,7 +27,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as PB2Resource, ) -from opentelemetry.sdk._logs import LogRecordLimits, ReadWriteLogRecord +from opentelemetry.sdk.logs import LogRecordLimits, ReadWriteLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import ( diff --git a/exporter/opentelemetry-exporter-otlp-json-file/src/opentelemetry/exporter/otlp/json/file/_log_exporter.py b/exporter/opentelemetry-exporter-otlp-json-file/src/opentelemetry/exporter/otlp/json/file/_log_exporter.py index 9614c3a6908..5ea8b078e31 100644 --- a/exporter/opentelemetry-exporter-otlp-json-file/src/opentelemetry/exporter/otlp/json/file/_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-json-file/src/opentelemetry/exporter/otlp/json/file/_log_exporter.py @@ -8,12 +8,12 @@ from opentelemetry.exporter.otlp.json.common._log_encoder import encode_logs from opentelemetry.exporter.otlp.json.file._internal import _FileExporter -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk._shared_internal import DuplicateFilter +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import ( LogRecordExporter, LogRecordExportResult, ) -from opentelemetry.sdk._shared_internal import DuplicateFilter _logger = logging.getLogger(__name__) _logger.addFilter(DuplicateFilter()) diff --git a/exporter/opentelemetry-exporter-otlp-json-file/tests/test_log_exporter.py b/exporter/opentelemetry-exporter-otlp-json-file/tests/test_log_exporter.py index 0b22f0772ab..5990b8e2226 100644 --- a/exporter/opentelemetry-exporter-otlp-json-file/tests/test_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-json-file/tests/test_log_exporter.py @@ -17,11 +17,11 @@ ) from opentelemetry.logs import LogRecord, SeverityNumber from opentelemetry.proto_json.logs.v1.logs import LogsData -from opentelemetry.sdk._logs import ( +from opentelemetry.sdk.logs import ( LoggerProvider, ReadableLogRecord, ) -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs.export import ( InMemoryLogRecordExporter, LogRecordExportResult, SimpleLogRecordProcessor, diff --git a/exporter/opentelemetry-exporter-otlp-json-http/src/opentelemetry/exporter/otlp/json/http/_log_exporter.py b/exporter/opentelemetry-exporter-otlp-json-http/src/opentelemetry/exporter/otlp/json/http/_log_exporter.py index 6f67664fb9e..a27f63fed10 100644 --- a/exporter/opentelemetry-exporter-otlp-json-http/src/opentelemetry/exporter/otlp/json/http/_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-json-http/src/opentelemetry/exporter/otlp/json/http/_log_exporter.py @@ -20,11 +20,6 @@ _resolve_headers, _resolve_timeout, ) -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import ( - LogRecordExporter, - LogRecordExportResult, -) from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, @@ -35,6 +30,11 @@ OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, ) +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import ( + LogRecordExporter, + LogRecordExportResult, +) _DEFAULT_LOGS_EXPORT_PATH = "v1/logs" diff --git a/exporter/opentelemetry-exporter-otlp-json-http/tests/test_log_exporter.py b/exporter/opentelemetry-exporter-otlp-json-http/tests/test_log_exporter.py index 274852c412a..5bf4f97583a 100644 --- a/exporter/opentelemetry-exporter-otlp-json-http/tests/test_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-json-http/tests/test_log_exporter.py @@ -27,12 +27,6 @@ OTLPLogExporter, ) from opentelemetry.logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( - InMemoryLogRecordExporter, - LogRecordExportResult, - SimpleLogRecordProcessor, -) from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, @@ -44,6 +38,12 @@ OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT, ) +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( + InMemoryLogRecordExporter, + LogRecordExportResult, + SimpleLogRecordProcessor, +) from opentelemetry.sdk.resources import Resource from opentelemetry.trace import ( NonRecordingSpan, diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py index bbb907a8d49..02343b9ee18 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py @@ -19,7 +19,7 @@ ResourceLogs, ScopeLogs, ) -from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk.logs import ReadableLogRecord def encode_logs( diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_bytes_resource.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_bytes_resource.py index bb74a36f4d3..01c219b7ae1 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_bytes_resource.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_bytes_resource.py @@ -12,9 +12,9 @@ encode_metrics, ) from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( - InMemoryLogExporter, +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( + InMemoryLogRecordExporter, SimpleLogRecordProcessor, ) from opentelemetry.sdk.metrics import MeterProvider @@ -52,7 +52,7 @@ def test_encode_metrics(self): provider.shutdown() def test_encode_logs(self): - exporter = InMemoryLogExporter() + exporter = InMemoryLogRecordExporter() provider = LoggerProvider(resource=self.resource, shutdown_on_exit=False) provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) provider.get_logger(__name__).emit(body="body") diff --git a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py index 20ba8612869..a6cc81e86e9 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py +++ b/exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py @@ -27,7 +27,7 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as PB2Resource, ) -from opentelemetry.sdk._logs import LogRecordLimits, ReadWriteLogRecord +from opentelemetry.sdk.logs import LogRecordLimits, ReadWriteLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import ( diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py index c704990d048..24af969db81 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py @@ -20,11 +20,6 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2_grpc import ( LogsServiceStub, ) -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import ( - LogRecordExporter, - LogRecordExportResult, -) from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_EXPORTER_OTLP_GRPC_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, @@ -36,6 +31,11 @@ OTEL_EXPORTER_OTLP_LOGS_INSECURE, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, ) +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import ( + LogRecordExporter, + LogRecordExportResult, +) from opentelemetry.semconv._incubating.attributes.otel_attributes import ( OtelComponentTypeValues, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py index be135045c7e..e611a4eeb35 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py @@ -78,8 +78,6 @@ KeyValue, ) from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401 -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import LogRecordExportResult from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_EXPORTER_OTLP_GRPC_CREDENTIAL_PROVIDER, @@ -94,6 +92,8 @@ OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, ) +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import LogRecordExportResult from opentelemetry.sdk.metrics.export import MetricExportResult, MetricsData from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.trace import ReadableSpan diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py index 7f8ed99c6ea..2ea76cf13ea 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py @@ -28,7 +28,6 @@ from opentelemetry.proto.resource.v1.resource_pb2 import ( Resource as OTLPResource, ) -from opentelemetry.sdk._logs import ReadWriteLogRecord from opentelemetry.sdk.environment_variables import ( OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE, @@ -38,6 +37,7 @@ OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, ) +from opentelemetry.sdk.logs import ReadWriteLogRecord from opentelemetry.sdk.resources import Resource as SDKResource from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.trace import ( diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index 69e7d434983..8def20cedc5 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -27,11 +27,6 @@ _resolve_timeout, ) from opentelemetry.metrics import MeterProvider -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import ( - LogRecordExporter, - LogRecordExportResult, -) from opentelemetry.sdk._shared_internal import DuplicateFilter from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, @@ -44,6 +39,11 @@ OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, ) +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import ( + LogRecordExporter, + LogRecordExportResult, +) from opentelemetry.semconv._incubating.attributes.otel_attributes import ( OtelComponentTypeValues, ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py index 9492ae3a18d..056ca99d034 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py @@ -39,8 +39,6 @@ from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import ( ExportLogsServiceRequest, ) -from opentelemetry.sdk._logs import ReadWriteLogRecord -from opentelemetry.sdk._logs.export import LogRecordExportResult from opentelemetry.sdk.environment_variables import ( _OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER, OTEL_EXPORTER_OTLP_COMPRESSION, @@ -56,6 +54,8 @@ OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, ) +from opentelemetry.sdk.logs import ReadWriteLogRecord +from opentelemetry.sdk.logs.export import LogRecordExportResult from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import InMemoryMetricReader from opentelemetry.sdk.resources import Resource as SDKResource diff --git a/opentelemetry-api/src/opentelemetry/logs/_internal/__init__.py b/opentelemetry-api/src/opentelemetry/logs/_internal/__init__.py index 7d5a02ea9be..3a746baf0d9 100644 --- a/opentelemetry-api/src/opentelemetry/logs/_internal/__init__.py +++ b/opentelemetry-api/src/opentelemetry/logs/_internal/__init__.py @@ -470,7 +470,7 @@ def get_logger( """Returns a `Logger` for use within a python process. This function is a convenience wrapper for - opentelemetry.sdk._logs.LoggerProvider.get_logger. + opentelemetry.sdk.logs.LoggerProvider.get_logger. If logger_provider param is omitted the current configured one is used. """ diff --git a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py index 58de32420c7..8daa41f7f20 100644 --- a/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py +++ b/opentelemetry-configuration/src/opentelemetry/configuration/_logger_provider.py @@ -52,13 +52,13 @@ SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig, ) from opentelemetry.logs import set_logger_provider -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs._internal import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs._internal import ( LogRecordLimits, _LoggerConfig, _RuleBasedLoggerConfigurator, ) -from opentelemetry.sdk._logs._internal.export import ( +from opentelemetry.sdk.logs._internal.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, LogRecordExporter, diff --git a/opentelemetry-configuration/tests/test_logger_provider.py b/opentelemetry-configuration/tests/test_logger_provider.py index 2b6bf3e08e4..936fbe32f7e 100644 --- a/opentelemetry-configuration/tests/test_logger_provider.py +++ b/opentelemetry-configuration/tests/test_logger_provider.py @@ -64,8 +64,8 @@ SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig, ) from opentelemetry.logs import get_logger_provider -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs._internal.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs._internal.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, SimpleLogRecordProcessor, diff --git a/opentelemetry-sdk/benchmarks/logs/test_benchmark_logging_handler.py b/opentelemetry-sdk/benchmarks/logs/test_benchmark_logging_handler.py deleted file mode 100644 index 43c61d46324..00000000000 --- a/opentelemetry-sdk/benchmarks/logs/test_benchmark_logging_handler.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -import logging - -import pytest - -from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler -from opentelemetry.sdk._logs.export import ( - InMemoryLogRecordExporter, - SimpleLogRecordProcessor, -) - - -def _set_up_logging_handler(level): - logger_provider = LoggerProvider() - exporter = InMemoryLogRecordExporter() - processor = SimpleLogRecordProcessor(exporter=exporter) - logger_provider.add_log_record_processor(processor) - handler = LoggingHandler(level=level, logger_provider=logger_provider) - return handler - - -def _create_logger(handler, name): - logger = logging.getLogger(name) - logger.addHandler(handler) - return logger - - -@pytest.mark.parametrize("num_loggers", [1, 10, 100, 1000]) -def test_simple_get_logger_different_names(benchmark, num_loggers): - handler = _set_up_logging_handler(level=logging.DEBUG) - loggers = [_create_logger(handler, str(f"logger_{i}")) for i in range(num_loggers)] - - def benchmark_get_logger(): - for index in range(1000): - loggers[index % num_loggers].warning("test message") - - benchmark(benchmark_get_logger) diff --git a/opentelemetry-sdk/benchmarks/logs/test_benchmark_logs.py b/opentelemetry-sdk/benchmarks/logs/test_benchmark_logs.py index c38386d0bf8..e102b71170a 100644 --- a/opentelemetry-sdk/benchmarks/logs/test_benchmark_logs.py +++ b/opentelemetry-sdk/benchmarks/logs/test_benchmark_logs.py @@ -4,8 +4,8 @@ import pytest from opentelemetry.logs import SeverityNumber -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( BatchLogRecordProcessor, InMemoryLogRecordExporter, SimpleLogRecordProcessor, diff --git a/opentelemetry-sdk/pyproject.toml b/opentelemetry-sdk/pyproject.toml index 0a71623735c..354aab8e465 100644 --- a/opentelemetry-sdk/pyproject.toml +++ b/opentelemetry-sdk/pyproject.toml @@ -55,10 +55,10 @@ traceidratio = "opentelemetry.sdk.trace.sampling:TraceIdRatioBased" parentbased_traceidratio = "opentelemetry.sdk.trace.sampling:ParentBasedTraceIdRatio" [project.entry-points.opentelemetry_logger_provider] -sdk_logger_provider = "opentelemetry.sdk._logs:LoggerProvider" +sdk_logger_provider = "opentelemetry.sdk.logs:LoggerProvider" [project.entry-points.opentelemetry_logs_exporter] -console = "opentelemetry.sdk._logs.export:ConsoleLogRecordExporter" +console = "opentelemetry.sdk.logs.export:ConsoleLogRecordExporter" [project.entry-points.opentelemetry_meter_provider] sdk_meter_provider = "opentelemetry.sdk.metrics:MeterProvider" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 11dfc81413b..be248f8b060 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -9,9 +9,7 @@ from __future__ import annotations import logging -import logging.config import os -import warnings from abc import ABC, abstractmethod from collections.abc import Callable, Mapping, Sequence from os import environ @@ -25,18 +23,7 @@ ) from opentelemetry.logs import set_logger_provider from opentelemetry.metrics import set_meter_provider -from opentelemetry.sdk._logs import ( - LoggerProvider, - LoggingHandler, - LogRecordProcessor, -) -from opentelemetry.sdk._logs._internal import _LoggerConfiguratorT -from opentelemetry.sdk._logs.export import ( - BatchLogRecordProcessor, - LogRecordExporter, -) from opentelemetry.sdk.environment_variables import ( - _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, OTEL_CONFIG_FILE, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, @@ -48,6 +35,15 @@ OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, ) +from opentelemetry.sdk.logs import ( + LoggerProvider, + LogRecordProcessor, +) +from opentelemetry.sdk.logs._internal import _LoggerConfiguratorT +from opentelemetry.sdk.logs.export import ( + BatchLogRecordProcessor, + LogRecordExporter, +) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics._internal import _MeterConfiguratorT from opentelemetry.sdk.metrics.export import ( @@ -259,7 +255,6 @@ def _init_metrics( def _init_logging( exporters: dict[str, type[LogRecordExporter]], resource: Resource | None = None, - setup_logging_handler: bool = True, exporter_args_map: ExporterArgsMap | None = None, log_record_processors: Sequence[LogRecordProcessor] | None = None, export_log_record_processor: _ConfigurationExporterLogRecordProcessorT | None = None, @@ -279,44 +274,6 @@ def _init_logging( exporter_args = exporter_args_map.get(exporter_class, {}) provider.add_log_record_processor(export_processor(exporter_class(**exporter_args))) - if setup_logging_handler: - warnings.warn( - "The `OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` environment variable " - "and the `LoggingHandler` in `opentelemetry-sdk` that it controls are deprecated." - "Install `opentelemetry-instrumentation-logging` package instead.", - DeprecationWarning, - ) - - # Add OTel handler - handler = LoggingHandler(level=logging.NOTSET, logger_provider=provider) - logging.getLogger().addHandler(handler) - _overwrite_logging_config_fns(handler) - - -def _overwrite_logging_config_fns(handler: LoggingHandler) -> None: - root = logging.getLogger() - - def wrapper(config_fn: Callable) -> Callable: - def overwritten_config_fn(*args, **kwargs): - removed_handler = False - # We don't want the OTLP handler to be modified or deleted by the logging config functions. - # So we remove it and then add it back after the function call. - if handler in root.handlers: - removed_handler = True - root.handlers.remove(handler) - try: - config_fn(*args, **kwargs) - finally: - # Ensure handler is added back if logging function throws exception. - if removed_handler: - root.addHandler(handler) - - return overwritten_config_fn - - logging.config.fileConfig = wrapper(logging.config.fileConfig) - logging.config.dictConfig = wrapper(logging.config.dictConfig) - logging.basicConfig = wrapper(logging.basicConfig) - def _import_logger_configurator( logger_configurator_name: str | None, @@ -509,7 +466,6 @@ def _initialize_components( sampler: Sampler | None = None, resource_attributes: Attributes | None = None, id_generator: IdGenerator | None = None, - setup_logging_handler: bool | None = None, exporter_args_map: ExporterArgsMap | None = None, span_processors: Sequence[SpanProcessor] | None = None, export_span_processor: _ConfigurationExporterSpanProcessorT | None = None, @@ -585,14 +541,9 @@ def _initialize_components( exporter_args_map=exporter_args_map, meter_configurator=meter_configurator, ) - if setup_logging_handler is None: - setup_logging_handler = ( - os.getenv(_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false").strip().lower() == "true" - ) _init_logging( log_exporters, resource, - setup_logging_handler, exporter_args_map=exporter_args_map, log_record_processors=log_record_processors, export_log_record_processor=export_log_record_processor, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py index 36f218e1fb6..f2089d234c9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py @@ -1,31 +1,58 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 +""" +Deprecated alias of :mod:`opentelemetry.sdk.logs`. -from opentelemetry.sdk._logs._internal import ( +.. deprecated:: 1.46.0 + This module will be removed in a future release. Use + :mod:`opentelemetry.sdk.logs` instead. +""" + +import sys +import warnings + +from opentelemetry.sdk.logs import ( ConcurrentMultiLogRecordProcessor, - LogDroppedAttributesWarning, Logger, LoggerProvider, - LoggingHandler, - LogLimits, LogRecordDroppedAttributesWarning, LogRecordLimits, LogRecordProcessor, ReadableLogRecord, ReadWriteLogRecord, SynchronousMultiLogRecordProcessor, + _internal, + export, +) +from opentelemetry.sdk.logs._internal import ( + _exceptions, + _logger_metrics, ) +from opentelemetry.sdk.logs._internal import export as _internal_export +from opentelemetry.sdk.logs._internal.export import in_memory_log_exporter + +warnings.warn( + "The opentelemetry.sdk._logs module is deprecated since version 1.46.0 " + "and will be removed in a future release. " + "Use opentelemetry.sdk.logs instead.", + DeprecationWarning, + stacklevel=2, +) + +sys.modules[f"{__name__}.export"] = export +sys.modules[f"{__name__}._internal"] = _internal +sys.modules[f"{__name__}._internal._exceptions"] = _exceptions +sys.modules[f"{__name__}._internal._logger_metrics"] = _logger_metrics +sys.modules[f"{__name__}._internal.export"] = _internal_export +sys.modules[f"{__name__}._internal.export.in_memory_log_exporter"] = in_memory_log_exporter __all__ = [ "ConcurrentMultiLogRecordProcessor", - "LogDroppedAttributesWarning", - "LogLimits", "LogRecordDroppedAttributesWarning", "LogRecordLimits", "LogRecordProcessor", "Logger", "LoggerProvider", - "LoggingHandler", "ReadWriteLogRecord", "ReadableLogRecord", "SynchronousMultiLogRecordProcessor", diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 303f62924bc..643a296c824 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -21,8 +21,7 @@ ``${env:VAR}`` substitution inside the file. Python-implementation extensions outside the spec (``OTEL_PYTHON_*`` -variables such as ``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED`` or -:envvar:`OTEL_PYTHON_TRACER_CONFIGURATOR`) are also bypassed when +variables such as :envvar:`OTEL_PYTHON_TRACER_CONFIGURATOR`) are also bypassed when :envvar:`OTEL_CONFIG_FILE` is set, because the env-var initialisation path is skipped entirely in favour of the declarative file. Honouring these alongside a config file is tracked separately. @@ -716,22 +715,6 @@ def channel_credential_provider() -> grpc.ChannelCredentials: """ -_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" -""" -.. envvar:: OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED - -The :envvar:`OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED` environment variable allows users to -enable/disable the auto instrumentation for the python logging module. -Default: False - -Note: Logs SDK and its related settings are experimental. - -.. warning:: - - This option is deprecated, instead you should install `opentelemetry-instrumentation-logging`. -""" - - OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" """ .. envvar:: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE @@ -847,7 +830,7 @@ def channel_credential_provider() -> grpc.ChannelCredentials: The :envvar:`OTEL_PYTHON_LOGGER_CONFIGURATOR` environment variable allows users to set a custom Logger Configurator function. -Default: opentelemetry.sdk._logs._internal._default_logger_configurator +Default: opentelemetry.sdk.logs._internal._default_logger_configurator This is an experimental environment variable and the name of this variable and its behavior can change in a non-backwards compatible way. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py new file mode 100644 index 00000000000..e814efbdb59 --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -0,0 +1,26 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.sdk.logs._internal import ( + ConcurrentMultiLogRecordProcessor, + Logger, + LoggerProvider, + LogRecordDroppedAttributesWarning, + LogRecordLimits, + LogRecordProcessor, + ReadableLogRecord, + ReadWriteLogRecord, + SynchronousMultiLogRecordProcessor, +) + +__all__ = [ + "ConcurrentMultiLogRecordProcessor", + "LogRecordDroppedAttributesWarning", + "LogRecordLimits", + "LogRecordProcessor", + "Logger", + "LoggerProvider", + "ReadWriteLogRecord", + "ReadableLogRecord", + "SynchronousMultiLogRecordProcessor", +] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/__init__.py similarity index 77% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/__init__.py index f4aca9c48e7..bd44ea76b6e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/__init__.py @@ -10,14 +10,12 @@ import logging import os import threading -import traceback import warnings -from collections.abc import Callable, Mapping, Sequence +from collections.abc import Callable from dataclasses import dataclass, field from os import environ from threading import Lock from time import time_ns -from types import NoneType from typing import ( # noqa Any, Tuple, @@ -27,10 +25,7 @@ ) from weakref import WeakMethod, WeakSet -from typing_extensions import deprecated - from opentelemetry.attributes import BoundedAttributes -from opentelemetry.context import get_current from opentelemetry.context.context import Context from opentelemetry.logs import Logger as APILogger from opentelemetry.logs import LoggerProvider as APILoggerProvider @@ -38,19 +33,8 @@ LogRecord, NoOpLogger, SeverityNumber, - get_logger, - get_logger_provider, ) from opentelemetry.metrics import MeterProvider, get_meter_provider -from opentelemetry.sdk._logs._internal._exceptions import ( - _copy_log_record_with_exception, - _create_log_record_with_exception, - _set_log_record_exception_attributes, -) -from opentelemetry.sdk._logs._internal._logger_metrics import ( - LoggerMetricsT, - create_logger_metrics, -) from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -62,6 +46,15 @@ from opentelemetry.sdk.environment_variables._internal import ( parse_boolean_environment_variable, ) +from opentelemetry.sdk.logs._internal._exceptions import ( + _copy_log_record_with_exception, + _create_log_record_with_exception, + _set_log_record_exception_attributes, +) +from opentelemetry.sdk.logs._internal._logger_metrics import ( + LoggerMetricsT, + create_logger_metrics, +) from opentelemetry.sdk.resources import ( Resource, _get_process_dependent_resource, @@ -71,10 +64,6 @@ from opentelemetry.sdk.util.instrumentation import ( InstrumentationScope, ) -from opentelemetry.semconv.attributes import ( - code_attributes, - exception_attributes, -) from opentelemetry.trace import ( format_span_id, format_trace_id, @@ -108,13 +97,6 @@ class LogRecordDroppedAttributesWarning(UserWarning): warnings.simplefilter("once", LogRecordDroppedAttributesWarning) -@deprecated( - "Use LogRecordDroppedAttributesWarning. Since logs are not stable yet this WILL be removed in future releases." -) -class LogDroppedAttributesWarning(LogRecordDroppedAttributesWarning): - pass - - class LogRecordLimits: """This class is based on a SpanLimits class in the Tracing module. @@ -220,11 +202,6 @@ def _from_env_if_absent(cls, value: int | None, env_var: str, default: int | Non return value -@deprecated("Use LogRecordLimits. Since logs are not stable yet this WILL be removed in future releases.") -class LogLimits(LogRecordLimits): - pass - - @dataclass(frozen=True) class ReadableLogRecord: """Readable LogRecord should be kept exactly in-sync with ReadWriteLogRecord, only difference is the frozen=True param.""" @@ -386,7 +363,7 @@ def enabled( # pylint: disable=no-self-use,unused-argument @abc.abstractmethod def shutdown(self) -> None: - """Called when a :class:`opentelemetry.sdk._logs.Logger` is shutdown""" + """Called when a :class:`opentelemetry.sdk.logs.Logger` is shutdown""" @abc.abstractmethod def force_flush(self, timeout_millis: int = 30000) -> bool: @@ -566,165 +543,6 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: return True -# skip natural LogRecord attributes -# http://docs.python.org/library/logging.html#logrecord-attributes -_RESERVED_ATTRS = frozenset( - ( - "asctime", - "args", - "created", - "exc_info", - "exc_text", - "filename", - "funcName", - "getMessage", - "message", - "levelname", - "levelno", - "lineno", - "module", - "msecs", - "msg", - "name", - "pathname", - "process", - "processName", - "relativeCreated", - "stack_info", - "thread", - "threadName", - "taskName", - ) -) - - -class LoggingHandler(logging.Handler): - """A handler class which writes logging records, in OTLP format, to - a network destination or file. Supports signals from the `logging` module. - https://docs.python.org/3/library/logging.html - """ - - def __init__( - self, - level: int = logging.NOTSET, - logger_provider: APILoggerProvider | None = None, - ) -> None: - super().__init__(level=level) - self._logger_provider = logger_provider or get_logger_provider() - - warnings.warn( - "`LoggingHandler` in `opentelemetry-sdk` is deprecated. Use the " - "handler from `opentelemetry-instrumentation-logging` instead.", - DeprecationWarning, - ) - - @staticmethod - def _get_attributes(record: logging.LogRecord) -> Attributes: - attributes = {k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS} - - # Add standard code attributes for logs. - attributes[code_attributes.CODE_FILE_PATH] = record.pathname - attributes[code_attributes.CODE_FUNCTION_NAME] = record.funcName - attributes[code_attributes.CODE_LINE_NUMBER] = record.lineno - - if record.exc_info: - exctype, value, tb = record.exc_info - if exctype is not None: - attributes[exception_attributes.EXCEPTION_TYPE] = exctype.__name__ - if value is not None and value.args: - attributes[exception_attributes.EXCEPTION_MESSAGE] = str(value.args[0]) - if tb is not None: - # https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#stacktrace-representation - attributes[exception_attributes.EXCEPTION_STACKTRACE] = "".join( - traceback.format_exception(*record.exc_info) - ) - return attributes - - def _translate(self, record: logging.LogRecord) -> LogRecord: - timestamp = int(record.created * 1e9) - observered_timestamp = time_ns() - attributes = self._get_attributes(record) - severity_number = std_to_otel(record.levelno) - if self.formatter: - body = self.format(record) - else: - # `record.getMessage()` uses `record.msg` as a template to format - # `record.args` into. There is a special case in `record.getMessage()` - # where it will only attempt formatting if args are provided, - # otherwise, it just stringifies `record.msg`. - # - # Since the OTLP body field has a type of 'any' and the logging module - # is sometimes used in such a way that objects incorrectly end up - # set as record.msg, in those cases we would like to bypass - # `record.getMessage()` completely and set the body to the object - # itself instead of its string representation. - # For more background, see: https://github.com/open-telemetry/opentelemetry-python/pull/4216 - if not record.args and not isinstance(record.msg, str): - # if record.msg is not a value we can export, cast it to string - # TODO: https://github.com/open-telemetry/opentelemetry-python/issues/5304 - do something better - # than just casting to a string. - if not isinstance( - record.msg, - ( - NoneType, - bool, - bytes, - int, - float, - str, - Sequence, - Mapping, - ), - ): - body = str(record.msg) - else: - body = record.msg - else: - body = record.getMessage() - - # Map Python log level names to OTel severity text as defined in - # https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#displaying-severity - # Python "WARNING" -> OTel "WARN" (see #3548) - # Python "CRITICAL" -> OTel "FATAL" (see #4984) - _python_to_otel_severity_text = { - "WARNING": "WARN", - "CRITICAL": "FATAL", - } - level_name = _python_to_otel_severity_text.get(record.levelname, record.levelname) - - return LogRecord( - timestamp=timestamp, - observed_timestamp=observered_timestamp, - context=get_current() or None, - severity_text=level_name, - severity_number=severity_number, - body=body, - attributes=attributes, - ) - - def emit(self, record: logging.LogRecord) -> None: - """ - Emit a record. Skip emitting if logger is NoOp. - - The record is translated to OTel format, and then sent across the pipeline. - """ - logger = get_logger(record.name, logger_provider=self._logger_provider) - if not isinstance(logger, NoOpLogger): - logger.emit(self._translate(record)) - - def flush(self) -> None: - """ - Flushes the logging output. Skip flushing if logging_provider has no force_flush method. - """ - if hasattr(self._logger_provider, "force_flush") and callable( - self._logger_provider.force_flush # type: ignore[reportAttributeAccessIssue] - ): - # This is done in a separate thread to avoid a potential deadlock, for - # details see https://github.com/open-telemetry/opentelemetry-python/pull/4636. - thread = threading.Thread(target=self._logger_provider.force_flush) # type: ignore[reportAttributeAccessIssue] - thread.start() - - @dataclass class _LoggerConfig: is_enabled: bool = True @@ -1030,63 +848,3 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: False otherwise. """ return self._multi_log_record_processor.force_flush(timeout_millis) - - -_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, -} - - -def std_to_otel(levelno: int) -> SeverityNumber: - """ - Map python log levelno as defined in https://docs.python.org/3/library/logging.html#logging-levels - to OTel log severity number as defined here: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-severitynumber - """ - if levelno < 10: - return SeverityNumber.UNSPECIFIED - if levelno > 53: - return SeverityNumber.FATAL4 - return _STD_TO_OTEL[levelno] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/_exceptions.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/_exceptions.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/_exceptions.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/_exceptions.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/_logger_metrics.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/_logger_metrics.py similarity index 100% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/_logger_metrics.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/_logger_metrics.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/__init__.py similarity index 95% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/__init__.py index 55921e043db..ae5903ad7f9 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/__init__.py @@ -11,8 +11,6 @@ from os import environ, linesep from typing import IO -from typing_extensions import deprecated - from opentelemetry.context import ( _ON_EMIT_RECURSION_COUNT_KEY, _SUPPRESS_INSTRUMENTATION_KEY, @@ -23,11 +21,6 @@ set_value, ) from opentelemetry.metrics import MeterProvider, get_meter_provider -from opentelemetry.sdk._logs import ( - LogRecordProcessor, - ReadableLogRecord, - ReadWriteLogRecord, -) from opentelemetry.sdk._shared_internal import ( BatchProcessor, DuplicateFilter, @@ -45,6 +38,11 @@ from opentelemetry.sdk.environment_variables._internal import ( parse_boolean_environment_variable, ) +from opentelemetry.sdk.logs import ( + LogRecordProcessor, + ReadableLogRecord, + ReadWriteLogRecord, +) from opentelemetry.sdk.resources import Resource from opentelemetry.semconv._incubating.attributes.otel_attributes import ( OtelComponentTypeValues, @@ -67,19 +65,13 @@ class LogRecordExportResult(enum.Enum): FAILURE = 1 -@deprecated("Use LogRecordExportResult. Since logs are not stable yet this WILL be removed in future releases.") -class LogExportResult(enum.Enum): - SUCCESS = 0 - FAILURE = 1 - - class LogRecordExporter(abc.ABC): """Interface for exporting logs. Interface to be implemented by services that want to export logs received in their own format. - To export data this MUST be registered to the :class:`opentelemetry.sdk._logs.Logger` + To export data this MUST be registered to the :class:`opentelemetry.sdk.logs.Logger` using a log processor. Important @@ -131,11 +123,6 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: """ -@deprecated("Use LogRecordExporter. Since logs are not stable yet this WILL be removed in future releases.") -class LogExporter(LogRecordExporter): - pass - - class ConsoleLogRecordExporter(LogRecordExporter): """Implementation of :class:`LogRecordExporter` that prints log records to the console. @@ -165,11 +152,6 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: return True -@deprecated("Use ConsoleLogRecordExporter. Since logs are not stable yet this WILL be removed in future releases.") -class ConsoleLogExporter(ConsoleLogRecordExporter): - pass - - class SimpleLogRecordProcessor(LogRecordProcessor): """Implementation of LogRecordProcessor that exports logs synchronously. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/in_memory_log_exporter.py similarity index 79% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/in_memory_log_exporter.py index 580fd605098..15f08ffaa28 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/in_memory_log_exporter.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/_internal/export/in_memory_log_exporter.py @@ -4,10 +4,8 @@ import collections.abc import threading -from typing_extensions import deprecated - -from opentelemetry.sdk._logs import ReadableLogRecord -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import ReadableLogRecord +from opentelemetry.sdk.logs.export import ( LogRecordExporter, LogRecordExportResult, ) @@ -46,8 +44,3 @@ def shutdown(self) -> None: def force_flush(self, timeout_millis: int = 30000) -> bool: return True - - -@deprecated("Use InMemoryLogRecordExporter. Since logs are not stable yet this WILL be removed in future releases.") -class InMemoryLogExporter(InMemoryLogRecordExporter): - pass diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py similarity index 62% rename from opentelemetry-sdk/src/opentelemetry/sdk/_logs/export/__init__.py rename to opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py index 310a19425ab..66041867d6e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py @@ -1,31 +1,23 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.sdk._logs._internal.export import ( +from opentelemetry.sdk.logs._internal.export import ( BatchLogRecordProcessor, - ConsoleLogExporter, ConsoleLogRecordExporter, - LogExporter, - LogExportResult, LogRecordExporter, LogRecordExportResult, SimpleLogRecordProcessor, ) # The point module is not in the export directory to avoid a circular import. -from opentelemetry.sdk._logs._internal.export.in_memory_log_exporter import ( - InMemoryLogExporter, +from opentelemetry.sdk.logs._internal.export.in_memory_log_exporter import ( InMemoryLogRecordExporter, ) __all__ = [ "BatchLogRecordProcessor", - "ConsoleLogExporter", "ConsoleLogRecordExporter", - "InMemoryLogExporter", "InMemoryLogRecordExporter", - "LogExportResult", - "LogExporter", "LogRecordExportResult", "LogRecordExporter", "SimpleLogRecordProcessor", diff --git a/opentelemetry-sdk/tests/logs/scripts/logger_provider_resource_after_fork.py b/opentelemetry-sdk/tests/logs/scripts/logger_provider_resource_after_fork.py index df227a37b9e..eb9871e701f 100644 --- a/opentelemetry-sdk/tests/logs/scripts/logger_provider_resource_after_fork.py +++ b/opentelemetry-sdk/tests/logs/scripts/logger_provider_resource_after_fork.py @@ -5,8 +5,8 @@ import os from opentelemetry.logs import LogRecord -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( InMemoryLogRecordExporter, SimpleLogRecordProcessor, ) diff --git a/opentelemetry-sdk/tests/logs/test_deprecated_logs_module.py b/opentelemetry-sdk/tests/logs/test_deprecated_logs_module.py new file mode 100644 index 00000000000..7efdf14a21b --- /dev/null +++ b/opentelemetry-sdk/tests/logs/test_deprecated_logs_module.py @@ -0,0 +1,126 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# pylint: disable=protected-access +from __future__ import annotations + +import importlib +import sys +import unittest +import warnings +from unittest.mock import Mock, patch + +import opentelemetry.sdk.logs +import opentelemetry.sdk.logs._internal +import opentelemetry.sdk.logs._internal._exceptions +import opentelemetry.sdk.logs._internal._logger_metrics +import opentelemetry.sdk.logs._internal.export +import opentelemetry.sdk.logs._internal.export.in_memory_log_exporter +import opentelemetry.sdk.logs.export + +_DEPRECATED_MODULES = ( + "opentelemetry.sdk._logs", + "opentelemetry.sdk._logs.export", + "opentelemetry.sdk._logs._internal", + "opentelemetry.sdk._logs._internal._exceptions", + "opentelemetry.sdk._logs._internal._logger_metrics", + "opentelemetry.sdk._logs._internal.export", + "opentelemetry.sdk._logs._internal.export.in_memory_log_exporter", +) + + +def _unload_deprecated_modules() -> None: + for name in _DEPRECATED_MODULES: + sys.modules.pop(name, None) + sys.modules["opentelemetry.sdk"].__dict__.pop("_logs", None) + + +class TestDeprecatedLogsModule(unittest.TestCase): + def setUp(self) -> None: + _unload_deprecated_modules() + + def test_import_emits_deprecation_warning(self) -> None: + for name in _DEPRECATED_MODULES: + with self.subTest(module=name): + _unload_deprecated_modules() + with self.assertWarns(DeprecationWarning) as context: + importlib.import_module(name) + message = str(context.warning) + self.assertIn("opentelemetry.sdk.logs", message) + self.assertIn("will be removed in a future release", message) + + def test_deprecation_warning_points_at_importer(self) -> None: + # pylint: disable=import-outside-toplevel,unused-import,redefined-outer-name,import-error + # Literal import statements are required here since importlib.import_module + # would attribute the warning to importlib itself. + with self.subTest(statement="import opentelemetry.sdk._logs"): + _unload_deprecated_modules() + with self.assertWarns(DeprecationWarning) as context: + import opentelemetry.sdk._logs # noqa: F401, PLC0415 + self.assertEqual(context.filename, __file__) + + with self.subTest(statement="from opentelemetry.sdk._logs import ..."): + _unload_deprecated_modules() + with self.assertWarns(DeprecationWarning) as context: + from opentelemetry.sdk._logs import LoggerProvider # noqa: F401, PLC0415 + self.assertEqual(context.filename, __file__) + + with self.subTest(statement="from opentelemetry.sdk._logs.export import ..."): + _unload_deprecated_modules() + with self.assertWarns(DeprecationWarning) as context: + from opentelemetry.sdk._logs.export import ( # noqa: F401, PLC0415 + BatchLogRecordProcessor, + ) + self.assertEqual(context.filename, __file__) + + def test_new_module_does_not_emit_deprecation_warning(self) -> None: + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + importlib.reload(opentelemetry.sdk.logs) + importlib.reload(opentelemetry.sdk.logs.export) + self.assertEqual( + [w for w in caught if issubclass(w.category, DeprecationWarning)], + [], + ) + self.assertNotIn("opentelemetry.sdk._logs", sys.modules) + + def test_reexports_are_identical(self) -> None: + with self.assertWarns(DeprecationWarning): + deprecated = importlib.import_module("opentelemetry.sdk._logs") + + self.assertEqual(deprecated.__all__, opentelemetry.sdk.logs.__all__) + for name in opentelemetry.sdk.logs.__all__: + with self.subTest(name=name): + self.assertIs(getattr(deprecated, name), getattr(opentelemetry.sdk.logs, name)) + + def test_submodules_are_aliased(self) -> None: + with self.assertWarns(DeprecationWarning): + importlib.import_module("opentelemetry.sdk._logs") + + aliases = { + "opentelemetry.sdk._logs.export": opentelemetry.sdk.logs.export, + "opentelemetry.sdk._logs._internal": opentelemetry.sdk.logs._internal, + "opentelemetry.sdk._logs._internal._exceptions": opentelemetry.sdk.logs._internal._exceptions, + "opentelemetry.sdk._logs._internal._logger_metrics": opentelemetry.sdk.logs._internal._logger_metrics, + "opentelemetry.sdk._logs._internal.export": opentelemetry.sdk.logs._internal.export, + "opentelemetry.sdk._logs._internal.export.in_memory_log_exporter": ( + opentelemetry.sdk.logs._internal.export.in_memory_log_exporter + ), + } + for old_name, new_module in aliases.items(): + with self.subTest(module=old_name): + self.assertIs(importlib.import_module(old_name), new_module) + + def test_patching_deprecated_path_patches_new_module(self) -> None: + with self.assertWarns(DeprecationWarning): + importlib.import_module("opentelemetry.sdk._logs") + + mock_create_logger_metrics = Mock() + with patch( + "opentelemetry.sdk._logs._internal.create_logger_metrics", + mock_create_logger_metrics, + ): + self.assertIs( + opentelemetry.sdk.logs._internal.create_logger_metrics, + mock_create_logger_metrics, + ) diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 42ca5418df2..b467cc54bef 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -4,7 +4,6 @@ # pylint: disable=protected-access,too-many-lines import logging import os -import sys import threading import time import unittest @@ -15,19 +14,23 @@ from unittest import mock from unittest.mock import Mock, patch -from pytest import mark - from opentelemetry.logs import LogRecord, SeverityNumber from opentelemetry.metrics import NoOpMeterProvider from opentelemetry.sdk import trace -from opentelemetry.sdk._logs import ( +from opentelemetry.sdk.environment_variables import ( + OTEL_BLRP_EXPORT_TIMEOUT, + OTEL_BLRP_MAX_EXPORT_BATCH_SIZE, + OTEL_BLRP_MAX_QUEUE_SIZE, + OTEL_BLRP_SCHEDULE_DELAY, + OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, +) +from opentelemetry.sdk.logs import ( LoggerProvider, - LoggingHandler, ReadableLogRecord, ReadWriteLogRecord, ) -from opentelemetry.sdk._logs._internal.export import _logger -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs._internal.export import _logger +from opentelemetry.sdk.logs.export import ( BatchLogRecordProcessor, ConsoleLogRecordExporter, InMemoryLogRecordExporter, @@ -35,13 +38,6 @@ LogRecordExportResult, SimpleLogRecordProcessor, ) -from opentelemetry.sdk.environment_variables import ( - OTEL_BLRP_EXPORT_TIMEOUT, - OTEL_BLRP_MAX_EXPORT_BATCH_SIZE, - OTEL_BLRP_MAX_QUEUE_SIZE, - OTEL_BLRP_SCHEDULE_DELAY, - OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, -) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import InMemoryMetricReader from opentelemetry.sdk.resources import Resource as SDKResource @@ -61,11 +57,9 @@ class TestSimpleLogRecordProcessor(unittest.TestCase): - @mark.skipif( - (3, 13, 0) <= sys.version_info <= (3, 13, 5), - reason="This will fail on 3.13.5 due to https://github.com/python/cpython/pull/131812 which prevents recursive log messages but was rolled back in 3.13.6.", - ) def test_simple_log_record_processor_doesnt_enter_recursive_loop(self): + logger_provider = LoggerProvider() + class Exporter(LogRecordExporter): def shutdown(self): pass @@ -74,36 +68,25 @@ def force_flush(self, timeout_millis: int = 10_000) -> bool: return True def export(self, batch: Sequence[ReadableLogRecord]): - logger = logging.getLogger("any logger..") - logger.warning("Something happened.") + # Emitting from within the exporter sends the record back to + # SimpleLogRecordProcessor.on_emit. + logger_provider.get_logger("any logger..").emit(body="Something happened.") exporter = Exporter() - logger_provider = LoggerProvider() logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) - root_logger = logging.getLogger() - # Add the OTLP handler to the root logger like is done in auto instrumentation. - # This causes logs generated from within SimpleLogRecordProcessor.on_emit (such as the above log in export) - # to be sent back to SimpleLogRecordProcessor.on_emit - handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider) - root_logger.addHandler(handler) - propagate_false_logger = logging.getLogger("opentelemetry.sdk._logs._internal.export.propagate.false") + propagate_false_logger = logging.getLogger("opentelemetry.sdk.logs._internal.export.propagate.false") # This would cause a max recursion depth exceeded error.. - try: - with self.assertLogs(propagate_false_logger) as cm: - root_logger.warning("hello!") - assert "SimpleLogRecordProcessor.on_emit has entered a recursive loop" in cm.output[0] - finally: - root_logger.removeHandler(handler) + with self.assertLogs(propagate_false_logger) as cm: + logger_provider.get_logger("root").emit(body="hello!") + assert "SimpleLogRecordProcessor.on_emit has entered a recursive loop" in cm.output[0] @patch.dict("os.environ", {OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED: "true"}) - @mark.skipif( - (3, 13, 0) <= sys.version_info <= (3, 13, 5), - reason="This will fail on 3.13.5 due to https://github.com/python/cpython/pull/131812 which prevents the recursion from being detected.", - ) def test_metrics_recursive_loop(self): metric_reader = InMemoryMetricReader() meter_provider = MeterProvider(metric_readers=[metric_reader]) + logger_provider = LoggerProvider() + class Exporter(LogRecordExporter): def shutdown(self): pass @@ -112,22 +95,14 @@ def force_flush(self, timeout_millis: int = 10_000) -> bool: return True def export(self, batch: Sequence[ReadableLogRecord]): - logger = logging.getLogger("any logger..") - logger.warning("Something happened.") + logger_provider.get_logger("any logger..").emit(body="Something happened.") exporter = Exporter() - logger_provider = LoggerProvider() logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter, meter_provider=meter_provider)) - root_logger = logging.getLogger() - handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider) - root_logger.addHandler(handler) - propagate_false_logger = logging.getLogger("opentelemetry.sdk._logs._internal.export.propagate.false") - try: - with self.assertLogs(propagate_false_logger) as cm: - root_logger.warning("hello!") - assert "SimpleLogRecordProcessor.on_emit has entered a recursive loop" in cm.output[0] - finally: - root_logger.removeHandler(handler) + propagate_false_logger = logging.getLogger("opentelemetry.sdk.logs._internal.export.propagate.false") + with self.assertLogs(propagate_false_logger) as cm: + logger_provider.get_logger("root").emit(body="hello!") + assert "SimpleLogRecordProcessor.on_emit has entered a recursive loop" in cm.output[0] metrics_data = metric_reader.get_metrics_data() scope_metrics = metrics_data.resource_metrics[0].scope_metrics[0] @@ -155,11 +130,13 @@ def test_simple_log_record_processor_default_level(self): logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) - logger = logging.getLogger("default_level") - logger.propagate = False - logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + logger = logger_provider.get_logger("default_level") - logger.warning("Something is wrong") + logger.emit( + body="Something is wrong", + severity_text="WARN", + severity_number=SeverityNumber.WARN, + ) finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) warning_log_record = finished_logs[0] @@ -168,49 +145,19 @@ def test_simple_log_record_processor_default_level(self): self.assertEqual(warning_log_record.log_record.severity_number, SeverityNumber.WARN) self.assertEqual(finished_logs[0].instrumentation_scope.name, "default_level") - def test_simple_log_record_processor_custom_level(self): - exporter = InMemoryLogRecordExporter() - logger_provider = LoggerProvider() - - logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) - - logger = logging.getLogger("custom_level") - logger.propagate = False - logger.setLevel(logging.ERROR) - logger.addHandler(LoggingHandler(logger_provider=logger_provider)) - - logger.warning("Warning message") - logger.debug("Debug message") - logger.error("Error message") - logger.critical("Critical message") - finished_logs = exporter.get_finished_logs() - # Make sure only level >= logging.CRITICAL logs are recorded - self.assertEqual(len(finished_logs), 2) - critical_log_record = finished_logs[0] - fatal_log_record = finished_logs[1] - self.assertEqual(critical_log_record.log_record.body, "Error message") - self.assertEqual(critical_log_record.log_record.severity_text, "ERROR") - self.assertEqual( - critical_log_record.log_record.severity_number, - SeverityNumber.ERROR, - ) - self.assertEqual(fatal_log_record.log_record.body, "Critical message") - self.assertEqual(fatal_log_record.log_record.severity_text, "FATAL") - self.assertEqual(fatal_log_record.log_record.severity_number, SeverityNumber.FATAL) - self.assertEqual(finished_logs[0].instrumentation_scope.name, "custom_level") - self.assertEqual(finished_logs[1].instrumentation_scope.name, "custom_level") - def test_simple_log_record_processor_trace_correlation(self): exporter = InMemoryLogRecordExporter() logger_provider = LoggerProvider() logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) - logger = logging.getLogger("trace_correlation") - logger.propagate = False - logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + logger = logger_provider.get_logger("trace_correlation") - logger.warning("Warning message") + logger.emit( + body="Warning message", + severity_text="WARN", + severity_number=SeverityNumber.WARN, + ) finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) sdk_record = finished_logs[0] @@ -225,7 +172,11 @@ def test_simple_log_record_processor_trace_correlation(self): tracer = trace.TracerProvider().get_tracer(__name__) with tracer.start_as_current_span("test") as span: - logger.critical("Critical message within span") + logger.emit( + body="Critical message within span", + severity_text="FATAL", + severity_number=SeverityNumber.FATAL, + ) finished_logs = exporter.get_finished_logs() sdk_record = finished_logs[0] @@ -247,11 +198,13 @@ def test_simple_log_record_processor_shutdown(self): logger_provider.add_log_record_processor(SimpleLogRecordProcessor(exporter)) - logger = logging.getLogger("shutdown") - logger.propagate = False - logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + logger = logger_provider.get_logger("shutdown") - logger.warning("Something is wrong") + logger.emit( + body="Something is wrong", + severity_text="WARN", + severity_number=SeverityNumber.WARN, + ) finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 1) warning_log_record = finished_logs[0] @@ -261,133 +214,10 @@ def test_simple_log_record_processor_shutdown(self): self.assertEqual(finished_logs[0].instrumentation_scope.name, "shutdown") exporter.clear() logger_provider.shutdown() - logger.warning("Log after shutdown") + logger.emit(body="Log after shutdown", severity_number=SeverityNumber.WARN) finished_logs = exporter.get_finished_logs() self.assertEqual(len(finished_logs), 0) - def test_simple_log_record_processor_different_msg_types(self): - exporter = InMemoryLogRecordExporter() - log_record_processor = BatchLogRecordProcessor(exporter) - - provider = LoggerProvider() - provider.add_log_record_processor(log_record_processor) - - logger = logging.getLogger("different_msg_types") - logger.addHandler(LoggingHandler(logger_provider=provider)) - - logger.warning("warning message: %s", "possible upcoming heatwave") - logger.error("Very high rise in temperatures across the globe") - logger.critical("Temperature hits high 420 C in Hyderabad") - logger.warning(["list", "of", "strings"]) - logger.error({"key": "value"}) - log_record_processor.shutdown() - - finished_logs = exporter.get_finished_logs() - expected = [ - ("warning message: possible upcoming heatwave", "WARN"), - ("Very high rise in temperatures across the globe", "ERROR"), - ( - "Temperature hits high 420 C in Hyderabad", - "FATAL", - ), - (["list", "of", "strings"], "WARN"), - ({"key": "value"}, "ERROR"), - ] - emitted = [(item.log_record.body, item.log_record.severity_text) for item in finished_logs] - self.assertEqual(expected, emitted) - for item in finished_logs: - self.assertEqual(item.instrumentation_scope.name, "different_msg_types") - - def test_simple_log_record_processor_custom_single_obj(self): - """ - Tests that special-case handling for logging a single non-string object - is correctly applied. - """ - exporter = InMemoryLogRecordExporter() - log_record_processor = BatchLogRecordProcessor(exporter) - - provider = LoggerProvider() - provider.add_log_record_processor(log_record_processor) - - logger = logging.getLogger("single_obj") - logger.addHandler(LoggingHandler(logger_provider=provider)) - - # NOTE: the behaviour of `record.getMessage` is detailed in the - # `logging.Logger.debug` documentation: - # > The msg is the message format string, and the args are the arguments - # > which are merged into msg using the string formatting operator. [...] - # > No % formatting operation is performed on msg when no args are supplied. - - # This test uses the presence of '%s' in the first arg to determine if - # formatting was applied - - # string msg with no args - getMessage bypasses formatting and sets the string directly - logger.warning("a string with a percent-s: %s") # pylint: disable=logging-too-few-args - # string msg with args - getMessage formats args into the msg - logger.warning("a string with a percent-s: %s", "and arg") - # non-string msg with args - getMessage stringifies msg and formats args into it - logger.warning(["a non-string with a percent-s", "%s"], "and arg") - # non-string msg with no args: - # - normally getMessage would stringify the object and bypass formatting - # - SPECIAL CASE: bypass stringification as well to keep the raw object - logger.warning(["a non-string with a percent-s", "%s"]) - log_record_processor.shutdown() - - finished_logs = exporter.get_finished_logs() - expected = [ - ("a string with a percent-s: %s"), - ("a string with a percent-s: and arg"), - ("['a non-string with a percent-s', 'and arg']"), - (["a non-string with a percent-s", "%s"]), - ] - for emitted, expected in zip(finished_logs, expected): - self.assertEqual(emitted.log_record.body, expected) - self.assertEqual(emitted.instrumentation_scope.name, "single_obj") - - def test_simple_log_record_processor_different_msg_types_with_formatter( - self, - ): - exporter = InMemoryLogRecordExporter() - log_record_processor = BatchLogRecordProcessor(exporter) - - provider = LoggerProvider() - provider.add_log_record_processor(log_record_processor) - - logger = logging.getLogger("different_msg_types") - handler = LoggingHandler(logger_provider=provider) - handler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s")) - logger.addHandler(handler) - - logger.warning("warning message: %s", "possible upcoming heatwave") - logger.error("Very high rise in temperatures across the globe") - logger.critical("Temperature hits high 420 C in Hyderabad") - logger.warning(["list", "of", "strings"]) - logger.error({"key": "value"}) - log_record_processor.shutdown() - - finished_logs = exporter.get_finished_logs() - expected = [ - ( - "different_msg_types - WARNING - warning message: possible upcoming heatwave", - "WARN", - ), - ( - "different_msg_types - ERROR - Very high rise in temperatures across the globe", - "ERROR", - ), - ( - "different_msg_types - CRITICAL - Temperature hits high 420 C in Hyderabad", - "FATAL", - ), - ( - "different_msg_types - WARNING - ['list', 'of', 'strings']", - "WARN", - ), - ("different_msg_types - ERROR - {'key': 'value'}", "ERROR"), - ] - emitted = [(item.log_record.body, item.log_record.severity_text) for item in finished_logs] - self.assertEqual(expected, emitted) - @patch.dict("os.environ", {OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED: "true"}) def test_metrics(self): # pylint: disable=too-many-locals metric_reader = InMemoryMetricReader() @@ -479,11 +309,9 @@ def test_emit_call_log_record(self): provider = LoggerProvider() provider.add_log_record_processor(log_record_processor) - logger = logging.getLogger("emit_call") - logger.propagate = False - logger.addHandler(LoggingHandler(logger_provider=provider)) + logger = provider.get_logger("emit_call") - logger.error("error") + logger.emit(body="error", severity_number=SeverityNumber.ERROR) self.assertEqual(log_record_processor.on_emit.call_count, 1) log_record_processor.shutdown() diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py deleted file mode 100644 index b00f1690794..00000000000 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ /dev/null @@ -1,525 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -import logging -import os -import unittest -from unittest.mock import Mock, patch - -from opentelemetry.attributes import BoundedAttributes -from opentelemetry.logs import NoOpLoggerProvider, SeverityNumber -from opentelemetry.logs import get_logger as APIGetLogger -from opentelemetry.sdk import trace -from opentelemetry.sdk._logs import ( - LoggerProvider, - LoggingHandler, - LogRecordProcessor, - ReadableLogRecord, -) -from opentelemetry.sdk.environment_variables import OTEL_ATTRIBUTE_COUNT_LIMIT -from opentelemetry.semconv.attributes import ( - code_attributes, - exception_attributes, -) -from opentelemetry.trace import ( - INVALID_SPAN_CONTEXT, - set_span_in_context, -) - - -# pylint: disable=too-many-public-methods -class TestLoggingHandler(unittest.TestCase): - def test_warns_when_used(self): - with self.assertWarnsRegex( - DeprecationWarning, - "`LoggingHandler` in `opentelemetry-sdk` is deprecated", - ): - LoggingHandler() - - def test_handler_default_log_level(self): - processor, logger, handler = set_up_test_logging(logging.NOTSET) - - # Make sure debug messages are ignored by default - logger.debug("Debug message") - assert processor.emit_count() == 0 - - # Assert emit gets called for warning message - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message") - self.assertEqual(processor.emit_count(), 1) - - logger.removeHandler(handler) - - def test_handler_custom_log_level(self): - processor, logger, handler = set_up_test_logging(logging.ERROR) - - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message test custom log level") - # Make sure any log with level < ERROR is ignored - assert processor.emit_count() == 0 - - with self.assertLogs(level=logging.ERROR): - logger.error("Mumbai, we have a major problem") - with self.assertLogs(level=logging.CRITICAL): - logger.critical("No Time For Caution") - self.assertEqual(processor.emit_count(), 2) - - logger.removeHandler(handler) - - # pylint: disable=protected-access - def test_log_record_emit_noop(self): - noop_logger_provder = NoOpLoggerProvider() - logger_mock = APIGetLogger(__name__, logger_provider=noop_logger_provder) - logger = logging.getLogger(__name__) - handler_mock = Mock(spec=LoggingHandler) - handler_mock._logger = logger_mock - handler_mock.level = logging.WARNING - logger.addHandler(handler_mock) - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message") - - logger.removeHandler(handler_mock) - - def test_log_flush_noop(self): - no_op_logger_provider = NoOpLoggerProvider() - - logger = logging.getLogger("foo") - handler = LoggingHandler(level=logging.NOTSET, logger_provider=no_op_logger_provider) - logger.addHandler(handler) - - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message") - - # the LoggingHandler flush method will call the force_flush method of LoggerProvider in - # a separate thread if present. NoOpLoggerProvider is not supposed to have that - with patch("opentelemetry.sdk._logs._internal.threading") as threading_mock: - logger.handlers[0].flush() - - threading_mock.Thread.assert_not_called() - - logger.removeHandler(handler) - - def test_log_record_no_span_context(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - - # Assert emit gets called for warning message - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message") - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertEqual(record.log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id) - self.assertEqual(record.log_record.span_id, INVALID_SPAN_CONTEXT.span_id) - self.assertEqual( - record.log_record.trace_flags, - INVALID_SPAN_CONTEXT.trace_flags, - ) - - logger.removeHandler(handler) - - def test_log_record_observed_timestamp(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message") - - record = processor.get_log_record(0) - self.assertIsNotNone(record.log_record.observed_timestamp) - - logger.removeHandler(handler) - - def test_log_record_user_attributes(self): - """Attributes can be injected into logs by adding them to the ReadWriteLogRecord""" - processor, logger, handler = set_up_test_logging(logging.WARNING) - - # Assert emit gets called for warning message - with self.assertLogs(level=logging.WARNING): - logger.warning("Warning message", extra={"http.status_code": 200}) - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertEqual(len(record.log_record.attributes), 4) - self.assertEqual(record.log_record.attributes["http.status_code"], 200) - self.assertTrue(record.log_record.attributes[code_attributes.CODE_FILE_PATH].endswith("test_handler.py")) - self.assertEqual( - record.log_record.attributes[code_attributes.CODE_FUNCTION_NAME], - "test_log_record_user_attributes", - ) - # The line of the log statement is not a constant (changing tests may change that), - # so only check that the attribute is present. - self.assertTrue(code_attributes.CODE_LINE_NUMBER in record.log_record.attributes) - self.assertTrue(isinstance(record.log_record.attributes, BoundedAttributes)) - - logger.removeHandler(handler) - - def test_log_record_exception(self): - """Exception information will be included in attributes""" - processor, logger, handler = set_up_test_logging(logging.ERROR) - - try: - raise ZeroDivisionError("division by zero") - except ZeroDivisionError: - with self.assertLogs(level=logging.ERROR): - logger.exception("Zero Division Error") - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertTrue(isinstance(record.log_record.body, str)) - self.assertEqual(record.log_record.body, "Zero Division Error") - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], - ZeroDivisionError.__name__, - ) - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], - "division by zero", - ) - stack_trace = record.log_record.attributes[exception_attributes.EXCEPTION_STACKTRACE] - self.assertIsInstance(stack_trace, str) - self.assertTrue("Traceback" in stack_trace) - self.assertTrue("ZeroDivisionError" in stack_trace) - self.assertTrue("division by zero" in stack_trace) - self.assertTrue(__file__ in stack_trace) - - logger.removeHandler(handler) - - def test_log_record_recursive_exception(self): - """Exception information will be included in attributes even though it is recursive""" - processor, logger, handler = set_up_test_logging(logging.ERROR) - - try: - raise ZeroDivisionError(ZeroDivisionError(ZeroDivisionError("division by zero"))) - except ZeroDivisionError: - with self.assertLogs(level=logging.ERROR): - logger.exception("Zero Division Error") - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertEqual(record.log_record.body, "Zero Division Error") - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], - ZeroDivisionError.__name__, - ) - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], - "division by zero", - ) - stack_trace = record.log_record.attributes[exception_attributes.EXCEPTION_STACKTRACE] - self.assertIsInstance(stack_trace, str) - self.assertTrue("Traceback" in stack_trace) - self.assertTrue("ZeroDivisionError" in stack_trace) - self.assertTrue("division by zero" in stack_trace) - self.assertTrue(__file__ in stack_trace) - - logger.removeHandler(handler) - - def test_log_exc_info_false(self): - """Exception information will not be included in attributes""" - processor, logger, handler = set_up_test_logging(logging.NOTSET) - - try: - raise ZeroDivisionError("division by zero") - except ZeroDivisionError: - with self.assertLogs(level=logging.ERROR): - logger.error("Zero Division Error", exc_info=False) - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertEqual(record.log_record.body, "Zero Division Error") - self.assertNotIn( - exception_attributes.EXCEPTION_TYPE, - record.log_record.attributes, - ) - self.assertNotIn( - exception_attributes.EXCEPTION_MESSAGE, - record.log_record.attributes, - ) - self.assertNotIn( - exception_attributes.EXCEPTION_STACKTRACE, - record.log_record.attributes, - ) - - logger.removeHandler(handler) - - def test_log_record_exception_with_object_payload(self): - processor, logger, handler = set_up_test_logging(logging.ERROR) - - class CustomException(Exception): - def __str__(self): - return "CustomException stringified" - - try: - raise CustomException("CustomException message") - except CustomException as exception: - with self.assertLogs(level=logging.ERROR): - logger.exception(exception) - - record = processor.get_log_record(0) - - self.assertIsNotNone(record) - self.assertTrue(isinstance(record.log_record.body, str)) - self.assertEqual(record.log_record.body, "CustomException stringified") - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_TYPE], - CustomException.__name__, - ) - self.assertEqual( - record.log_record.attributes[exception_attributes.EXCEPTION_MESSAGE], - "CustomException message", - ) - stack_trace = record.log_record.attributes[exception_attributes.EXCEPTION_STACKTRACE] - self.assertIsInstance(stack_trace, str) - self.assertTrue("Traceback" in stack_trace) - self.assertTrue("CustomException" in stack_trace) - self.assertTrue(__file__ in stack_trace) - - logger.removeHandler(handler) - - def test_log_record_trace_correlation(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - - tracer = trace.TracerProvider().get_tracer(__name__) - with tracer.start_as_current_span("test") as span: - mock_context = set_span_in_context(span) - - with patch( - "opentelemetry.sdk._logs._internal.get_current", - return_value=mock_context, - ): - with self.assertLogs(level=logging.CRITICAL): - logger.critical("Critical message within span") - - record = processor.get_log_record(0) - - self.assertEqual( - record.log_record.body, - "Critical message within span", - ) - self.assertEqual(record.log_record.severity_text, "FATAL") - self.assertEqual( - record.log_record.severity_number, - SeverityNumber.FATAL, - ) - self.assertEqual(record.log_record.context, mock_context) - span_context = span.get_span_context() - self.assertEqual(record.log_record.trace_id, span_context.trace_id) - self.assertEqual(record.log_record.span_id, span_context.span_id) - self.assertEqual( - record.log_record.trace_flags, - span_context.trace_flags, - ) - - logger.removeHandler(handler) - - def test_log_record_trace_correlation_deprecated(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - - tracer = trace.TracerProvider().get_tracer(__name__) - with tracer.start_as_current_span("test") as span: - with self.assertLogs(level=logging.CRITICAL): - logger.critical("Critical message within span") - - record = processor.get_log_record(0) - - self.assertEqual(record.log_record.body, "Critical message within span") - self.assertEqual(record.log_record.severity_text, "FATAL") - self.assertEqual(record.log_record.severity_number, SeverityNumber.FATAL) - span_context = span.get_span_context() - self.assertEqual(record.log_record.trace_id, span_context.trace_id) - self.assertEqual(record.log_record.span_id, span_context.span_id) - self.assertEqual(record.log_record.trace_flags, span_context.trace_flags) - - logger.removeHandler(handler) - - def test_warning_without_formatter(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - logger.warning("Test message") - - record = processor.get_log_record(0) - self.assertEqual(record.log_record.body, "Test message") - - logger.removeHandler(handler) - - def test_exception_without_formatter(self): - processor, logger, handler = set_up_test_logging(logging.WARNING) - logger.exception("Test exception") - - record = processor.get_log_record(0) - self.assertEqual(record.log_record.body, "Test exception") - - logger.removeHandler(handler) - - def test_warning_with_formatter(self): - processor, logger, handler = set_up_test_logging( - logging.WARNING, - formatter=logging.Formatter("%(name)s - %(levelname)s - %(message)s"), - ) - logger.warning("Test message") - - record = processor.get_log_record(0) - self.assertEqual(record.log_record.body, "foo - WARNING - Test message") - - logger.removeHandler(handler) - - def test_log_body_is_always_string_with_formatter(self): - processor, logger, handler = set_up_test_logging( - logging.WARNING, - formatter=logging.Formatter("%(name)s - %(levelname)s - %(message)s"), - ) - logger.warning(["something", "of", "note"]) - - record = processor.get_log_record(0) - self.assertIsInstance(record.log_record.body, str) - - logger.removeHandler(handler) - - @patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"}) - def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error( - self, - ): - processor, logger, handler = set_up_test_logging(logging.NOTSET, root_logger=True) - logger.warning("hello") - - self.assertEqual(processor.emit_count(), 0) - - logger.removeHandler(handler) - - @patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "3"}) - def test_otel_attribute_count_limit_respected_in_logging_handler(self): - """Test that OTEL_ATTRIBUTE_COUNT_LIMIT is properly respected by LoggingHandler.""" - # Create a new LoggerProvider within the patched environment - # This will create LogRecordLimits() that reads from the environment variable - logger_provider = LoggerProvider() - processor = FakeProcessor() - logger_provider.add_log_record_processor(processor) - logger = logging.getLogger("env_test") - handler = LoggingHandler(level=logging.WARNING, logger_provider=logger_provider) - logger.addHandler(handler) - - # Create a log record with many extra attributes - extra_attrs = {f"custom_attr_{i}": f"value_{i}" for i in range(10)} - - with self.assertLogs(level=logging.WARNING): - logger.warning("Test message with many attributes", extra=extra_attrs) - - record = processor.get_log_record(0) - - # With OTEL_ATTRIBUTE_COUNT_LIMIT=3, should have exactly 3 attributes - total_attrs = len(record.log_record.attributes) - self.assertEqual( - total_attrs, - 3, - f"Should have exactly 3 attributes due to limit, got {total_attrs}", - ) - - # Should have 10 dropped attributes (10 custom + 3 code - 3 kept = 10 dropped) - self.assertEqual( - record.dropped_attributes, - 10, - f"Should have 10 dropped attributes, got {record.dropped_attributes}", - ) - - logger.removeHandler(handler) - - @patch.dict(os.environ, {OTEL_ATTRIBUTE_COUNT_LIMIT: "5"}) - def test_otel_attribute_count_limit_includes_code_attributes(self): - """Test that OTEL_ATTRIBUTE_COUNT_LIMIT applies to all attributes including code attributes.""" - # Create a new LoggerProvider within the patched environment - # This will create LogRecordLimits() that reads from the environment variable - logger_provider = LoggerProvider() - processor = FakeProcessor() - logger_provider.add_log_record_processor(processor) - logger = logging.getLogger("env_test_2") - handler = LoggingHandler(level=logging.WARNING, logger_provider=logger_provider) - logger.addHandler(handler) - - # Create a log record with some extra attributes - extra_attrs = {f"user_attr_{i}": f"value_{i}" for i in range(8)} - - with self.assertLogs(level=logging.WARNING): - logger.warning("Test message", extra=extra_attrs) - - record = processor.get_log_record(0) - - # With OTEL_ATTRIBUTE_COUNT_LIMIT=5, should have exactly 5 attributes - total_attrs = len(record.log_record.attributes) - self.assertEqual( - total_attrs, - 5, - f"Should have exactly 5 attributes due to limit, got {total_attrs}", - ) - - # Should have 6 dropped attributes (8 user + 3 code - 5 kept = 6 dropped) - self.assertEqual( - record.dropped_attributes, - 6, - f"Should have 6 dropped attributes, got {record.dropped_attributes}", - ) - - logger.removeHandler(handler) - - def test_logging_handler_without_env_var_uses_default_limit(self): - """Test that without OTEL_ATTRIBUTE_COUNT_LIMIT, default limit (128) should apply.""" - processor, logger, handler = set_up_test_logging(logging.WARNING) - - # Create a log record with many attributes (more than default limit of 128) - extra_attrs = {f"attr_{i}": f"value_{i}" for i in range(150)} - - with self.assertLogs(level=logging.WARNING): - logger.warning("Test message with many attributes", extra=extra_attrs) - - record = processor.get_log_record(0) - - # Should be limited to default limit (128) total attributes - total_attrs = len(record.log_record.attributes) - self.assertEqual( - total_attrs, - 128, - f"Should have exactly 128 attributes (default limit), got {total_attrs}", - ) - - # Should have 25 dropped attributes (150 user + 3 code - 128 kept = 25 dropped) - self.assertEqual( - record.dropped_attributes, - 25, - f"Should have 25 dropped attributes, got {record.dropped_attributes}", - ) - - logger.removeHandler(handler) - - -def set_up_test_logging(level, formatter=None, root_logger=False): - logger_provider = LoggerProvider() - processor = FakeProcessor() - logger_provider.add_log_record_processor(processor) - logger = logging.getLogger(None if root_logger else "foo") - handler = LoggingHandler(level=level, logger_provider=logger_provider) - if formatter: - handler.setFormatter(formatter) - logger.addHandler(handler) - return processor, logger, handler - - -class FakeProcessor(LogRecordProcessor): - def __init__(self): - self.log_data_emitted = [] - - def on_emit(self, log_record: ReadableLogRecord): - self.log_data_emitted.append(log_record) - - def shutdown(self): - pass - - def force_flush(self, timeout_millis: int = 30000): - pass - - def emit_count(self): - return len(self.log_data_emitted) - - def get_log_record(self, i): - return self.log_data_emitted[i] diff --git a/opentelemetry-sdk/tests/logs/test_log_limits.py b/opentelemetry-sdk/tests/logs/test_log_limits.py index b98aac24ade..562383780e6 100644 --- a/opentelemetry-sdk/tests/logs/test_log_limits.py +++ b/opentelemetry-sdk/tests/logs/test_log_limits.py @@ -4,16 +4,16 @@ import unittest from unittest.mock import patch -from opentelemetry.sdk._logs import LogRecordLimits -from opentelemetry.sdk._logs._internal import ( - _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT, -) from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT, OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT, ) +from opentelemetry.sdk.logs import LogRecordLimits +from opentelemetry.sdk.logs._internal import ( + _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT, +) class TestLogLimits(unittest.TestCase): diff --git a/opentelemetry-sdk/tests/logs/test_log_record.py b/opentelemetry-sdk/tests/logs/test_log_record.py index 59d6d11e767..b050755a49a 100644 --- a/opentelemetry-sdk/tests/logs/test_log_record.py +++ b/opentelemetry-sdk/tests/logs/test_log_record.py @@ -8,7 +8,7 @@ from opentelemetry.attributes import BoundedAttributes from opentelemetry.context import get_current from opentelemetry.logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs import ( +from opentelemetry.sdk.logs import ( LogRecordDroppedAttributesWarning, LogRecordLimits, ReadableLogRecord, diff --git a/opentelemetry-sdk/tests/logs/test_logger_provider_cache.py b/opentelemetry-sdk/tests/logs/test_logger_provider_cache.py index ee7c403cae5..9c0f5ce69f0 100644 --- a/opentelemetry-sdk/tests/logs/test_logger_provider_cache.py +++ b/opentelemetry-sdk/tests/logs/test_logger_provider_cache.py @@ -1,81 +1,69 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -import logging import unittest -from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( InMemoryLogRecordExporter, SimpleLogRecordProcessor, ) -def set_up_logging_handler(level): +def set_up_logger_provider(): logger_provider = LoggerProvider() exporter = InMemoryLogRecordExporter() processor = SimpleLogRecordProcessor(exporter=exporter) logger_provider.add_log_record_processor(processor) - handler = LoggingHandler(level=level, logger_provider=logger_provider) - return handler, logger_provider - - -def create_logger(handler, name): - logger = logging.getLogger(name) - logger.addHandler(handler) - return logger + return logger_provider class TestLoggerProviderCache(unittest.TestCase): - def test_get_logger_single_handler(self): - handler, logger_provider = set_up_logging_handler(level=logging.DEBUG) + def test_get_logger_single_name(self): + logger_provider = set_up_logger_provider() # pylint: disable=protected-access logger_cache = logger_provider._logger_cache - logger = create_logger(handler, "test_logger") # Ensure logger is lazily cached self.assertEqual(0, len(logger_cache)) - with self.assertLogs(level=logging.WARNING): - logger.warning("test message") + logger = logger_provider.get_logger("test_logger") + logger.emit(body="test message") self.assertEqual(1, len(logger_cache)) # Ensure only one logger is cached - with self.assertLogs(level=logging.WARNING): - rounds = 100 - for _ in range(rounds): - logger.warning("test message") + rounds = 100 + for _ in range(rounds): + logger_provider.get_logger("test_logger").emit(body="test message") self.assertEqual(1, len(logger_cache)) - def test_get_logger_multiple_loggers(self): - handler, logger_provider = set_up_logging_handler(level=logging.DEBUG) + def test_get_logger_multiple_names(self): + logger_provider = set_up_logger_provider() # pylint: disable=protected-access logger_cache = logger_provider._logger_cache num_loggers = 10 - loggers = [create_logger(handler, str(i)) for i in range(num_loggers)] + names = [str(i) for i in range(num_loggers)] # Ensure loggers are lazily cached self.assertEqual(0, len(logger_cache)) - with self.assertLogs(level=logging.WARNING): - for logger in loggers: - logger.warning("test message") + for name in names: + logger_provider.get_logger(name).emit(body="test message") self.assertEqual(num_loggers, len(logger_cache)) - with self.assertLogs(level=logging.WARNING): - rounds = 100 - for _ in range(rounds): - for logger in loggers: - logger.warning("test message") + rounds = 100 + for _ in range(rounds): + for name in names: + logger_provider.get_logger(name).emit(body="test message") self.assertEqual(num_loggers, len(logger_cache)) def test_provider_get_logger_no_cache(self): - _, logger_provider = set_up_logging_handler(level=logging.DEBUG) + logger_provider = set_up_logger_provider() # pylint: disable=protected-access logger_cache = logger_provider._logger_cache @@ -90,7 +78,7 @@ def test_provider_get_logger_no_cache(self): self.assertEqual(0, len(logger_cache)) def test_provider_get_logger_cached(self): - _, logger_provider = set_up_logging_handler(level=logging.DEBUG) + logger_provider = set_up_logger_provider() # pylint: disable=protected-access logger_cache = logger_provider._logger_cache diff --git a/opentelemetry-sdk/tests/logs/test_logs.py b/opentelemetry-sdk/tests/logs/test_logs.py index bd904ddfd36..e83f322c0d0 100644 --- a/opentelemetry-sdk/tests/logs/test_logs.py +++ b/opentelemetry-sdk/tests/logs/test_logs.py @@ -14,13 +14,17 @@ from opentelemetry.attributes import BoundedAttributes from opentelemetry.context import get_current from opentelemetry.logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs import ( +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, + OTEL_SDK_DISABLED, +) +from opentelemetry.sdk.logs import ( Logger, LoggerProvider, ReadableLogRecord, ReadWriteLogRecord, ) -from opentelemetry.sdk._logs._internal import ( +from opentelemetry.sdk.logs._internal import ( NoOpLogger, SynchronousMultiLogRecordProcessor, _disable_logger_configurator, @@ -28,10 +32,6 @@ _RuleBasedLoggerConfigurator, create_logger_metrics, ) -from opentelemetry.sdk.environment_variables import ( - OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, - OTEL_SDK_DISABLED, -) from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import ( InstrumentationScope, diff --git a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py index 0160d3230fb..431c79ff473 100644 --- a/opentelemetry-sdk/tests/logs/test_multi_log_processor.py +++ b/opentelemetry-sdk/tests/logs/test_multi_log_processor.py @@ -3,7 +3,6 @@ # pylint:disable=protected-access,no-self-use,no-member -import logging import threading import time import unittest @@ -11,10 +10,9 @@ from unittest.mock import Mock from opentelemetry.logs import LogRecord, SeverityNumber -from opentelemetry.sdk._logs._internal import ( +from opentelemetry.sdk.logs._internal import ( ConcurrentMultiLogRecordProcessor, LoggerProvider, - LoggingHandler, LogRecordProcessor, ReadWriteLogRecord, SynchronousMultiLogRecordProcessor, @@ -49,29 +47,36 @@ def force_flush(self, timeout_millis=30000): class TestLogRecordProcessor(unittest.TestCase): def test_log_record_processor(self): provider = LoggerProvider() - handler = LoggingHandler(logger_provider=provider) logs_list_1 = [] processor1 = AnotherLogRecordProcessor(Mock(), logs_list_1) logs_list_2 = [] processor2 = AnotherLogRecordProcessor(Mock(), logs_list_2) - logger = logging.getLogger("test.span.processor") - logger.addHandler(handler) + logger = provider.get_logger("test.span.processor") # Test no proessor added - with self.assertLogs(level=logging.CRITICAL): - logger.critical("Odisha, we have another major cyclone") + logger.emit( + body="Odisha, we have another major cyclone", + severity_number=SeverityNumber.FATAL, + severity_text="FATAL", + ) self.assertEqual(len(logs_list_1), 0) self.assertEqual(len(logs_list_2), 0) # Add one processor provider.add_log_record_processor(processor1) - with self.assertLogs(level=logging.WARNING): - logger.warning("Brace yourself") - with self.assertLogs(level=logging.ERROR): - logger.error("Some error message") + logger.emit( + body="Brace yourself", + severity_number=SeverityNumber.WARN, + severity_text="WARN", + ) + logger.emit( + body="Some error message", + severity_number=SeverityNumber.ERROR, + severity_text="ERROR", + ) expected_list_1 = [ ("Brace yourself", "WARN"), @@ -81,8 +86,11 @@ def test_log_record_processor(self): # Add another processor provider.add_log_record_processor(processor2) - with self.assertLogs(level=logging.CRITICAL): - logger.critical("Something disastrous") + logger.emit( + body="Something disastrous", + severity_number=SeverityNumber.FATAL, + severity_text="FATAL", + ) expected_list_1.append(("Something disastrous", "FATAL")) expected_list_2 = [("Something disastrous", "FATAL")] diff --git a/opentelemetry-sdk/tests/logs/test_sdk_metrics.py b/opentelemetry-sdk/tests/logs/test_sdk_metrics.py index 8191039f847..42a97d2a6af 100644 --- a/opentelemetry-sdk/tests/logs/test_sdk_metrics.py +++ b/opentelemetry-sdk/tests/logs/test_sdk_metrics.py @@ -4,10 +4,10 @@ from unittest import TestCase from unittest.mock import patch -from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk.environment_variables import ( OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, ) +from opentelemetry.sdk.logs import LoggerProvider from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import InMemoryMetricReader diff --git a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py index a352a66a10d..37d7605915f 100644 --- a/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py +++ b/opentelemetry-sdk/tests/shared_internal/test_batch_processor.py @@ -20,15 +20,15 @@ from opentelemetry.logs import ( LogRecord, ) -from opentelemetry.sdk._logs import ( +from opentelemetry.sdk._shared_internal import ( + DuplicateFilter, +) +from opentelemetry.sdk.logs import ( ReadWriteLogRecord, ) -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs.export import ( BatchLogRecordProcessor, ) -from opentelemetry.sdk._shared_internal import ( - DuplicateFilter, -) from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.util.instrumentation import InstrumentationScope diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index a418a8513a3..740f46216ba 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -5,10 +5,8 @@ # pylint: skip-file from __future__ import annotations -import logging -import logging.config from collections.abc import Iterable, Sequence -from logging import WARNING, getLogger +from logging import WARNING from os import environ from unittest import TestCase, mock from unittest.mock import Mock, patch @@ -41,19 +39,19 @@ _initialize_components, _OTelSDKConfigurator, ) -from opentelemetry.sdk._logs import LoggingHandler, LogRecordProcessor -from opentelemetry.sdk._logs._internal import _RuleBasedLoggerConfigurator -from opentelemetry.sdk._logs._internal.export import LogRecordExporter -from opentelemetry.sdk._logs.export import ( - ConsoleLogRecordExporter, - SimpleLogRecordProcessor, -) from opentelemetry.sdk.environment_variables import ( OTEL_PYTHON_LOGGER_CONFIGURATOR, OTEL_PYTHON_METER_CONFIGURATOR, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG, ) +from opentelemetry.sdk.logs import LogRecordProcessor +from opentelemetry.sdk.logs._internal import _RuleBasedLoggerConfigurator +from opentelemetry.sdk.logs._internal.export import LogRecordExporter +from opentelemetry.sdk.logs.export import ( + ConsoleLogRecordExporter, + SimpleLogRecordProcessor, +) from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics._internal import ( _default_meter_configurator, @@ -666,91 +664,30 @@ def tearDown(self): self.processor_patch.stop() self.set_provider_patch.stop() self.provider_patch.stop() - root_logger = getLogger("root") - root_logger.handlers = [handler for handler in root_logger.handlers if not isinstance(handler, LoggingHandler)] def test_logging_init_empty(self): - with ResetGlobalLoggingState(): - auto_resource = Resource.create( - { - "telemetry.auto.version": "auto-version", - } - ) - _init_logging({}, resource=auto_resource) - self.assertEqual(self.set_provider_mock.call_count, 1) - provider = self.set_provider_mock.call_args[0][0] - self.assertIsInstance(provider, DummyLoggerProvider) - self.assertIsInstance(provider.resource, Resource) - self.assertEqual( - provider.resource.attributes.get("telemetry.auto.version"), - "auto-version", - ) + auto_resource = Resource.create( + { + "telemetry.auto.version": "auto-version", + } + ) + _init_logging({}, resource=auto_resource) + self.assertEqual(self.set_provider_mock.call_count, 1) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertIsInstance(provider.resource, Resource) + self.assertEqual( + provider.resource.attributes.get("telemetry.auto.version"), + "auto-version", + ) @patch.dict( environ, {"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"}, ) def test_logging_init_exporter(self): - with ResetGlobalLoggingState(): - resource = Resource.create({}) - _init_logging({"otlp": DummyOTLPLogExporter}, resource=resource) - self.assertEqual(self.set_provider_mock.call_count, 1) - provider = self.set_provider_mock.call_args[0][0] - self.assertIsInstance(provider, DummyLoggerProvider) - self.assertIsInstance(provider.resource, Resource) - self.assertEqual( - provider.resource.attributes.get("service.name"), - "otlp-service", - ) - self.assertEqual(len(provider.processors), 1) - self.assertIsInstance(provider.processors[0], DummyLogRecordProcessor) - self.assertIsInstance(provider.processors[0].exporter, DummyOTLPLogExporter) - getLogger(__name__).error("hello") - self.assertEqual(len(provider.processors), 1) - self.assertTrue(provider.processors[0].exporter.export_called) - - def test_logging_init_exporter_uses_exporter_args_map(self): - with ResetGlobalLoggingState(): - resource = Resource.create({}) - _init_logging( - {"otlp": DummyOTLPLogExporter}, - resource=resource, - exporter_args_map={ - DummyOTLPLogExporter: {"compression": "gzip"}, - DummyOTLPMetricExporter: {"compression": "no"}, - }, - ) - self.assertEqual(self.set_provider_mock.call_count, 1) - provider = self.set_provider_mock.call_args[0][0] - self.assertEqual(len(provider.processors), 1) - self.assertEqual(provider.processors[0].exporter.compression, "gzip") - - def test_logging_init_custom_log_record_processors(self): - log_record_processor = mock.Mock(spec=LogRecordProcessor) - with ResetGlobalLoggingState(): - resource = Resource.create({}) - _init_logging( - {"otlp": DummyOTLPLogExporter}, - resource=resource, - log_record_processors=[log_record_processor], - export_log_record_processor=SimpleLogRecordProcessor, - ) - provider = self.set_provider_mock.call_args[0][0] - self.assertEqual(len(provider.processors), 2) - self.assertEqual(provider.processors[0], log_record_processor) - self.assertIsInstance(provider.processors[1], SimpleLogRecordProcessor) - - @patch.dict( - environ, - {"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"}, - ) - def test_logging_init_exporter_without_handler_setup(self): resource = Resource.create({}) - _init_logging( - {"otlp": DummyOTLPLogExporter}, - resource=resource, - setup_logging_handler=False, - ) + _init_logging({"otlp": DummyOTLPLogExporter}, resource=resource) self.assertEqual(self.set_provider_mock.call_count, 1) provider = self.set_provider_mock.call_args[0][0] self.assertIsInstance(provider, DummyLoggerProvider) @@ -762,21 +699,35 @@ def test_logging_init_exporter_without_handler_setup(self): self.assertEqual(len(provider.processors), 1) self.assertIsInstance(provider.processors[0], DummyLogRecordProcessor) self.assertIsInstance(provider.processors[0].exporter, DummyOTLPLogExporter) - getLogger(__name__).error("hello") - self.assertFalse(provider.processors[0].exporter.export_called) - def test_logging_init_with_setup_logging_handler_to_true_warns(self): + def test_logging_init_exporter_uses_exporter_args_map(self): resource = Resource.create({}) - with self.assertWarnsRegex( - DeprecationWarning, - "and the `LoggingHandler` in `opentelemetry-sdk` that it controls are deprecated", - ): - with ResetGlobalLoggingState(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - resource=resource, - setup_logging_handler=True, - ) + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + exporter_args_map={ + DummyOTLPLogExporter: {"compression": "gzip"}, + DummyOTLPMetricExporter: {"compression": "no"}, + }, + ) + self.assertEqual(self.set_provider_mock.call_count, 1) + provider = self.set_provider_mock.call_args[0][0] + self.assertEqual(len(provider.processors), 1) + self.assertEqual(provider.processors[0].exporter.compression, "gzip") + + def test_logging_init_custom_log_record_processors(self): + log_record_processor = mock.Mock(spec=LogRecordProcessor) + resource = Resource.create({}) + _init_logging( + {"otlp": DummyOTLPLogExporter}, + resource=resource, + log_record_processors=[log_record_processor], + export_log_record_processor=SimpleLogRecordProcessor, + ) + provider = self.set_provider_mock.call_args[0][0] + self.assertEqual(len(provider.processors), 2) + self.assertEqual(provider.processors[0], log_record_processor) + self.assertIsInstance(provider.processors[1], SimpleLogRecordProcessor) @patch.dict( environ, @@ -790,7 +741,6 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock): logging_mock.assert_called_once_with( mock.ANY, mock.ANY, - False, exporter_args_map=None, log_record_processors=None, export_log_record_processor=None, @@ -806,13 +756,11 @@ def test_logging_init_disable_default(self, logging_mock, tracing_mock): ) @patch("opentelemetry.sdk._configuration._init_tracing") @patch("opentelemetry.sdk._configuration._init_logging") - def test_logging_init_enable_env(self, logging_mock, tracing_mock): - with self.assertLogs(level=WARNING): - _initialize_components(auto_instrumentation_version="auto-version") + def test_logging_init_ignores_removed_auto_instrumentation_env(self, logging_mock, tracing_mock): + _initialize_components(auto_instrumentation_version="auto-version") logging_mock.assert_called_once_with( mock.ANY, mock.ANY, - True, exporter_args_map=None, log_record_processors=None, export_log_record_processor=None, @@ -824,7 +772,6 @@ def test_logging_init_enable_env(self, logging_mock, tracing_mock): environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service", - "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED": "True", }, ) @patch("opentelemetry.sdk._configuration._init_tracing") @@ -858,7 +805,6 @@ def test_initialize_components_resource(self, metrics_mock, logging_mock, tracin environ, { "OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service, custom.key.1=env-value", - "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED": "False", }, ) @patch("opentelemetry.sdk._configuration.Resource") @@ -897,7 +843,6 @@ def test_initialize_components_kwargs( "custom.key.2": "pass-in-value-2", }, "id_generator": "TEST_GENERATOR", - "setup_logging_handler": True, "exporter_args_map": {1: {"compression": "gzip"}}, "export_log_record_processor": SimpleLogRecordProcessor, "export_span_processor": SimpleSpanProcessor, @@ -952,113 +897,24 @@ def test_initialize_components_kwargs( logging_mock.assert_called_once_with( "TEST_LOG_EXPORTERS_DICT", "TEST_RESOURCE", - True, exporter_args_map={1: {"compression": "gzip"}}, log_record_processors=[], export_log_record_processor=SimpleLogRecordProcessor, logger_configurator=None, ) - def test_basicConfig_works_with_otel_handler(self): - with ResetGlobalLoggingState(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - Resource.create({}), - setup_logging_handler=True, - ) - - logging.basicConfig(level=logging.INFO) - - root_logger = logging.getLogger() - stream_handlers = [h for h in root_logger.handlers if isinstance(h, logging.StreamHandler)] - self.assertEqual( - len(stream_handlers), - 1, - "basicConfig should add a StreamHandler even when OTel handler exists", - ) - - def test_basicConfig_preserves_otel_handler(self): - with ResetGlobalLoggingState(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - Resource.create({}), - setup_logging_handler=True, - ) - - root_logger = logging.getLogger() - self.assertEqual( - len(root_logger.handlers), - 1, - "Should be exactly one OpenTelemetry LoggingHandler", - ) - handler = root_logger.handlers[0] - self.assertIsInstance(handler, LoggingHandler) - logging.basicConfig() - - self.assertGreater(len(root_logger.handlers), 1) - - logging_handlers = [h for h in root_logger.handlers if isinstance(h, LoggingHandler)] - self.assertEqual( - len(logging_handlers), - 1, - "Should still have exactly one OpenTelemetry LoggingHandler", - ) - - def test_dictConfig_preserves_otel_handler(self): - with ResetGlobalLoggingState(): - _init_logging( - {"otlp": DummyOTLPLogExporter}, - Resource.create({}), - setup_logging_handler=True, - ) - - root = logging.getLogger() - self.assertEqual( - len(root.handlers), - 1, - "Should be exactly one OpenTelemetry LoggingHandler", - ) - logging.config.dictConfig( - { - "version": 1, - "disable_existing_loggers": False, # If this is True all loggers are disabled. Many unit tests assert loggers emit logs. - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "stream": "ext://sys.stdout", - }, - }, - "loggers": { - "": { # root logger - "handlers": ["console"], - }, - }, - } - ) - self.assertEqual(len(root.handlers), 2) - - logging_handlers = [h for h in root.handlers if isinstance(h, LoggingHandler)] - self.assertEqual( - len(logging_handlers), - 1, - "Should still have exactly one OpenTelemetry LoggingHandler", - ) - def test_logging_init_logger_configurator_none_by_default(self): - with ResetGlobalLoggingState(): - _init_logging({}) - provider = self.set_provider_mock.call_args[0][0] - self.assertIsInstance(provider, DummyLoggerProvider) - self.assertIsNone(provider._logger_configurator) + _init_logging({}) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertIsNone(provider._logger_configurator) def test_logging_init_logger_configurator_passed_directly(self): mock_configurator = Mock() - with ResetGlobalLoggingState(): - _init_logging({}, logger_configurator=mock_configurator) - provider = self.set_provider_mock.call_args[0][0] - self.assertIsInstance(provider, DummyLoggerProvider) - self.assertEqual(provider._logger_configurator, mock_configurator) + _init_logging({}, logger_configurator=mock_configurator) + provider = self.set_provider_mock.call_args[0][0] + self.assertIsInstance(provider, DummyLoggerProvider) + self.assertEqual(provider._logger_configurator, mock_configurator) @patch.dict( "os.environ", @@ -1070,8 +926,7 @@ def test_logging_init_custom_logger_configurator_with_env_non_existent_entry_poi logger_configurator_name = _get_logger_configurator() with self.assertLogs(level=WARNING): logger_configurator = _import_logger_configurator(logger_configurator_name) - with ResetGlobalLoggingState(): - _init_logging({}, logger_configurator=logger_configurator) + _init_logging({}, logger_configurator=logger_configurator) @patch("opentelemetry.sdk._configuration.entry_points") @patch.dict( @@ -1093,10 +948,9 @@ def custom_logger_configurator(logger_scope): logger_configurator_name = _get_logger_configurator() logger_configurator = _import_logger_configurator(logger_configurator_name) - with ResetGlobalLoggingState(): - _init_logging({}, logger_configurator=logger_configurator) - provider = self.set_provider_mock.call_args[0][0] - self.assertEqual(provider._logger_configurator, custom_logger_configurator) + _init_logging({}, logger_configurator=logger_configurator) + provider = self.set_provider_mock.call_args[0][0] + self.assertEqual(provider._logger_configurator, custom_logger_configurator) class TestMetricsInit(TestCase): @@ -1378,64 +1232,6 @@ def _configure(self, **kwargs): self.assertIs(configurator, ConfiguratorWithArgs("TEST_NAME", strict=True)) -# Any test that calls _init_logging with setup_logging_handler=True -# should call _init_logging within this context manager, to -# ensure the global logging state is reset after the test. -class ResetGlobalLoggingState: - def __init__(self): - self.original_basic_config = logging.basicConfig - self.original_dict_config = logging.config.dictConfig - self.original_file_config = logging.config.fileConfig - self.root_logger = getLogger() - self.original_handlers = None - - def __enter__(self): - self.original_handlers = self.root_logger.handlers[:] - self.root_logger.handlers = [] - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.root_logger.handlers = [] - for handler in self.original_handlers: - self.root_logger.addHandler(handler) - logging.basicConfig = self.original_basic_config - logging.config.dictConfig = self.original_dict_config - logging.config.fileConfig = self.original_file_config - - -class TestClearLoggingHandlers(TestCase): - def test_preserves_handlers(self): - root_logger = getLogger() - initial_handlers = root_logger.handlers[:] - - test_handler = logging.StreamHandler() - root_logger.addHandler(test_handler) - expected_handlers = initial_handlers + [test_handler] - - with ResetGlobalLoggingState(): - self.assertEqual(len(root_logger.handlers), 0) - temp_handler = logging.StreamHandler() - root_logger.addHandler(temp_handler) - - self.assertEqual(len(root_logger.handlers), len(expected_handlers)) - for h1, h2 in zip(root_logger.handlers, expected_handlers): - self.assertIs(h1, h2) - - root_logger.removeHandler(test_handler) - - def test_preserves_original_logging_fns(self): - def f(x): - print("f") - - with ResetGlobalLoggingState(): - logging.basicConfig = f - logging.config.dictConfig = f - logging.config.fileConfig = f - self.assertEqual(logging.config.dictConfig.__name__, "dictConfig") - self.assertEqual(logging.basicConfig.__name__, "basicConfig") - self.assertEqual(logging.config.fileConfig.__name__, "fileConfig") - - class TestOpAMPInit(TestCase): @patch("opentelemetry.sdk._configuration._init_metrics") @patch("opentelemetry.sdk._configuration._init_tracing") diff --git a/tests/opentelemetry-docker-tests/tests/otlpexporter/test_otlp_logs_functional.py b/tests/opentelemetry-docker-tests/tests/otlpexporter/test_otlp_logs_functional.py index c4a4b086f56..d895fec723c 100644 --- a/tests/opentelemetry-docker-tests/tests/otlpexporter/test_otlp_logs_functional.py +++ b/tests/opentelemetry-docker-tests/tests/otlpexporter/test_otlp_logs_functional.py @@ -35,8 +35,8 @@ OTLPLogExporter as HTTPLogExporter, ) from opentelemetry.logs import Logger, SeverityNumber -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import ( +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import ( LogRecordExporter, SimpleLogRecordProcessor, ) diff --git a/tests/opentelemetry-test-utils/tests/test_otlp_test_server.py b/tests/opentelemetry-test-utils/tests/test_otlp_test_server.py index b4a412e5d57..8e8df87b214 100644 --- a/tests/opentelemetry-test-utils/tests/test_otlp_test_server.py +++ b/tests/opentelemetry-test-utils/tests/test_otlp_test_server.py @@ -9,8 +9,8 @@ import requests from opentelemetry.logs import SeverityNumber -from opentelemetry.sdk._logs import LoggerProvider -from opentelemetry.sdk._logs.export import SimpleLogRecordProcessor +from opentelemetry.sdk.logs import LoggerProvider +from opentelemetry.sdk.logs.export import SimpleLogRecordProcessor from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk.resources import Resource