Skip to content

bugfix(auto-instrumentation): attach OTLPHandler to root logger #2450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 10, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `opentelemetry-exporter-otlp-grpc` update SDK dependency to ~1.9.
([#2442](https://github.com/open-telemetry/opentelemetry-python/pull/2442))
- bugfix(auto-instrumentation): attach OTLPHandler to root logger
([#2450](https://github.com/open-telemetry/opentelemetry-python/pull/2450))

## [1.9.1-0.28b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.9.1-0.28b1) - 2022-01-29

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
"""

import logging
from abc import ABC, abstractmethod
from os import environ
from typing import Dict, Optional, Sequence, Tuple, Type
Expand All @@ -31,6 +32,7 @@
)
from opentelemetry.sdk._logs import (
LogEmitterProvider,
OTLPHandler,
set_log_emitter_provider,
)
from opentelemetry.sdk._logs.export import BatchLogProcessor, LogExporter
Expand Down Expand Up @@ -91,7 +93,7 @@ def _init_tracing(


def _init_logging(
exporters: Dict[str, Sequence[LogExporter]],
exporters: Dict[str, Type[LogExporter]],
auto_instrumentation_version: Optional[str] = None,
):
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
Expand All @@ -111,6 +113,11 @@ def _init_logging(
BatchLogProcessor(exporter_class(**exporter_args))
)

log_emitter = provider.get_log_emitter(__name__)
handler = OTLPHandler(level=logging.NOTSET, log_emitter=log_emitter)

logging.getLogger().addHandler(handler)


def _import_config_components(
selected_components, entry_point_name
Expand Down
121 changes: 118 additions & 3 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.
# type: ignore

import logging
from os import environ
from unittest import TestCase
from unittest.mock import patch
Expand All @@ -26,8 +27,10 @@
_get_id_generator,
_import_exporters,
_import_id_generator,
_init_logging,
_init_tracing,
)
from opentelemetry.sdk._logs import OTLPHandler
from opentelemetry.sdk._logs.export import ConsoleLogExporter
from opentelemetry.sdk._metrics.export import ConsoleMetricExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
Expand All @@ -45,6 +48,45 @@ def add_span_processor(self, processor):
self.processor = processor


class DummyLogEmitterProvider:
def __init__(self, resource=None):
self.resource = resource
self.processor = DummyLogProcessor(DummyOTLPLogExporter())

def add_log_processor(self, processor):
self.processor = processor

def get_log_emitter(self, name):
return DummyLogEmitter(name, self.resource, self.processor)


class DummyLogEmitter:
def __init__(self, name, resource, processor):
self.name = name
self.resource = resource
self.processor = processor

def emit(self, record):
self.processor.emit(record)

def flush(self):
pass


class DummyLogProcessor:
def __init__(self, exporter):
self.exporter = exporter

def emit(self, record):
self.exporter.export([record])

def force_flush(self, time):
pass

def shutdown(self):
pass


class Processor:
def __init__(self, exporter):
self.exporter = exporter
Expand All @@ -63,10 +105,21 @@ def shutdown(self):
pass


class OTLPExporter:
class OTLPSpanExporter:
pass


class DummyOTLPLogExporter:
def __init__(self, *args, **kwargs):
self.export_called = False

def export(self, batch):
self.export_called = True

def shutdown(self):
pass


class CustomIdGenerator(IdGenerator):
def generate_span_id(self):
pass
Expand Down Expand Up @@ -133,14 +186,14 @@ def test_trace_init_default(self):
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=my-otlp-test-service"},
)
def test_trace_init_otlp(self):
_init_tracing({"otlp": OTLPExporter}, RandomIdGenerator)
_init_tracing({"otlp": OTLPSpanExporter}, RandomIdGenerator)

self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertIsInstance(provider, Provider)
self.assertIsInstance(provider.id_generator, RandomIdGenerator)
self.assertIsInstance(provider.processor, Processor)
self.assertIsInstance(provider.processor.exporter, OTLPExporter)
self.assertIsInstance(provider.processor.exporter, OTLPSpanExporter)
self.assertIsInstance(provider.resource, Resource)
self.assertEqual(
provider.resource.attributes.get("service.name"),
Expand All @@ -163,6 +216,68 @@ def test_trace_init_custom_id_generator(self, mock_iter_entry_points):
self.assertIsInstance(provider.id_generator, CustomIdGenerator)


class TestLoggingInit(TestCase):
def setUp(self):
self.processor_patch = patch(
"opentelemetry.sdk._configuration.BatchLogProcessor",
DummyLogProcessor,
)
self.provider_patch = patch(
"opentelemetry.sdk._configuration.LogEmitterProvider",
DummyLogEmitterProvider,
)
self.set_provider_patch = patch(
"opentelemetry.sdk._configuration.set_log_emitter_provider"
)

self.processor_mock = self.processor_patch.start()
self.provider_mock = self.provider_patch.start()
self.set_provider_mock = self.set_provider_patch.start()

def tearDown(self):
self.processor_patch.stop()
self.set_provider_patch.stop()
self.provider_patch.stop()
root_logger = logging.getLogger("root")
root_logger.handlers = [
handler
for handler in root_logger.handlers
if not isinstance(handler, OTLPHandler)
]

def test_logging_init_empty(self):
_init_logging({}, "auto-version")
self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertIsInstance(provider, DummyLogEmitterProvider)
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):
_init_logging({"otlp": DummyOTLPLogExporter})
self.assertEqual(self.set_provider_mock.call_count, 1)
provider = self.set_provider_mock.call_args[0][0]
self.assertIsInstance(provider, DummyLogEmitterProvider)
self.assertIsInstance(provider.resource, Resource)
self.assertEqual(
provider.resource.attributes.get("service.name"),
"otlp-service",
)
self.assertIsInstance(provider.processor, DummyLogProcessor)
self.assertIsInstance(
provider.processor.exporter, DummyOTLPLogExporter
)
logging.getLogger(__name__).error("hello")
self.assertTrue(provider.processor.exporter.export_called)


class TestExporterNames(TestCase):
def test_otlp_exporter_overwrite(self):
for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_PROTO_GRPC]:
Expand Down