Skip to content

Commit f1de47f

Browse files
xrmxlzchen
andauthored
Warnings before removals (#4771)
* WIP warnings before deprecations and renames * different classes * tests * lint * Keep using one warning * Update changelog * Feedback: drop warnings that have a deprecations * Update logrecord warning message * Use default stacklevel * Silence deprecation warnings from internal users * Add test for logdata warnings * WIP NOT WORKING capture deprecations warnings in EventLogger.emit * Use stacklevel=2 for getting the caller --------- Co-authored-by: Leighton Chen <lechen@microsoft.com>
1 parent 1797c1d commit f1de47f

File tree

5 files changed

+167
-44
lines changed

5 files changed

+167
-44
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
([#4755](https://github.com/open-telemetry/opentelemetry-python/pull/4755))
1717
- logs: extend Logger.emit to accept separated keyword arguments
1818
([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737))
19+
- logs: add warnings for classes that would be deprecated and renamed in 1.39.0
20+
([#4771](https://github.com/open-telemetry/opentelemetry-python/pull/4771))
1921

2022
## Version 1.37.0/0.58b0 (2025-09-11)
2123

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

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
1415
import logging
16+
import warnings
1517
from time import time_ns
1618
from typing import Optional
1719

@@ -20,7 +22,12 @@
2022
from opentelemetry._events import EventLogger as APIEventLogger
2123
from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider
2224
from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider
23-
from opentelemetry.sdk._logs import Logger, LoggerProvider, LogRecord
25+
from opentelemetry.sdk._logs import (
26+
LogDeprecatedInitWarning,
27+
Logger,
28+
LoggerProvider,
29+
LogRecord,
30+
)
2431
from opentelemetry.util.types import _ExtendedAttributes
2532

2633
_logger = logging.getLogger(__name__)
@@ -50,18 +57,23 @@ def emit(self, event: Event) -> None:
5057
# Do nothing if SDK is disabled
5158
return
5259
span_context = trace.get_current_span().get_span_context()
53-
log_record = LogRecord(
54-
timestamp=event.timestamp or time_ns(),
55-
observed_timestamp=None,
56-
trace_id=event.trace_id or span_context.trace_id,
57-
span_id=event.span_id or span_context.span_id,
58-
trace_flags=event.trace_flags or span_context.trace_flags,
59-
severity_text=None,
60-
severity_number=event.severity_number or SeverityNumber.INFO,
61-
body=event.body,
62-
resource=getattr(self._logger, "resource", None),
63-
attributes=event.attributes,
64-
)
60+
61+
# silence deprecation warnings from internal users
62+
with warnings.catch_warnings():
63+
warnings.simplefilter("ignore", category=LogDeprecatedInitWarning)
64+
65+
log_record = LogRecord(
66+
timestamp=event.timestamp or time_ns(),
67+
observed_timestamp=None,
68+
trace_id=event.trace_id or span_context.trace_id,
69+
span_id=event.span_id or span_context.span_id,
70+
trace_flags=event.trace_flags or span_context.trace_flags,
71+
severity_text=None,
72+
severity_number=event.severity_number or SeverityNumber.INFO,
73+
body=event.body,
74+
resource=getattr(self._logger, "resource", None),
75+
attributes=event.attributes,
76+
)
6577
self._logger.emit(log_record)
6678

6779

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class LogDroppedAttributesWarning(UserWarning):
8585

8686

8787
class LogDeprecatedInitWarning(UserWarning):
88-
"""Custom warning to indicate deprecated LogRecord init was used.
88+
"""Custom warning to indicate that deprecated and soon to be deprecated Log classes was used.
8989
9090
This class is used to filter and handle these specific warnings separately
9191
from other warnings, ensuring that they are only shown once without
@@ -234,6 +234,11 @@ def __init__( # pylint:disable=too-many-locals
234234
limits: LogLimits | None = None,
235235
event_name: str | None = None,
236236
):
237+
warnings.warn(
238+
"LogRecord will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
239+
LogDeprecatedInitWarning,
240+
stacklevel=2,
241+
)
237242
if not context:
238243
context = get_current()
239244

@@ -358,6 +363,11 @@ def __init__(
358363
log_record: LogRecord,
359364
instrumentation_scope: InstrumentationScope,
360365
):
366+
warnings.warn(
367+
"LogData will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
368+
LogDeprecatedInitWarning,
369+
stacklevel=2,
370+
)
361371
self.log_record = log_record
362372
self.instrumentation_scope = instrumentation_scope
363373

@@ -727,25 +737,28 @@ def emit(
727737
and instrumentation info.
728738
"""
729739

730-
if not record:
731-
record = LogRecord(
732-
timestamp=timestamp,
733-
observed_timestamp=observed_timestamp,
734-
context=context,
735-
severity_text=severity_text,
736-
severity_number=severity_number,
737-
body=body,
738-
attributes=attributes,
739-
event_name=event_name,
740-
resource=self._resource,
741-
)
742-
elif not isinstance(record, LogRecord):
743-
# pylint:disable=protected-access
744-
record = LogRecord._from_api_log_record(
745-
record=record, resource=self._resource
746-
)
740+
# silence deprecation warnings from internal users
741+
with warnings.catch_warnings():
742+
warnings.simplefilter("ignore", category=LogDeprecatedInitWarning)
743+
if not record:
744+
record = LogRecord(
745+
timestamp=timestamp,
746+
observed_timestamp=observed_timestamp,
747+
context=context,
748+
severity_text=severity_text,
749+
severity_number=severity_number,
750+
body=body,
751+
attributes=attributes,
752+
event_name=event_name,
753+
resource=self._resource,
754+
)
755+
elif not isinstance(record, LogRecord):
756+
# pylint:disable=protected-access
757+
record = LogRecord._from_api_log_record(
758+
record=record, resource=self._resource
759+
)
747760

748-
log_data = LogData(record, self._instrumentation_scope)
761+
log_data = LogData(record, self._instrumentation_scope)
749762

750763
self._multi_log_record_processor.on_emit(log_data)
751764

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
detach,
2727
set_value,
2828
)
29-
from opentelemetry.sdk._logs import LogData, LogRecord, LogRecordProcessor
29+
from opentelemetry.sdk._logs import (
30+
LogData,
31+
LogRecord,
32+
LogRecordProcessor,
33+
)
3034
from opentelemetry.sdk._shared_internal import BatchProcessor, DuplicateFilter
3135
from opentelemetry.sdk.environment_variables import (
3236
OTEL_BLRP_EXPORT_TIMEOUT,

opentelemetry-sdk/tests/logs/test_log_record.py

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
from opentelemetry.attributes import BoundedAttributes
2222
from opentelemetry.context import get_current
2323
from opentelemetry.sdk._logs import (
24+
LogData,
2425
LogDeprecatedInitWarning,
2526
LogDroppedAttributesWarning,
2627
LogLimits,
2728
LogRecord,
2829
)
2930
from opentelemetry.sdk.resources import Resource
31+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
3032
from opentelemetry.trace.span import TraceFlags
3133

3234

@@ -142,11 +144,22 @@ def test_log_record_dropped_attributes_set_limits_warning_once(self):
142144
attributes=attr,
143145
limits=limits,
144146
)
145-
self.assertEqual(len(cw), 1)
146-
self.assertIsInstance(cw[-1].message, LogDroppedAttributesWarning)
147+
148+
# Check that at least one LogDroppedAttributesWarning was emitted
149+
dropped_attributes_warnings = [
150+
w for w in cw if isinstance(w.message, LogDroppedAttributesWarning)
151+
]
152+
self.assertEqual(
153+
len(dropped_attributes_warnings),
154+
1,
155+
"Expected exactly one LogDroppedAttributesWarning due to simplefilter('once')",
156+
)
157+
158+
# Check the message content of the LogDroppedAttributesWarning
159+
warning_message = str(dropped_attributes_warnings[0].message)
147160
self.assertIn(
148161
"Log record attributes were dropped due to limits",
149-
str(cw[-1].message),
162+
warning_message,
150163
)
151164

152165
def test_log_record_dropped_attributes_unset_limits(self):
@@ -159,7 +172,7 @@ def test_log_record_dropped_attributes_unset_limits(self):
159172
self.assertTrue(result.dropped_attributes == 0)
160173
self.assertEqual(attr, result.attributes)
161174

162-
def test_log_record_deprecated_init_warning(self):
175+
def test_log_record_context_deprecated_init_warning(self):
163176
test_cases = [
164177
{"trace_id": 123},
165178
{"span_id": 123},
@@ -172,17 +185,66 @@ def test_log_record_deprecated_init_warning(self):
172185
for _ in range(10):
173186
LogRecord(**params)
174187

175-
self.assertEqual(len(cw), 1)
176-
self.assertIsInstance(cw[-1].message, LogDeprecatedInitWarning)
177-
self.assertIn(
178-
"LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead.",
179-
str(cw[-1].message),
180-
)
188+
# Check that the LogDeprecatedInitWarning was emitted
189+
context_deprecated_warnings = [
190+
w
191+
for w in cw
192+
if isinstance(w.message, LogDeprecatedInitWarning)
193+
]
194+
self.assertEqual(len(context_deprecated_warnings), 2)
195+
196+
# Check we have the expected message once
197+
log_record_context_warning = [
198+
w.message
199+
for w in cw
200+
if "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead."
201+
in str(w.message)
202+
]
203+
204+
self.assertEqual(len(log_record_context_warning), 1)
181205

182206
with warnings.catch_warnings(record=True) as cw:
183207
for _ in range(10):
184208
LogRecord(context=get_current())
185-
self.assertEqual(len(cw), 0)
209+
210+
# Check that no LogDeprecatedInitWarning was emitted when using context
211+
context_deprecated_warnings = [
212+
w for w in cw if isinstance(w.message, LogDeprecatedInitWarning)
213+
]
214+
self.assertEqual(len(context_deprecated_warnings), 1)
215+
216+
# Check we have no message
217+
log_record_context_warning = [
218+
w.message
219+
for w in cw
220+
if "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead."
221+
in str(w.message)
222+
]
223+
224+
self.assertEqual(len(log_record_context_warning), 0)
225+
226+
def test_log_record_init_deprecated_warning(self):
227+
"""Test that LogRecord initialization emits a LogDeprecatedInitWarning."""
228+
with warnings.catch_warnings(record=True) as cw:
229+
warnings.simplefilter("always")
230+
LogRecord()
231+
232+
# Check that at least one LogDeprecatedInitWarning was emitted
233+
log_record_init_warnings = [
234+
w for w in cw if isinstance(w.message, LogDeprecatedInitWarning)
235+
]
236+
self.assertGreater(
237+
len(log_record_init_warnings),
238+
0,
239+
"Expected at least one LogDeprecatedInitWarning",
240+
)
241+
242+
# Check the message content of the LogDeprecatedInitWarning
243+
warning_message = str(log_record_init_warnings[0].message)
244+
self.assertIn(
245+
"LogRecord will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
246+
warning_message,
247+
)
186248

187249
# pylint:disable=protected-access
188250
def test_log_record_from_api_log_record(self):
@@ -217,3 +279,33 @@ def test_log_record_from_api_log_record(self):
217279
self.assertEqual(record.attributes, {"a": "b"})
218280
self.assertEqual(record.event_name, "an.event")
219281
self.assertEqual(record.resource, resource)
282+
283+
284+
class TestLogData(unittest.TestCase):
285+
def test_init_deprecated_warning(self):
286+
"""Test that LogData initialization emits a LogDeprecatedInitWarning."""
287+
log_record = LogRecord()
288+
289+
with warnings.catch_warnings(record=True) as cw:
290+
warnings.simplefilter("always")
291+
LogData(
292+
log_record=log_record,
293+
instrumentation_scope=InstrumentationScope("foo", "bar"),
294+
)
295+
296+
# Check that at least one LogDeprecatedInitWarning was emitted
297+
init_warnings = [
298+
w for w in cw if isinstance(w.message, LogDeprecatedInitWarning)
299+
]
300+
self.assertGreater(
301+
len(init_warnings),
302+
0,
303+
"Expected at least one LogDeprecatedInitWarning",
304+
)
305+
306+
# Check the message content of the LogDeprecatedInitWarning
307+
warning_message = str(init_warnings[0].message)
308+
self.assertIn(
309+
"LogData will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
310+
warning_message,
311+
)

0 commit comments

Comments
 (0)