Skip to content

Commit 834143b

Browse files
committed
opentelemetry-instrumentation-logging: don't add out of spec attributes by default
Add span context attributes to logging module LogRecord only if the instrumentation has been configured to do so via set_logging_format argument or OTEL_PYTHON_LOG_CORRELATION environment variable.
1 parent 1bbb508 commit 834143b

2 files changed

Lines changed: 104 additions & 72 deletions

File tree

instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ class LoggingInstrumentor(BaseInstrumentor): # pylint: disable=empty-docstring
9797
{DEFAULT_LOGGING_FORMAT}
9898
9999
def log_hook(span: Span, record: LogRecord):
100-
if span and span.is_recording():
101-
record.custom_user_attribute_from_log_hook = "some-value"
100+
if span and span.is_recording():
101+
record.custom_user_attribute_from_log_hook = "some-value"
102102
103103
Args:
104104
tracer_provider: Tracer provider instance that can be used to fetch a tracer.
@@ -130,32 +130,56 @@ def _instrument(self, **kwargs):
130130

131131
service_name = None
132132

133-
def record_factory(*args, **kwargs):
134-
record = old_factory(*args, **kwargs)
133+
set_logging_format = kwargs.get(
134+
"set_logging_format",
135+
environ.get(OTEL_PYTHON_LOG_CORRELATION, "false").lower()
136+
== "true",
137+
)
135138

136-
record.otelSpanID = "0"
137-
record.otelTraceID = "0"
138-
record.otelTraceSampled = False
139+
if set_logging_format:
140+
log_format = kwargs.get(
141+
"logging_format", environ.get(OTEL_PYTHON_LOG_FORMAT, None)
142+
)
143+
log_format = log_format or DEFAULT_LOGGING_FORMAT
144+
145+
log_level = kwargs.get(
146+
"log_level", LEVELS.get(environ.get(OTEL_PYTHON_LOG_LEVEL))
147+
)
148+
log_level = log_level or logging.INFO
149+
150+
logging.basicConfig(format=log_format, level=log_level)
139151

140-
nonlocal service_name
141-
if service_name is None:
142-
resource = getattr(provider, "resource", None)
143-
if resource:
144-
service_name = (
145-
resource.attributes.get("service.name") or ""
146-
)
147-
else:
148-
service_name = ""
152+
def record_factory(*args, **kwargs):
153+
record = old_factory(*args, **kwargs)
149154

150-
record.otelServiceName = service_name
155+
# this factory is a no-op if log correlation or log hook are not set
156+
if not set_logging_format and not callable(
157+
LoggingInstrumentor._log_hook
158+
):
159+
return record
160+
161+
# out of spec attributes are added to the log record only if log correlation is specified
162+
if set_logging_format:
163+
record.otelSpanID = "0"
164+
record.otelTraceID = "0"
165+
record.otelTraceSampled = False
166+
167+
nonlocal service_name
168+
if service_name is None:
169+
resource = getattr(provider, "resource", None)
170+
if resource:
171+
service_name = (
172+
resource.attributes.get("service.name") or ""
173+
)
174+
else:
175+
service_name = ""
176+
177+
record.otelServiceName = service_name
151178

152179
span = get_current_span()
153180
if span != INVALID_SPAN:
154181
ctx = span.get_span_context()
155182
if ctx != INVALID_SPAN_CONTEXT:
156-
record.otelSpanID = format(ctx.span_id, "016x")
157-
record.otelTraceID = format(ctx.trace_id, "032x")
158-
record.otelTraceSampled = ctx.trace_flags.sampled
159183
if callable(LoggingInstrumentor._log_hook):
160184
try:
161185
LoggingInstrumentor._log_hook( # pylint: disable=E1102
@@ -164,29 +188,15 @@ def record_factory(*args, **kwargs):
164188
except Exception: # pylint: disable=W0703
165189
pass
166190

191+
if set_logging_format:
192+
record.otelSpanID = format(ctx.span_id, "016x")
193+
record.otelTraceID = format(ctx.trace_id, "032x")
194+
record.otelTraceSampled = ctx.trace_flags.sampled
195+
167196
return record
168197

169198
logging.setLogRecordFactory(record_factory)
170199

171-
set_logging_format = kwargs.get(
172-
"set_logging_format",
173-
environ.get(OTEL_PYTHON_LOG_CORRELATION, "false").lower()
174-
== "true",
175-
)
176-
177-
if set_logging_format:
178-
log_format = kwargs.get(
179-
"logging_format", environ.get(OTEL_PYTHON_LOG_FORMAT, None)
180-
)
181-
log_format = log_format or DEFAULT_LOGGING_FORMAT
182-
183-
log_level = kwargs.get(
184-
"log_level", LEVELS.get(environ.get(OTEL_PYTHON_LOG_LEVEL))
185-
)
186-
log_level = log_level or logging.INFO
187-
188-
logging.basicConfig(format=log_format, level=log_level)
189-
190200
def _uninstrument(self, **kwargs):
191201
if LoggingInstrumentor._old_factory:
192202
logging.setLogRecordFactory(LoggingInstrumentor._old_factory)

instrumentation/opentelemetry-instrumentation-logging/tests/test_logging.py

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def inject_fixtures(self, caplog):
4747

4848
def setUp(self):
4949
super().setUp()
50-
LoggingInstrumentor().instrument(tracer_provider=FakeTracerProvider())
50+
LoggingInstrumentor().instrument(
51+
tracer_provider=FakeTracerProvider(), set_logging_format=True
52+
)
5153

5254
def tearDown(self):
5355
super().tearDown()
@@ -94,7 +96,21 @@ def assert_trace_context_injected(self, span_id, trace_id, trace_sampled):
9496
self.assertEqual(record.otelTraceSampled, trace_sampled)
9597
self.assertEqual(record.otelServiceName, "unknown_service")
9698

97-
def test_trace_context_injection(self):
99+
@mock.patch.dict("os.environ", {"OTEL_PYTHON_LOG_CORRELATION": "true"})
100+
def test_trace_context_injection_with_log_correlation_from_env_var(self):
101+
LoggingInstrumentor().uninstrument()
102+
LoggingInstrumentor().instrument()
103+
with self.tracer.start_as_current_span("s1") as span:
104+
span_id = format(span.get_span_context().span_id, "016x")
105+
trace_id = format(span.get_span_context().trace_id, "032x")
106+
trace_sampled = span.get_span_context().trace_flags.sampled
107+
self.assert_trace_context_injected(
108+
span_id, trace_id, trace_sampled
109+
)
110+
111+
def test_trace_context_injection_with_log_correlation_instrument_arg(self):
112+
LoggingInstrumentor().uninstrument()
113+
LoggingInstrumentor().instrument(set_logging_format=True)
98114
with self.tracer.start_as_current_span("s1") as span:
99115
span_id = format(span.get_span_context().span_id, "016x")
100116
trace_id = format(span.get_span_context().trace_id, "032x")
@@ -104,6 +120,8 @@ def test_trace_context_injection(self):
104120
)
105121

106122
def test_trace_context_injection_without_span(self):
123+
LoggingInstrumentor().uninstrument()
124+
LoggingInstrumentor().instrument(set_logging_format=True)
107125
self.assert_trace_context_injected("0", "0", False)
108126

109127
@mock.patch("logging.basicConfig")
@@ -113,15 +131,13 @@ def test_basic_config_called(self, basic_config_mock):
113131
self.assertFalse(basic_config_mock.called)
114132
LoggingInstrumentor().uninstrument()
115133

116-
env_patch = mock.patch.dict(
134+
with mock.patch.dict(
117135
"os.environ", {"OTEL_PYTHON_LOG_CORRELATION": "true"}
118-
)
119-
env_patch.start()
120-
LoggingInstrumentor().instrument()
121-
basic_config_mock.assert_called_with(
122-
format=DEFAULT_LOGGING_FORMAT, level=logging.INFO
123-
)
124-
env_patch.stop()
136+
):
137+
LoggingInstrumentor().instrument()
138+
basic_config_mock.assert_called_with(
139+
format=DEFAULT_LOGGING_FORMAT, level=logging.INFO
140+
)
125141

126142
@mock.patch("logging.basicConfig")
127143
def test_custom_format_and_level_env(self, basic_config_mock):
@@ -130,20 +146,18 @@ def test_custom_format_and_level_env(self, basic_config_mock):
130146
self.assertFalse(basic_config_mock.called)
131147
LoggingInstrumentor().uninstrument()
132148

133-
env_patch = mock.patch.dict(
149+
with mock.patch.dict(
134150
"os.environ",
135151
{
136152
"OTEL_PYTHON_LOG_CORRELATION": "true",
137153
"OTEL_PYTHON_LOG_FORMAT": "%(message)s %(otelSpanID)s",
138154
"OTEL_PYTHON_LOG_LEVEL": "error",
139155
},
140-
)
141-
env_patch.start()
142-
LoggingInstrumentor().instrument()
143-
basic_config_mock.assert_called_with(
144-
format="%(message)s %(otelSpanID)s", level=logging.ERROR
145-
)
146-
env_patch.stop()
156+
):
157+
LoggingInstrumentor().instrument()
158+
basic_config_mock.assert_called_with(
159+
format="%(message)s %(otelSpanID)s", level=logging.ERROR
160+
)
147161

148162
@mock.patch("logging.basicConfig")
149163
def test_custom_format_and_level_api(self, basic_config_mock): # pylint: disable=no-self-use
@@ -158,6 +172,25 @@ def test_custom_format_and_level_api(self, basic_config_mock): # pylint: disabl
158172
)
159173

160174
def test_log_hook(self):
175+
LoggingInstrumentor().uninstrument()
176+
LoggingInstrumentor().instrument(
177+
log_hook=log_hook,
178+
)
179+
with self.tracer.start_as_current_span("s1"):
180+
with self.caplog.at_level(level=logging.INFO):
181+
logger = logging.getLogger("test logger")
182+
logger.info("hello")
183+
self.assertEqual(len(self.caplog.records), 1)
184+
record = self.caplog.records[0]
185+
self.assertFalse(hasattr(record, "otelSpanID"))
186+
self.assertFalse(hasattr(record, "otelTraceID"))
187+
self.assertFalse(hasattr(record, "otelServiceName"))
188+
self.assertFalse(hasattr(record, "otelTraceSampled"))
189+
self.assertEqual(
190+
record.custom_user_attribute_from_log_hook, "some-value"
191+
)
192+
193+
def test_log_hook_with_set_logging_format(self):
161194
LoggingInstrumentor().uninstrument()
162195
LoggingInstrumentor().instrument(
163196
set_logging_format=True,
@@ -181,21 +214,8 @@ def test_log_hook(self):
181214
)
182215

183216
def test_uninstrumented(self):
184-
with self.tracer.start_as_current_span("s1") as span:
185-
span_id = format(span.get_span_context().span_id, "016x")
186-
trace_id = format(span.get_span_context().trace_id, "032x")
187-
trace_sampled = span.get_span_context().trace_flags.sampled
188-
self.assert_trace_context_injected(
189-
span_id, trace_id, trace_sampled
190-
)
191-
192217
LoggingInstrumentor().uninstrument()
193-
194-
self.caplog.clear()
195-
with self.tracer.start_as_current_span("s1") as span:
196-
span_id = format(span.get_span_context().span_id, "016x")
197-
trace_id = format(span.get_span_context().trace_id, "032x")
198-
trace_sampled = span.get_span_context().trace_flags.sampled
218+
with self.tracer.start_as_current_span("s1"):
199219
with self.caplog.at_level(level=logging.INFO):
200220
logger = logging.getLogger("test logger")
201221
logger.info("hello")
@@ -208,7 +228,9 @@ def test_uninstrumented(self):
208228

209229
def test_no_op_tracer_provider(self):
210230
LoggingInstrumentor().uninstrument()
211-
LoggingInstrumentor().instrument(tracer_provider=NoOpTracerProvider())
231+
LoggingInstrumentor().instrument(
232+
tracer_provider=NoOpTracerProvider(), set_logging_format=True
233+
)
212234

213235
with self.caplog.at_level(level=logging.INFO):
214236
logger = logging.getLogger("test logger")

0 commit comments

Comments
 (0)