Skip to content

Commit e0a4f31

Browse files
authored
bugfix(auto-instrumentation): attach OTLPHandler to root logger (open-telemetry#2450)
1 parent d5868fa commit e0a4f31

File tree

3 files changed

+128
-4
lines changed

3 files changed

+128
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

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

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

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
1818
"""
1919

20+
import logging
2021
from abc import ABC, abstractmethod
2122
from os import environ
2223
from typing import Dict, Optional, Sequence, Tuple, Type
@@ -31,6 +32,7 @@
3132
)
3233
from opentelemetry.sdk._logs import (
3334
LogEmitterProvider,
35+
OTLPHandler,
3436
set_log_emitter_provider,
3537
)
3638
from opentelemetry.sdk._logs.export import BatchLogProcessor, LogExporter
@@ -91,7 +93,7 @@ def _init_tracing(
9193

9294

9395
def _init_logging(
94-
exporters: Dict[str, Sequence[LogExporter]],
96+
exporters: Dict[str, Type[LogExporter]],
9597
auto_instrumentation_version: Optional[str] = None,
9698
):
9799
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
@@ -111,6 +113,11 @@ def _init_logging(
111113
BatchLogProcessor(exporter_class(**exporter_args))
112114
)
113115

116+
log_emitter = provider.get_log_emitter(__name__)
117+
handler = OTLPHandler(level=logging.NOTSET, log_emitter=log_emitter)
118+
119+
logging.getLogger().addHandler(handler)
120+
114121

115122
def _import_config_components(
116123
selected_components, entry_point_name

opentelemetry-sdk/tests/test_configurator.py

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414
# type: ignore
1515

16+
import logging
1617
from os import environ
1718
from unittest import TestCase
1819
from unittest.mock import patch
@@ -26,8 +27,10 @@
2627
_get_id_generator,
2728
_import_exporters,
2829
_import_id_generator,
30+
_init_logging,
2931
_init_tracing,
3032
)
33+
from opentelemetry.sdk._logs import OTLPHandler
3134
from opentelemetry.sdk._logs.export import ConsoleLogExporter
3235
from opentelemetry.sdk._metrics.export import ConsoleMetricExporter
3336
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
@@ -45,6 +48,45 @@ def add_span_processor(self, processor):
4548
self.processor = processor
4649

4750

51+
class DummyLogEmitterProvider:
52+
def __init__(self, resource=None):
53+
self.resource = resource
54+
self.processor = DummyLogProcessor(DummyOTLPLogExporter())
55+
56+
def add_log_processor(self, processor):
57+
self.processor = processor
58+
59+
def get_log_emitter(self, name):
60+
return DummyLogEmitter(name, self.resource, self.processor)
61+
62+
63+
class DummyLogEmitter:
64+
def __init__(self, name, resource, processor):
65+
self.name = name
66+
self.resource = resource
67+
self.processor = processor
68+
69+
def emit(self, record):
70+
self.processor.emit(record)
71+
72+
def flush(self):
73+
pass
74+
75+
76+
class DummyLogProcessor:
77+
def __init__(self, exporter):
78+
self.exporter = exporter
79+
80+
def emit(self, record):
81+
self.exporter.export([record])
82+
83+
def force_flush(self, time):
84+
pass
85+
86+
def shutdown(self):
87+
pass
88+
89+
4890
class Processor:
4991
def __init__(self, exporter):
5092
self.exporter = exporter
@@ -63,10 +105,21 @@ def shutdown(self):
63105
pass
64106

65107

66-
class OTLPExporter:
108+
class OTLPSpanExporter:
67109
pass
68110

69111

112+
class DummyOTLPLogExporter:
113+
def __init__(self, *args, **kwargs):
114+
self.export_called = False
115+
116+
def export(self, batch):
117+
self.export_called = True
118+
119+
def shutdown(self):
120+
pass
121+
122+
70123
class CustomIdGenerator(IdGenerator):
71124
def generate_span_id(self):
72125
pass
@@ -133,14 +186,14 @@ def test_trace_init_default(self):
133186
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=my-otlp-test-service"},
134187
)
135188
def test_trace_init_otlp(self):
136-
_init_tracing({"otlp": OTLPExporter}, RandomIdGenerator)
189+
_init_tracing({"otlp": OTLPSpanExporter}, RandomIdGenerator)
137190

138191
self.assertEqual(self.set_provider_mock.call_count, 1)
139192
provider = self.set_provider_mock.call_args[0][0]
140193
self.assertIsInstance(provider, Provider)
141194
self.assertIsInstance(provider.id_generator, RandomIdGenerator)
142195
self.assertIsInstance(provider.processor, Processor)
143-
self.assertIsInstance(provider.processor.exporter, OTLPExporter)
196+
self.assertIsInstance(provider.processor.exporter, OTLPSpanExporter)
144197
self.assertIsInstance(provider.resource, Resource)
145198
self.assertEqual(
146199
provider.resource.attributes.get("service.name"),
@@ -163,6 +216,68 @@ def test_trace_init_custom_id_generator(self, mock_iter_entry_points):
163216
self.assertIsInstance(provider.id_generator, CustomIdGenerator)
164217

165218

219+
class TestLoggingInit(TestCase):
220+
def setUp(self):
221+
self.processor_patch = patch(
222+
"opentelemetry.sdk._configuration.BatchLogProcessor",
223+
DummyLogProcessor,
224+
)
225+
self.provider_patch = patch(
226+
"opentelemetry.sdk._configuration.LogEmitterProvider",
227+
DummyLogEmitterProvider,
228+
)
229+
self.set_provider_patch = patch(
230+
"opentelemetry.sdk._configuration.set_log_emitter_provider"
231+
)
232+
233+
self.processor_mock = self.processor_patch.start()
234+
self.provider_mock = self.provider_patch.start()
235+
self.set_provider_mock = self.set_provider_patch.start()
236+
237+
def tearDown(self):
238+
self.processor_patch.stop()
239+
self.set_provider_patch.stop()
240+
self.provider_patch.stop()
241+
root_logger = logging.getLogger("root")
242+
root_logger.handlers = [
243+
handler
244+
for handler in root_logger.handlers
245+
if not isinstance(handler, OTLPHandler)
246+
]
247+
248+
def test_logging_init_empty(self):
249+
_init_logging({}, "auto-version")
250+
self.assertEqual(self.set_provider_mock.call_count, 1)
251+
provider = self.set_provider_mock.call_args[0][0]
252+
self.assertIsInstance(provider, DummyLogEmitterProvider)
253+
self.assertIsInstance(provider.resource, Resource)
254+
self.assertEqual(
255+
provider.resource.attributes.get("telemetry.auto.version"),
256+
"auto-version",
257+
)
258+
259+
@patch.dict(
260+
environ,
261+
{"OTEL_RESOURCE_ATTRIBUTES": "service.name=otlp-service"},
262+
)
263+
def test_logging_init_exporter(self):
264+
_init_logging({"otlp": DummyOTLPLogExporter})
265+
self.assertEqual(self.set_provider_mock.call_count, 1)
266+
provider = self.set_provider_mock.call_args[0][0]
267+
self.assertIsInstance(provider, DummyLogEmitterProvider)
268+
self.assertIsInstance(provider.resource, Resource)
269+
self.assertEqual(
270+
provider.resource.attributes.get("service.name"),
271+
"otlp-service",
272+
)
273+
self.assertIsInstance(provider.processor, DummyLogProcessor)
274+
self.assertIsInstance(
275+
provider.processor.exporter, DummyOTLPLogExporter
276+
)
277+
logging.getLogger(__name__).error("hello")
278+
self.assertTrue(provider.processor.exporter.export_called)
279+
280+
166281
class TestExporterNames(TestCase):
167282
def test_otlp_exporter_overwrite(self):
168283
for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_PROTO_GRPC]:

0 commit comments

Comments
 (0)