Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
816ce49
Logs API/SDK accepts additional otel context
tammy-baylis-swi May 22, 2025
4783932
Changelog
tammy-baylis-swi May 22, 2025
5308eee
LoggingHandler translates to LogRecord with current Otel context
tammy-baylis-swi May 22, 2025
a45694c
Add LogRecord init priority for context's span over old span info
tammy-baylis-swi May 22, 2025
f8c56d3
Add LogRecord serialized_context for to_json of arbitrary objects
tammy-baylis-swi May 23, 2025
fa5ec39
Add test coverage
tammy-baylis-swi May 23, 2025
56172fa
Changelog
tammy-baylis-swi May 23, 2025
4b38118
lint
tammy-baylis-swi May 23, 2025
ded31f6
Fix tests
tammy-baylis-swi May 24, 2025
bce5a7e
Changelog
tammy-baylis-swi May 24, 2025
9ff670e
Merge branch 'main' into logs-api-accept-otel-context
tammy-baylis-swi May 27, 2025
73e7b39
Rm Context inclusion from to_json of LogRecord
tammy-baylis-swi May 29, 2025
676d9ff
Revision: logs SDK does get_current, overload init and deprecate trac…
tammy-baylis-swi May 29, 2025
a0c817c
Simplify test
tammy-baylis-swi May 29, 2025
111d8d8
Changelog
tammy-baylis-swi May 29, 2025
bb92b7e
Use typing_extensions deprecated, not custom
tammy-baylis-swi May 30, 2025
0aad430
Merge branch 'main' into logs-api-accept-otel-context
emdneto Jun 4, 2025
172f1c5
Update LogRecord API; simplify test
tammy-baylis-swi Jun 5, 2025
84c6b33
Force logrecord api kwarg-only to avoid param order issues
tammy-baylis-swi Jun 5, 2025
eb28a8d
Add special LogDeprecatedInitWarning that logs once
tammy-baylis-swi Jun 11, 2025
c4c6aac
Merge branch 'main' into logs-api-accept-otel-context
tammy-baylis-swi Jun 11, 2025
04f5da7
Rm deprecated decorator
tammy-baylis-swi Jun 11, 2025
dca0d41
api too
tammy-baylis-swi Jun 11, 2025
52a5e93
catch_warnings instead of assertLogs in test
tammy-baylis-swi Jun 11, 2025
069c2f8
changelog
tammy-baylis-swi Jun 13, 2025
fa6111e
Rm with assertLogs for py3.13 ubuntu test
tammy-baylis-swi Jun 13, 2025
f709952
Merge branch 'main' into logs-api-accept-otel-context
tammy-baylis-swi Jun 13, 2025
87b300c
Merge branch 'main' into logs-api-accept-otel-context
emdneto Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#4621](https://github.com/open-telemetry/opentelemetry-python/pull/4621))
- Fix license field in pyproject.toml files
([#4625](https://github.com/open-telemetry/opentelemetry-python/pull/4625))
- Logging API accepts optional `context`; deprecates `trace_id`, `span_id`, `trace_flags`.
([#4597](https://github.com/open-telemetry/opentelemetry-python/pull/4597))

## Version 1.34.0/0.55b0 (2025-06-04)

Expand Down
36 changes: 34 additions & 2 deletions opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@
from logging import getLogger
from os import environ
from time import time_ns
from typing import Optional, cast
from typing import Optional, cast, overload

from opentelemetry._logs.severity import SeverityNumber
from opentelemetry.context.context import Context
from opentelemetry.environment_variables import _OTEL_PYTHON_LOGGER_PROVIDER
from opentelemetry.trace.span import TraceFlags
from opentelemetry.util._once import Once
Expand All @@ -57,8 +58,23 @@ class LogRecord(ABC):
pertinent to the event being logged.
"""

@overload
def __init__(
self,
*,
timestamp: Optional[int] = None,
observed_timestamp: Optional[int] = None,
context: Optional[Context] = None,
severity_text: Optional[str] = None,
severity_number: Optional[SeverityNumber] = None,
body: AnyValue = None,
attributes: Optional[_ExtendedAttributes] = None,
) -> None: ...

@overload
def __init__(
self,
*,
timestamp: Optional[int] = None,
observed_timestamp: Optional[int] = None,
trace_id: Optional[int] = None,
Expand All @@ -68,11 +84,27 @@ def __init__(
severity_number: Optional[SeverityNumber] = None,
body: AnyValue = None,
attributes: Optional[_ExtendedAttributes] = None,
):
) -> None: ...

def __init__(
self,
*,
timestamp: Optional[int] = None,
observed_timestamp: Optional[int] = None,
context: Optional[Context] = None,
trace_id: Optional[int] = None,
span_id: Optional[int] = None,
trace_flags: Optional["TraceFlags"] = None,
severity_text: Optional[str] = None,
severity_number: Optional[SeverityNumber] = None,
body: AnyValue = None,
attributes: Optional[_ExtendedAttributes] = None,
) -> None:
self.timestamp = timestamp
if observed_timestamp is None:
observed_timestamp = time_ns()
self.observed_timestamp = observed_timestamp
self.context = context
self.trace_id = trace_id
self.span_id = span_id
self.trace_flags = trace_flags
Expand Down
2 changes: 2 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from opentelemetry.sdk._logs._internal import (
LogData,
LogDeprecatedInitWarning,
LogDroppedAttributesWarning,
Logger,
LoggerProvider,
Expand All @@ -32,5 +33,6 @@
"LogLimits",
"LogRecord",
"LogRecordProcessor",
"LogDeprecatedInitWarning",
"LogDroppedAttributesWarning",
]
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from os import environ
from threading import Lock
from time import time_ns
from typing import Any, Callable, Tuple, Union, cast # noqa
from typing import Any, Callable, Tuple, Union, cast, overload # noqa

from opentelemetry._logs import Logger as APILogger
from opentelemetry._logs import LoggerProvider as APILoggerProvider
Expand All @@ -38,6 +38,8 @@
std_to_otel,
)
from opentelemetry.attributes import _VALID_ANY_VALUE_TYPES, BoundedAttributes
from opentelemetry.context import get_current
from opentelemetry.context.context import Context
from opentelemetry.sdk.environment_variables import (
OTEL_ATTRIBUTE_COUNT_LIMIT,
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
Expand Down Expand Up @@ -81,6 +83,18 @@ class LogDroppedAttributesWarning(UserWarning):
warnings.simplefilter("once", LogDroppedAttributesWarning)


class LogDeprecatedInitWarning(UserWarning):
"""Custom warning to indicate deprecated LogRecord init was used.

This class is used to filter and handle these specific warnings separately
from other warnings, ensuring that they are only shown once without
interfering with default user warnings.
"""


warnings.simplefilter("once", LogDeprecatedInitWarning)


class LogLimits:
"""This class is based on a SpanLimits class in the Tracing module.

Expand Down Expand Up @@ -180,6 +194,21 @@ class LogRecord(APILogRecord):
pertinent to the event being logged.
"""

@overload
def __init__(
self,
timestamp: int | None = None,
observed_timestamp: int | None = None,
context: Context | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = _UnsetLogLimits,
): ...

@overload
def __init__(
self,
timestamp: int | None = None,
Expand All @@ -193,11 +222,46 @@ def __init__(
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = _UnsetLogLimits,
): ...

def __init__(
self,
timestamp: int | None = None,
observed_timestamp: int | None = None,
context: Context | None = None,
trace_id: int | None = None,
span_id: int | None = None,
trace_flags: TraceFlags | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = _UnsetLogLimits,
):
if trace_id or span_id or trace_flags:
warnings.warn(
"LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated. Use `context` instead.",
LogDeprecatedInitWarning,
stacklevel=2,
)

if not context:
context = get_current()

if context is not None:
span = get_current_span(context)
span_context = span.get_span_context()
if span_context.is_valid:
trace_id = span_context.trace_id
span_id = span_context.span_id
trace_flags = span_context.trace_flags

super().__init__(
**{
"timestamp": timestamp,
"observed_timestamp": observed_timestamp,
"context": context,
"trace_id": trace_id,
"span_id": span_id,
"trace_flags": trace_flags,
Expand Down
3 changes: 1 addition & 2 deletions opentelemetry-sdk/tests/logs/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ def test_simple_log_record_processor_shutdown(self):
)
exporter.clear()
logger_provider.shutdown()
with self.assertLogs(level=logging.WARNING):
logger.warning("Log after shutdown")
logger.warning("Log after shutdown")
finished_logs = exporter.get_finished_logs()
self.assertEqual(len(finished_logs), 0)

Expand Down
36 changes: 35 additions & 1 deletion opentelemetry-sdk/tests/logs/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
)
from opentelemetry.semconv._incubating.attributes import code_attributes
from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import INVALID_SPAN_CONTEXT
from opentelemetry.trace import (
INVALID_SPAN_CONTEXT,
set_span_in_context,
)


class TestLoggingHandler(unittest.TestCase):
Expand Down Expand Up @@ -269,6 +272,37 @@ def __str__(self):
def test_log_record_trace_correlation(self):
processor, logger = set_up_test_logging(logging.WARNING)

tracer = trace.TracerProvider().get_tracer(__name__)
with tracer.start_as_current_span("test") as span:
mock_context = set_span_in_context(span)

with patch(
"opentelemetry.sdk._logs._internal.get_current",
return_value=mock_context,
):
with self.assertLogs(level=logging.CRITICAL):
logger.critical("Critical message within span")

log_record = processor.get_log_record(0)

self.assertEqual(
log_record.body, "Critical message within span"
)
self.assertEqual(log_record.severity_text, "CRITICAL")
self.assertEqual(
log_record.severity_number, SeverityNumber.FATAL
)
self.assertEqual(log_record.context, mock_context)
span_context = span.get_span_context()
self.assertEqual(log_record.trace_id, span_context.trace_id)
self.assertEqual(log_record.span_id, span_context.span_id)
self.assertEqual(
log_record.trace_flags, span_context.trace_flags
)

def test_log_record_trace_correlation_deprecated(self):
processor, logger = set_up_test_logging(logging.WARNING)

tracer = trace.TracerProvider().get_tracer(__name__)
with tracer.start_as_current_span("test") as span:
with self.assertLogs(level=logging.CRITICAL):
Expand Down
28 changes: 28 additions & 0 deletions opentelemetry-sdk/tests/logs/test_log_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@

from opentelemetry._logs.severity import SeverityNumber
from opentelemetry.attributes import BoundedAttributes
from opentelemetry.context import get_current
from opentelemetry.sdk._logs import (
LogDeprecatedInitWarning,
LogDroppedAttributesWarning,
LogLimits,
LogRecord,
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.trace.span import TraceFlags


class TestLogRecord(unittest.TestCase):
Expand Down Expand Up @@ -143,3 +146,28 @@ def test_log_record_dropped_attributes_unset_limits(self):
)
self.assertTrue(result.dropped_attributes == 0)
self.assertEqual(attr, result.attributes)

def test_log_record_deprecated_init_warning(self):
test_cases = [
{"trace_id": 123},
{"span_id": 123},
{"trace_flags": TraceFlags(0x01)},
]

for params in test_cases:
with self.subTest(params=params):
with warnings.catch_warnings(record=True) as cw:
for _ in range(10):
LogRecord(**params)

self.assertEqual(len(cw), 1)
self.assertIsInstance(cw[-1].message, LogDeprecatedInitWarning)
self.assertIn(
"LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated. Use `context` instead.",
str(cw[-1].message),
)

with warnings.catch_warnings(record=True) as cw:
for _ in range(10):
LogRecord(context=get_current())
self.assertEqual(len(cw), 0)