Skip to content

Commit cbc0920

Browse files
committed
fix: add tests for percent-style logging calls to ensure they're still possible
1 parent 0b8c9c5 commit cbc0920

File tree

1 file changed

+160
-152
lines changed

1 file changed

+160
-152
lines changed

opentelemetry-sdk/tests/logs/test_handler.py

+160-152
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
# limitations under the License.
1414
import logging
1515
import unittest
16-
from unittest.mock import Mock
16+
from unittest.mock import Mock, MagicMock
17+
from contextlib import contextmanager
1718

1819
from opentelemetry._logs import NoOpLoggerProvider, SeverityNumber
1920
from opentelemetry._logs import get_logger as APIGetLogger
@@ -31,30 +32,28 @@
3132

3233
class TestLoggingHandler(unittest.TestCase):
3334
def test_handler_default_log_level(self):
34-
processor, logger = set_up_test_logging(logging.NOTSET)
35+
with set_up_test_logging(logging.NOTSET) as (processor, logger):
36+
# Make sure debug messages are ignored by default
37+
logger.debug("Debug message")
38+
assert processor.emit_count() == 0
3539

36-
# Make sure debug messages are ignored by default
37-
logger.debug("Debug message")
38-
assert processor.emit_count() == 0
39-
40-
# Assert emit gets called for warning message
41-
with self.assertLogs(level=logging.WARNING):
42-
logger.warning("Warning message")
43-
self.assertEqual(processor.emit_count(), 1)
40+
# Assert emit gets called for warning message
41+
with self.assertLogs(level=logging.WARNING):
42+
logger.warning("Warning message")
43+
self.assertEqual(processor.emit_count(), 1)
4444

4545
def test_handler_custom_log_level(self):
46-
processor, logger = set_up_test_logging(logging.ERROR)
47-
48-
with self.assertLogs(level=logging.WARNING):
49-
logger.warning("Warning message test custom log level")
50-
# Make sure any log with level < ERROR is ignored
51-
assert processor.emit_count() == 0
46+
with set_up_test_logging(logging.ERROR) as (processor, logger):
47+
with self.assertLogs(level=logging.WARNING):
48+
logger.warning("Warning message test custom log level")
49+
# Make sure any log with level < ERROR is ignored
50+
assert processor.emit_count() == 0
5251

53-
with self.assertLogs(level=logging.ERROR):
54-
logger.error("Mumbai, we have a major problem")
55-
with self.assertLogs(level=logging.CRITICAL):
56-
logger.critical("No Time For Caution")
57-
self.assertEqual(processor.emit_count(), 2)
52+
with self.assertLogs(level=logging.ERROR):
53+
logger.error("Mumbai, we have a major problem")
54+
with self.assertLogs(level=logging.CRITICAL):
55+
logger.critical("No Time For Caution")
56+
self.assertEqual(processor.emit_count(), 2)
5857

5958
# pylint: disable=protected-access
6059
def test_log_record_emit_noop(self):
@@ -70,6 +69,9 @@ def test_log_record_emit_noop(self):
7069
with self.assertLogs(level=logging.WARNING):
7170
logger.warning("Warning message")
7271

72+
# teardown
73+
logger.removeHandler(handler_mock)
74+
7375
def test_log_flush_noop(self):
7476
no_op_logger_provider = NoOpLoggerProvider()
7577
no_op_logger_provider.force_flush = Mock()
@@ -86,173 +88,178 @@ def test_log_flush_noop(self):
8688
logger.handlers[0].flush()
8789
no_op_logger_provider.force_flush.assert_not_called()
8890

89-
def test_log_record_no_span_context(self):
90-
processor, logger = set_up_test_logging(logging.WARNING)
91+
# teardown
92+
logger.removeHandler(handler)
9193

92-
# Assert emit gets called for warning message
93-
with self.assertLogs(level=logging.WARNING):
94-
logger.warning("Warning message")
94+
def test_log_record_no_span_context(self):
95+
with set_up_test_logging(logging.WARNING) as (processor, logger):
96+
# Assert emit gets called for warning message
97+
with self.assertLogs(level=logging.WARNING):
98+
logger.warning("Warning message")
9599

96-
log_record = processor.get_log_record(0)
100+
log_record = processor.get_log_record(0)
97101

98-
self.assertIsNotNone(log_record)
99-
self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id)
100-
self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id)
101-
self.assertEqual(
102-
log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags
103-
)
102+
self.assertIsNotNone(log_record)
103+
self.assertEqual(log_record.trace_id, INVALID_SPAN_CONTEXT.trace_id)
104+
self.assertEqual(log_record.span_id, INVALID_SPAN_CONTEXT.span_id)
105+
self.assertEqual(
106+
log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags
107+
)
104108

105109
def test_log_record_observed_timestamp(self):
106-
processor, logger = set_up_test_logging(logging.WARNING)
110+
with set_up_test_logging(logging.WARNING) as (processor, logger):
111+
with self.assertLogs(level=logging.WARNING):
112+
logger.warning("Warning message")
107113

108-
with self.assertLogs(level=logging.WARNING):
109-
logger.warning("Warning message")
110-
111-
log_record = processor.get_log_record(0)
112-
self.assertIsNotNone(log_record.observed_timestamp)
114+
log_record = processor.get_log_record(0)
115+
self.assertIsNotNone(log_record.observed_timestamp)
113116

114117
def test_log_record_user_attributes(self):
115118
"""Attributes can be injected into logs by adding them to the LogRecord"""
116-
processor, logger = set_up_test_logging(logging.WARNING)
119+
with set_up_test_logging(logging.WARNING) as (processor, logger):
120+
# Assert emit gets called for warning message
121+
with self.assertLogs(level=logging.WARNING):
122+
logger.warning("Warning message", extra={"http.status_code": 200})
117123

118-
# Assert emit gets called for warning message
119-
with self.assertLogs(level=logging.WARNING):
120-
logger.warning("Warning message", extra={"http.status_code": 200})
121-
122-
log_record = processor.get_log_record(0)
124+
log_record = processor.get_log_record(0)
123125

124-
self.assertIsNotNone(log_record)
125-
self.assertEqual(
126-
log_record.attributes,
127-
{**log_record.attributes, **{"http.status_code": 200}},
128-
)
129-
self.assertTrue(
130-
log_record.attributes[SpanAttributes.CODE_FILEPATH].endswith(
131-
"test_handler.py"
126+
self.assertIsNotNone(log_record)
127+
self.assertEqual(
128+
log_record.attributes,
129+
{**log_record.attributes, "http.status_code": 200},
132130
)
133-
)
134-
self.assertEqual(
135-
log_record.attributes[SpanAttributes.CODE_FUNCTION],
136-
"test_log_record_user_attributes",
137-
)
138-
# The line of the log statement is not a constant (changing tests may change that),
139-
# so only check that the attribute is present.
140-
self.assertTrue(SpanAttributes.CODE_LINENO in log_record.attributes)
141-
self.assertTrue(isinstance(log_record.attributes, BoundedAttributes))
131+
self.assertTrue(
132+
log_record.attributes[SpanAttributes.CODE_FILEPATH].endswith(
133+
"test_handler.py"
134+
)
135+
)
136+
self.assertEqual(
137+
log_record.attributes[SpanAttributes.CODE_FUNCTION],
138+
"test_log_record_user_attributes",
139+
)
140+
# The line of the log statement is not a constant (changing tests may change that),
141+
# so only check that the attribute is present.
142+
self.assertTrue(SpanAttributes.CODE_LINENO in log_record.attributes)
143+
self.assertTrue(isinstance(log_record.attributes, BoundedAttributes))
142144

143145
def test_log_record_exception(self):
144146
"""Exception information will be included in attributes"""
145-
processor, logger = set_up_test_logging(logging.ERROR)
146-
147-
try:
148-
raise ZeroDivisionError("division by zero")
149-
except ZeroDivisionError:
150-
with self.assertLogs(level=logging.ERROR):
151-
logger.exception("Zero Division Error")
147+
with set_up_test_logging(logging.ERROR) as (processor, logger):
148+
try:
149+
raise ZeroDivisionError("division by zero")
150+
except ZeroDivisionError:
151+
with self.assertLogs(level=logging.ERROR):
152+
logger.exception("Zero Division Error")
152153

153-
log_record = processor.get_log_record(0)
154+
log_record = processor.get_log_record(0)
154155

155-
self.assertIsNotNone(log_record)
156-
self.assertIn("Zero Division Error", log_record.body)
157-
self.assertEqual(
158-
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
159-
ZeroDivisionError.__name__,
160-
)
161-
self.assertEqual(
162-
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
163-
"division by zero",
164-
)
165-
stack_trace = log_record.attributes[
166-
SpanAttributes.EXCEPTION_STACKTRACE
167-
]
168-
self.assertIsInstance(stack_trace, str)
169-
self.assertTrue("Traceback" in stack_trace)
170-
self.assertTrue("ZeroDivisionError" in stack_trace)
171-
self.assertTrue("division by zero" in stack_trace)
172-
self.assertTrue(__file__ in stack_trace)
156+
self.assertIsNotNone(log_record)
157+
self.assertIn("Zero Division Error", log_record.body)
158+
self.assertEqual(
159+
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
160+
ZeroDivisionError.__name__,
161+
)
162+
self.assertEqual(
163+
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
164+
"division by zero",
165+
)
166+
stack_trace = log_record.attributes[
167+
SpanAttributes.EXCEPTION_STACKTRACE
168+
]
169+
self.assertIsInstance(stack_trace, str)
170+
self.assertTrue("Traceback" in stack_trace)
171+
self.assertTrue("ZeroDivisionError" in stack_trace)
172+
self.assertTrue("division by zero" in stack_trace)
173+
self.assertTrue(__file__ in stack_trace)
173174

174175
def test_log_exc_info_false(self):
175176
"""Exception information will be included in attributes"""
176-
processor, logger = set_up_test_logging(logging.NOTSET)
177-
178-
try:
179-
raise ZeroDivisionError("division by zero")
180-
except ZeroDivisionError:
181-
with self.assertLogs(level=logging.ERROR):
182-
logger.error("Zero Division Error", exc_info=False)
177+
with set_up_test_logging(logging.NOTSET) as (processor, logger):
178+
try:
179+
raise ZeroDivisionError("division by zero")
180+
except ZeroDivisionError:
181+
with self.assertLogs(level=logging.ERROR):
182+
logger.error("Zero Division Error", exc_info=False)
183183

184-
log_record = processor.get_log_record(0)
184+
log_record = processor.get_log_record(0)
185185

186-
self.assertIsNotNone(log_record)
187-
self.assertEqual(log_record.body, "Zero Division Error")
188-
self.assertNotIn(SpanAttributes.EXCEPTION_TYPE, log_record.attributes)
189-
self.assertNotIn(
190-
SpanAttributes.EXCEPTION_MESSAGE, log_record.attributes
191-
)
192-
self.assertNotIn(
193-
SpanAttributes.EXCEPTION_STACKTRACE, log_record.attributes
194-
)
186+
self.assertIsNotNone(log_record)
187+
self.assertEqual(log_record.body, "Zero Division Error")
188+
self.assertNotIn(SpanAttributes.EXCEPTION_TYPE, log_record.attributes)
189+
self.assertNotIn(
190+
SpanAttributes.EXCEPTION_MESSAGE, log_record.attributes
191+
)
192+
self.assertNotIn(
193+
SpanAttributes.EXCEPTION_STACKTRACE, log_record.attributes
194+
)
195195

196196
def test_log_record_trace_correlation(self):
197-
processor, logger = set_up_test_logging(logging.WARNING)
197+
with set_up_test_logging(logging.WARNING) as (processor, logger):
198+
tracer = trace.TracerProvider().get_tracer(__name__)
199+
with tracer.start_as_current_span("test") as span:
200+
with self.assertLogs(level=logging.CRITICAL):
201+
logger.critical("Critical message within span")
202+
203+
log_record = processor.get_log_record(0)
204+
205+
self.assertEqual(log_record.body, "Critical message within span")
206+
self.assertEqual(log_record.severity_text, "CRITICAL")
207+
self.assertEqual(log_record.severity_number, SeverityNumber.FATAL)
208+
span_context = span.get_span_context()
209+
self.assertEqual(log_record.trace_id, span_context.trace_id)
210+
self.assertEqual(log_record.span_id, span_context.span_id)
211+
self.assertEqual(log_record.trace_flags, span_context.trace_flags)
198212

199-
tracer = trace.TracerProvider().get_tracer(__name__)
200-
with tracer.start_as_current_span("test") as span:
201-
with self.assertLogs(level=logging.CRITICAL):
202-
logger.critical("Critical message within span")
213+
def test_log_record_args_are_translated(self):
214+
with set_up_test_logging(logging.WARNING) as (processor, logger):
215+
logger.warning("Test message")
203216

204217
log_record = processor.get_log_record(0)
218+
self.assertEqual(
219+
set(log_record.attributes),
220+
{
221+
"code.filepath",
222+
"code.lineno",
223+
"code.function",
224+
},
225+
)
205226

206-
self.assertEqual(log_record.body, "Critical message within span")
207-
self.assertEqual(log_record.severity_text, "CRITICAL")
208-
self.assertEqual(log_record.severity_number, SeverityNumber.FATAL)
209-
span_context = span.get_span_context()
210-
self.assertEqual(log_record.trace_id, span_context.trace_id)
211-
self.assertEqual(log_record.span_id, span_context.span_id)
212-
self.assertEqual(log_record.trace_flags, span_context.trace_flags)
227+
def test_format_is_called(self):
228+
with set_up_test_logging(logging.WARNING, formatter=logging.Formatter("%(name)s - %(levelname)s - %(message)s")) as (processor, logger):
229+
logger.warning("Test message")
213230

214-
def test_log_record_args_are_translated(self):
215-
processor, logger = set_up_test_logging(logging.INFO)
216-
217-
with self.assertLogs(level=logging.INFO):
218-
logger.info("Test message")
219-
220-
log_record = processor.get_log_record(0)
221-
self.assertEqual(
222-
set(log_record.attributes),
223-
{
224-
"thread.id",
225-
"code.filepath",
226-
"code.lineno",
227-
"code.function",
228-
"thread.name",
229-
},
230-
)
231+
log_record = processor.get_log_record(0)
232+
self.assertEqual(
233+
log_record.body, "foo - WARNING - Test message"
234+
)
231235

232-
def test_format_is_called(self):
233-
processor, logger = set_up_test_logging(
234-
logging.INFO,
235-
logging.Formatter("%(name)s - %(levelname)s - %(message)s")
236-
)
236+
def test_log_body_is_always_string(self):
237+
with set_up_test_logging(logging.WARNING) as (processor, logger):
238+
logger.warning(["something", "of", "note"])
237239

238-
with self.assertLogs(level=logging.INFO):
239-
logger.info("Test message")
240+
log_record = processor.get_log_record(0)
241+
self.assertIsInstance(log_record.body, str)
240242

241-
log_record = processor.get_log_record(0)
242-
self.assertEqual(
243-
log_record.body, "foo - INFO - Test message"
244-
)
243+
def test_args_get_transformed_if_logged(self):
244+
with set_up_test_logging(logging.WARNING) as (_, logger):
245245

246-
def test_log_body_is_always_string(self):
247-
processor, logger = set_up_test_logging(logging.INFO)
246+
my_object = MagicMock()
247+
logger.warning("%s - %d", my_object, my_object)
248+
249+
self.assertTrue(my_object.__str__.called)
250+
self.assertTrue(my_object.__int__.called)
251+
252+
def test_args_do_not_get_transformed_if_not_logged(self):
253+
with set_up_test_logging(logging.WARNING) as (_, logger):
248254

249-
with self.assertLogs(level=logging.INFO):
250-
logger.info(["something", "of", "note"])
255+
my_object = MagicMock()
256+
logger.info("%s - %d", my_object, my_object)
251257

252-
log_record = processor.get_log_record(0)
253-
self.assertIsInstance(log_record.body, str)
258+
self.assertFalse(my_object.__str__.called)
259+
self.assertFalse(my_object.__int__.called)
254260

255261

262+
@contextmanager
256263
def set_up_test_logging(level, formatter=None):
257264
logger_provider = LoggerProvider()
258265
processor = FakeProcessor()
@@ -262,7 +269,8 @@ def set_up_test_logging(level, formatter=None):
262269
if formatter:
263270
handler.setFormatter(formatter)
264271
logger.addHandler(handler)
265-
return processor, logger
272+
yield processor, logger
273+
logger.removeHandler(handler)
266274

267275

268276
class FakeProcessor(LogRecordProcessor):

0 commit comments

Comments
 (0)