From 43c79ae2ce7a1c2d1fa3eda712371510c80cc41e Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sun, 6 Jun 2021 19:18:28 +0530 Subject: [PATCH 01/11] Add global LogEmitterProvider and convenience function get_log_emitter --- opentelemetry-sdk/setup.cfg | 2 + .../sdk/environment_variables/__init__.py | 5 ++ .../src/opentelemetry/sdk/logs/__init__.py | 67 ++++++++++++++++++- opentelemetry-sdk/tests/logs/__init__.py | 13 ++++ .../tests/logs/test_global_provider.py | 61 +++++++++++++++++ 5 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 opentelemetry-sdk/tests/logs/__init__.py create mode 100644 opentelemetry-sdk/tests/logs/test_global_provider.py diff --git a/opentelemetry-sdk/setup.cfg b/opentelemetry-sdk/setup.cfg index 6c258c9796..c4e19b1c15 100644 --- a/opentelemetry-sdk/setup.cfg +++ b/opentelemetry-sdk/setup.cfg @@ -51,6 +51,8 @@ where = src [options.entry_points] opentelemetry_tracer_provider = sdk_tracer_provider = opentelemetry.sdk.trace:TracerProvider +opentelemetry_log_emitter_provider = + sdk_log_emitter_provider = opentelemetry.sdk.logs:LogEmitterProvider opentelemetry_exporter = console_span = opentelemetry.sdk.trace.export:ConsoleSpanExporter opentelemetry_id_generator = diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index a59ca0e5c1..6e27fecd2d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -242,3 +242,8 @@ If both are set, :envvar:`OTEL_SERVICE_NAME` takes precedence. """ + +OTEL_PYTHON_LOG_EMITTER_PROVIDER = "OTEL_PYTHON_LOG_EMITTER_PROVIDER" +""" +.. envvar:: OTEL_PYTHON_LOG_EMITTER_PROVIDER +""" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index 9a0fb2a095..f93987e08d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -14,14 +14,22 @@ import abc import atexit -from typing import Any, Optional +import logging +import os +from typing import Any, Optional, cast +from opentelemetry.sdk.environment_variables import ( + OTEL_PYTHON_LOG_EMITTER_PROVIDER, +) from opentelemetry.sdk.logs.severity import SeverityNumber from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationInfo from opentelemetry.trace.span import TraceFlags +from opentelemetry.util._providers import _load_provider from opentelemetry.util.types import Attributes +_logger = logging.getLogger(__name__) + class LogRecord: """A LogRecord instance represents an event being logged. @@ -172,3 +180,60 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: False otherwise. """ # TODO: multi_log_processor.force_flush + + +_LOG_EMITTER_PROVIDER = None + + +def get_log_emitter_provider() -> LogEmitterProvider: + """Gets the current global :class:`~.LogEmitterProvider` object.""" + global _LOG_EMITTER_PROVIDER # pylint: disable=global-statement + if _LOG_EMITTER_PROVIDER is None: + if OTEL_PYTHON_LOG_EMITTER_PROVIDER not in os.environ: + _LOG_EMITTER_PROVIDER = LogEmitterProvider() + return _LOG_EMITTER_PROVIDER + + _LOG_EMITTER_PROVIDER = cast( + "LogEmitterProvider", + _load_provider( + OTEL_PYTHON_LOG_EMITTER_PROVIDER, "log_emitter_provider" + ), + ) + + return _LOG_EMITTER_PROVIDER + + +def set_log_emitter_provider(log_emitter_provider: LogEmitterProvider) -> None: + """Sets the current global :class:`~.LogEmitterProvider` object. + + This can only be done once, a warning will be logged if any furter attempt + is made. + """ + global _LOG_EMITTER_PROVIDER # pylint: disable=global-statement + + if _LOG_EMITTER_PROVIDER is not None: + _logger.warning( + "Overriding of current LogEmitterProvider is not allowed" + ) + return + + _LOG_EMITTER_PROVIDER = log_emitter_provider + + +def get_log_emitter( + instrumenting_module_name: str, + instrumenting_library_version: str = "", + log_emitter_provider: Optional[LogEmitterProvider] = None, +) -> LogEmitter: + """Returns a `LogEmitter` for use within a python process. + + This function is a convenience wrapper for + opentelemetry.sdk.logs.LogEmitterProvider.get_log_emitter. + + If log_emitter_provider param is omitted the current configured one is used. + """ + if log_emitter_provider is None: + log_emitter_provider = get_log_emitter_provider() + return log_emitter_provider.get_log_emitter( + instrumenting_module_name, instrumenting_library_version + ) diff --git a/opentelemetry-sdk/tests/logs/__init__.py b/opentelemetry-sdk/tests/logs/__init__.py new file mode 100644 index 0000000000..b0a6f42841 --- /dev/null +++ b/opentelemetry-sdk/tests/logs/__init__.py @@ -0,0 +1,13 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/opentelemetry-sdk/tests/logs/test_global_provider.py b/opentelemetry-sdk/tests/logs/test_global_provider.py new file mode 100644 index 0000000000..2490ed4457 --- /dev/null +++ b/opentelemetry-sdk/tests/logs/test_global_provider.py @@ -0,0 +1,61 @@ +# type:ignore +import unittest +from importlib import reload +from logging import WARNING +from unittest.mock import patch + +from opentelemetry.sdk import logs +from opentelemetry.sdk.environment_variables import ( + OTEL_PYTHON_LOG_EMITTER_PROVIDER, +) +from opentelemetry.sdk.logs import ( + LogEmitterProvider, + get_log_emitter_provider, + set_log_emitter_provider, +) + + +class TestGlobals(unittest.TestCase): + def tearDown(self): + reload(logs) + + def check_override_not_allowed(self): + """set_log_emitter_provider should throw a warning when overridden""" + provider = get_log_emitter_provider() + with self.assertLogs(level=WARNING) as test: + set_log_emitter_provider(LogEmitterProvider()) + self.assertEqual( + test.output, + [ + ( + "WARNING:opentelemetry.sdk.logs:Overriding of current " + "LogEmitterProvider is not allowed" + ) + ], + ) + self.assertIs(provider, get_log_emitter_provider()) + + def test_set_tracer_provider(self): + reload(logs) + provider = LogEmitterProvider() + set_log_emitter_provider(provider) + retrieved_provider = get_log_emitter_provider() + self.assertEqual(provider, retrieved_provider) + + def test_tracer_provider_override_warning(self): + reload(logs) + self.check_override_not_allowed() + + @patch.dict( + "os.environ", + {OTEL_PYTHON_LOG_EMITTER_PROVIDER: "sdk_log_emitter_provider"}, + ) + def test_sdk_log_emitter_provider(self): + reload(logs) + self.check_override_not_allowed() + + @patch.dict("os.environ", {OTEL_PYTHON_LOG_EMITTER_PROVIDER: "unknown"}) + def test_unknown_log_emitter_provider(self): + reload(logs) + with self.assertRaises(Exception): + get_log_emitter_provider() From b88bb2aad9f6e6b5de25167f904066da088a380e Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 10 Jun 2021 01:00:51 +0530 Subject: [PATCH 02/11] Add license --- .../tests/logs/test_global_provider.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/opentelemetry-sdk/tests/logs/test_global_provider.py b/opentelemetry-sdk/tests/logs/test_global_provider.py index 2490ed4457..fc687d1961 100644 --- a/opentelemetry-sdk/tests/logs/test_global_provider.py +++ b/opentelemetry-sdk/tests/logs/test_global_provider.py @@ -1,3 +1,17 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # type:ignore import unittest from importlib import reload From 8a071fe1f50ea66f7c1d101b6368ccac921b3ca9 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 7 Jun 2021 22:39:17 +0530 Subject: [PATCH 03/11] Add OTLPHandler for logging module --- .../src/opentelemetry/sdk/logs/__init__.py | 35 ++++++++ .../src/opentelemetry/sdk/util/severity.py | 71 ++++++++++++++++ opentelemetry-sdk/tests/logs/test_handler.py | 81 +++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py create mode 100644 opentelemetry-sdk/tests/logs/test_handler.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index f93987e08d..a149c4460b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -24,6 +24,8 @@ from opentelemetry.sdk.logs.severity import SeverityNumber from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationInfo +from opentelemetry.sdk.util.severity import std_to_otlp +from opentelemetry.trace import get_current_span from opentelemetry.trace.span import TraceFlags from opentelemetry.util._providers import _load_provider from opentelemetry.util.types import Attributes @@ -111,6 +113,35 @@ def force_flush(self, timeout_millis: int = 30000): """ +class OTLPHandler(logging.Handler): + def __init__(self, level=logging.NOTSET, log_emitter=None) -> None: + super().__init__(level=level) + self._log_emitter = log_emitter or get_log_emitter("") + + def _translate(self, record: logging.LogRecord) -> LogRecord: + timestamp = int(record.created * 1e9) + span_context = get_current_span().get_span_context() + attributes: Attributes = {} # TODO: attributes from record metadata + severity_number = std_to_otlp(record.levelno) + return LogRecord( + timestamp=timestamp, + trace_id=span_context.trace_id, + span_id=span_context.span_id, + trace_flags=span_context.trace_flags, + severity_text=record.levelname, + severity_number=severity_number, + body=record.getMessage(), + resource=self._log_emitter.resource, + attributes=attributes, + ) + + def emit(self, record: logging.LogRecord) -> None: + self._log_emitter.emit(self._translate(record)) + + def flush(self) -> None: + self._log_emitter.flush() + + class LogEmitter: # TODO: Add multi_log_processor def __init__( @@ -121,6 +152,10 @@ def __init__( self._resource = resource self._instrumentation_info = instrumentation_info + @property + def resource(self): + return self._resource + def emit(self, record: LogRecord): # TODO: multi_log_processor.emit pass diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py new file mode 100644 index 0000000000..e566f38382 --- /dev/null +++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py @@ -0,0 +1,71 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from opentelemetry.sdk.logs.severity import SeverityNumber + +_STD_TO_OTLP = { + 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_otlp(levelno: int) -> SeverityNumber: + """Map python log levelno to OTLP log severity number.""" + if levelno < 10: + return SeverityNumber.UNSPECIFIED + if levelno > 53: + return SeverityNumber.FATAL4 + return _STD_TO_OTLP[levelno] diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py new file mode 100644 index 0000000000..a070d21ff2 --- /dev/null +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -0,0 +1,81 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import unittest +from unittest.mock import Mock + +from opentelemetry.sdk import trace +from opentelemetry.sdk.logs import LogEmitter, OTLPHandler +from opentelemetry.sdk.logs.severity import SeverityNumber +from opentelemetry.trace import INVALID_SPAN_CONTEXT + + +def get_logger(level=logging.NOTSET, log_emitter=None): + logger = logging.getLogger(__name__) + handler = OTLPHandler(level=level, log_emitter=log_emitter) + logger.addHandler(handler) + return logger + + +class TestOTLPHandler(unittest.TestCase): + def test_handler_default_log_level(self): + emitter_mock = Mock(spec=LogEmitter) + logger = get_logger(log_emitter=emitter_mock) + # Make sure debug messages are ignored by default + logger.debug("Debug message") + self.assertEqual(emitter_mock.emit.call_count, 0) + # Assert emit gets called for warning message + logger.warning("Wanrning message") + self.assertEqual(emitter_mock.emit.call_count, 1) + + def test_handler_custom_log_level(self): + emitter_mock = Mock(spec=LogEmitter) + logger = get_logger(level=logging.ERROR, log_emitter=emitter_mock) + logger.warning("Warning message test custom log level") + # Make sure any log with level < ERROR is ignored + self.assertEqual(emitter_mock.emit.call_count, 0) + logger.error("Mumbai, we have a major problem") + logger.critical("No Time For Caution") + self.assertEqual(emitter_mock.emit.call_count, 2) + + def test_log_record_no_span_context(self): + emitter_mock = Mock(spec=LogEmitter) + logger = get_logger(log_emitter=emitter_mock) + # Assert emit gets called for warning message + logger.warning("Wanrning message") + log_record, *_ = emitter_mock.emit.call_args.args + self.assertIsNotNone(log_record) + self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id) + self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id) + self.assertEqual( + log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags + ) + + def test_log_record_trace_correlation(self): + emitter_mock = Mock(spec=LogEmitter) + logger = get_logger(log_emitter=emitter_mock) + + tracer = trace.TracerProvider().get_tracer(__name__) + with tracer.start_as_current_span("test") as span: + logger.critical("Critical message within span") + + log_record, *_ = emitter_mock.emit.call_args.args + self.assertEqual(log_record.body, "Critical message within span") + self.assertEqual(log_record.severity_text, "CRITICAL") + self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) + span_context = span.get_span_context() + self.assertEqual(log_record.trace_id, span_context.trace_id) + self.assertEqual(log_record.span_id, span_context.span_id) + self.assertEqual(log_record.trace_flags, span_context.trace_flags) From a10ccb869460457888d453c9c5630799c0f1813a Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 10 Jun 2021 04:34:21 +0530 Subject: [PATCH 04/11] Update handler --- .../src/opentelemetry/sdk/logs/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index a149c4460b..42f0deddbf 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -114,14 +114,17 @@ def force_flush(self, timeout_millis: int = 30000): class OTLPHandler(logging.Handler): + """A handler calss which writes logging records, in OTLP format, to + a network destination or file. + """ def __init__(self, level=logging.NOTSET, log_emitter=None) -> None: super().__init__(level=level) - self._log_emitter = log_emitter or get_log_emitter("") + self._log_emitter = log_emitter or get_log_emitter(__name__) def _translate(self, record: logging.LogRecord) -> LogRecord: timestamp = int(record.created * 1e9) span_context = get_current_span().get_span_context() - attributes: Attributes = {} # TODO: attributes from record metadata + attributes: Attributes = {} # TODO: attributes (or resource attributes?) from record metadata severity_number = std_to_otlp(record.levelno) return LogRecord( timestamp=timestamp, @@ -136,9 +139,17 @@ def _translate(self, record: logging.LogRecord) -> LogRecord: ) def emit(self, record: logging.LogRecord) -> None: + """ + Emit a record. + + The record is translated to OTLP format, and then sent across the pipeline. + """ self._log_emitter.emit(self._translate(record)) def flush(self) -> None: + """ + Flushes the logging output. + """ self._log_emitter.flush() From c31d36a6e7f239e43840fcf69916e52b45fc0906 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Fri, 11 Jun 2021 06:03:33 +0530 Subject: [PATCH 05/11] fix lint --- opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py | 4 +++- opentelemetry-sdk/tests/logs/test_handler.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index 42f0deddbf..0e0bf07c83 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -117,6 +117,7 @@ class OTLPHandler(logging.Handler): """A handler calss which writes logging records, in OTLP format, to a network destination or file. """ + def __init__(self, level=logging.NOTSET, log_emitter=None) -> None: super().__init__(level=level) self._log_emitter = log_emitter or get_log_emitter(__name__) @@ -124,7 +125,8 @@ def __init__(self, level=logging.NOTSET, log_emitter=None) -> None: def _translate(self, record: logging.LogRecord) -> LogRecord: timestamp = int(record.created * 1e9) span_context = get_current_span().get_span_context() - attributes: Attributes = {} # TODO: attributes (or resource attributes?) from record metadata + # TODO: attributes (or resource attributes?) from record metadata + attributes: Attributes = {} severity_number = std_to_otlp(record.levelno) return LogRecord( timestamp=timestamp, diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index a070d21ff2..12a79a9a13 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -71,6 +71,10 @@ def test_log_record_trace_correlation(self): with tracer.start_as_current_span("test") as span: logger.critical("Critical message within span") + print(emitter_mock.emit.call_args) + print(emitter_mock.emit.call_args.args) + print(emitter_mock.emit.call_args.kwargs) + log_record, *_ = emitter_mock.emit.call_args.args self.assertEqual(log_record.body, "Critical message within span") self.assertEqual(log_record.severity_text, "CRITICAL") From c1d3eb6344d2fca7051bb08064f2666fda00b48b Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Fri, 11 Jun 2021 06:21:08 +0530 Subject: [PATCH 06/11] Update handler tests --- opentelemetry-sdk/tests/logs/test_handler.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 12a79a9a13..156eca5ab1 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -71,11 +71,8 @@ def test_log_record_trace_correlation(self): with tracer.start_as_current_span("test") as span: logger.critical("Critical message within span") - print(emitter_mock.emit.call_args) - print(emitter_mock.emit.call_args.args) - print(emitter_mock.emit.call_args.kwargs) - - log_record, *_ = emitter_mock.emit.call_args.args + args, _ = emitter_mock.emit.call_args_list[0] + log_record = args[0] self.assertEqual(log_record.body, "Critical message within span") self.assertEqual(log_record.severity_text, "CRITICAL") self.assertEqual(log_record.severity_number, SeverityNumber.FATAL) From 0f6f3b4fb10caa6a12ee44ab777689a4b4888aa2 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Fri, 11 Jun 2021 06:24:20 +0530 Subject: [PATCH 07/11] Fix tests --- opentelemetry-sdk/tests/logs/test_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 156eca5ab1..1d1b84f0fd 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -55,7 +55,9 @@ def test_log_record_no_span_context(self): logger = get_logger(log_emitter=emitter_mock) # Assert emit gets called for warning message logger.warning("Wanrning message") - log_record, *_ = emitter_mock.emit.call_args.args + args, _ = emitter_mock.emit.call_args_list[0] + log_record = args[0] + self.assertIsNotNone(log_record) self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id) self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id) From 287719c593657940262e558ba001e11735baa12b Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 14 Jun 2021 23:23:07 +0530 Subject: [PATCH 08/11] Add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e76232d5ac..c09e71a5fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add global LogEmitterProvider and convenience function get_log_emitter ([#1901](https://github.com/open-telemetry/opentelemetry-python/pull/1901)) +- Add OTLPHandler for standard library logging module + ([#1903](https://github.com/open-telemetry/opentelemetry-python/pull/1903)) ### Changed - Updated `opentelemetry-opencensus-exporter` to use `service_name` of spans instead of resource From 0dc73185c0bd82e562245c9548e66bd02e05ec3f Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 16 Jun 2021 21:29:02 +0530 Subject: [PATCH 09/11] Update opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py Co-authored-by: alrex --- opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index 0e0bf07c83..1d18287365 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -114,7 +114,7 @@ def force_flush(self, timeout_millis: int = 30000): class OTLPHandler(logging.Handler): - """A handler calss which writes logging records, in OTLP format, to + """A handler class which writes logging records, in OTLP format, to a network destination or file. """ From ef20aee88feca3b588e5b079e32edbcdf6a590de Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 17 Jun 2021 02:11:57 +0530 Subject: [PATCH 10/11] Review changes --- .../src/opentelemetry/sdk/logs/__init__.py | 7 +- .../src/opentelemetry/sdk/logs/severity.py | 57 +++++++++++++++ .../src/opentelemetry/sdk/util/severity.py | 71 ------------------- 3 files changed, 62 insertions(+), 73 deletions(-) delete mode 100644 opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py index 0e0bf07c83..2ee0ed5a95 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py @@ -21,10 +21,9 @@ from opentelemetry.sdk.environment_variables import ( OTEL_PYTHON_LOG_EMITTER_PROVIDER, ) -from opentelemetry.sdk.logs.severity import SeverityNumber +from opentelemetry.sdk.logs.severity import SeverityNumber, std_to_otlp from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.util.instrumentation import InstrumentationInfo -from opentelemetry.sdk.util.severity import std_to_otlp from opentelemetry.trace import get_current_span from opentelemetry.trace.span import TraceFlags from opentelemetry.util._providers import _load_provider @@ -190,6 +189,10 @@ def __init__( if shutdown_on_exit: self._at_exit_handler = atexit.register(self.shutdown) + @property + def resource(self): + return self._resource + def get_log_emitter( self, instrumenting_module_name: str, diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py index 13a9d4e6c3..1d24738888 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py @@ -54,3 +54,60 @@ class SeverityNumber(enum.Enum): FATAL2 = 22 FATAL3 = 23 FATAL4 = 24 + + +_STD_TO_OTLP = { + 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_otlp(levelno: int) -> SeverityNumber: + """Map python log levelno to OTLP log severity number.""" + if levelno < 10: + return SeverityNumber.UNSPECIFIED + if levelno > 53: + return SeverityNumber.FATAL4 + return _STD_TO_OTLP[levelno] diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py deleted file mode 100644 index e566f38382..0000000000 --- a/opentelemetry-sdk/src/opentelemetry/sdk/util/severity.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright The OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from opentelemetry.sdk.logs.severity import SeverityNumber - -_STD_TO_OTLP = { - 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_otlp(levelno: int) -> SeverityNumber: - """Map python log levelno to OTLP log severity number.""" - if levelno < 10: - return SeverityNumber.UNSPECIFIED - if levelno > 53: - return SeverityNumber.FATAL4 - return _STD_TO_OTLP[levelno] From 38066141f3a0c3f5f9f886e5075dd005656acf57 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 17 Jun 2021 02:17:54 +0530 Subject: [PATCH 11/11] Add link to log levels --- opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py b/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py index 1d24738888..c0509ea2c1 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py @@ -105,7 +105,10 @@ class SeverityNumber(enum.Enum): def std_to_otlp(levelno: int) -> SeverityNumber: - """Map python log levelno to OTLP log severity number.""" + """ + Map python log levelno as defined in https://docs.python.org/3/library/logging.html#logging-levels + to OTLP log severity number. + """ if levelno < 10: return SeverityNumber.UNSPECIFIED if levelno > 53: