Skip to content

Add logger exception support for logs API/SDK#4908

Open
iblancasa wants to merge 15 commits intoopen-telemetry:mainfrom
iblancasa:4907
Open

Add logger exception support for logs API/SDK#4908
iblancasa wants to merge 15 commits intoopen-telemetry:mainfrom
iblancasa:4907

Conversation

@iblancasa
Copy link

Description

Fixes #4907

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Unit tests

Does This PR Require a Contrib Repo Change?

  • Yes. - Link to PR:
  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added

@iblancasa iblancasa requested a review from a team as a code owner February 11, 2026 17:28
@iblancasa iblancasa marked this pull request as draft February 11, 2026 17:34
Signed-off-by: Israel Blancas <iblancasa@gmail.com>
@xrmx
Copy link
Contributor

xrmx commented Feb 12, 2026

Haven't looked in detail at the PR but we are usually cautious on adding in development stuff to the api / sdk.

@trask
Copy link
Member

trask commented Feb 13, 2026

hey @xrmx! check out open-telemetry/opentelemetry-specification#4886

if you can give this PR a basic review and "lgtm" without approving/merging it, then we can treat it as a "qualifying prototype" for marking it stable in the spec. we already have 3 qualifying prototypes which is the min bar for stabilizing the spec, but the more the better!

Copy link
Member

@MikeGoldsmith MikeGoldsmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

github-merge-queue bot pushed a commit to open-telemetry/opentelemetry-specification that referenced this pull request Feb 18, 2026
Fixes #4858

Implementations / Prototypes:

- Java:
[`ExtendedLogRecordBuilder.setException(Throwable)`](https://github.com/open-telemetry/opentelemetry-java/blob/main/api/incubator/src/main/java/io/opentelemetry/api/incubator/logs/ExtendedLogRecordBuilder.java#L137)
- Builder method called before `emit()`
  - Merged and available to users
- .NET:
[`LogRecordAttributeList.RecordException(Exception)`](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Api/Logs/LogRecordAttributeList.cs#L191-L198)
- Attribute list method called before `EmitLog()`
- JavaScript:
[opentelemetry-js#6385](open-telemetry/opentelemetry-js#6385)
- Field on LogRecord passed to `emit()`
- Python:
[opentelemetry-python#4908](open-telemetry/opentelemetry-python#4908)
- Parameter on LogRecord passed to `emit()`

(only the Python prototype doesn't yet meet the definition of
[qualifying
prototype](https://github.com/open-telemetry/opentelemetry-specification/blob/main/CONTRIBUTING.md#proposing-a-change)
yet)

Co-authored-by: Robert Pająk <pellared@hotmail.com>
@xrmx
Copy link
Contributor

xrmx commented Feb 18, 2026

hey @xrmx! check out open-telemetry/opentelemetry-specification#4886

if you can give this PR a basic review and "lgtm" without approving/merging it, then we can treat it as a "qualifying prototype" for marking it stable in the spec. we already have 3 qualifying prototypes which is the min bar for stabilizing the spec, but the more the better!

Missed this comment but already approved the other PR 😅

Copy link

@nagkumar91 nagkumar91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice implementation — spec compliance looks solid. A couple of nits below.

body: AnyValue | None = None,
attributes: _ExtendedAttributes | None = None,
event_name: str | None = None,
exception: BaseException | None = None,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: When record is provided alongside an exception kwarg, the exception is silently dropped here (only forwarded in the else branch). The SDK's Logger.emit() explicitly handles both (exception or getattr(record, "exception", None)), so there's a subtle inconsistency between the proxy and SDK paths.

Not a contract violation (the overloads define them as separate call forms), but it might be worth either forwarding exception here too (self._logger.emit(record, exception=exception)) or adding a comment explaining why it's intentionally omitted.

)
self.assertIn(
"ValueError: boom",
attributes[exception_attributes.EXCEPTION_STACKTRACE],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: All tests create exceptions without raising them, so __traceback__ is None and traceback.format_exception() produces minimal output with no actual stack frames. Consider adding at least one test that raises and catches an exception to verify real stacktrace formatting:

def test_emit_with_raised_exception_has_stacktrace(self):
    logger, log_record_processor_mock = self._get_logger()
    try:
        raise ValueError("boom")
    except ValueError as exc:
        logger.emit(body="error", exception=exc)
    log_data = log_record_processor_mock.on_emit.call_args.args[0]
    stacktrace = dict(log_data.log_record.attributes)[exception_attributes.EXCEPTION_STACKTRACE]
    self.assertIn("Traceback (most recent call last)", stacktrace)
    self.assertIn("raise ValueError", stacktrace)

"""
# If a record is provided, use it directly
if record is not None:
record_exception = exception or getattr(record, "exception", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm not a huge fan of modifying attributes of an already created log record. For example, if the attributes in the created log record are immutable, the _apply_exception_attributes function will raise an exception at runtime.

Comment on lines +545 to +558
attributes = log_record.attributes
if attributes:
if isinstance(attributes, BoundedAttributes):
for key, value in exception_attributes_map.items():
if key not in attributes:
attributes[key] = value
return
merged = dict(attributes)
for key, value in exception_attributes_map.items():
merged.setdefault(key, value)
log_record.attributes = merged
return

log_record.attributes = exception_attributes_map
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
attributes = log_record.attributes
if attributes:
if isinstance(attributes, BoundedAttributes):
for key, value in exception_attributes_map.items():
if key not in attributes:
attributes[key] = value
return
merged = dict(attributes)
for key, value in exception_attributes_map.items():
merged.setdefault(key, value)
log_record.attributes = merged
return
log_record.attributes = exception_attributes_map
attributes = log_record.attributes or {}
if isinstance(attributes, BoundedAttributes):
for key, value in exception_attributes_map.items():
if key not in attributes:
attributes[key] = value
return
log_record.attributes = {
**exception_attributes_map,
**attributes,
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're 3.9 plus, I think a | b is recommended. at least we've been doing that in GenAI. I believe it calls into native code so shoudl be much quicker

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this shape, but it ended up surfacing type-check issues on this branch.

event_name=event_name,
exception=exception,
)
_apply_exception_attributes(log_record, exception)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If we're creating a log record from scratch (as we are here), we should be able to directly modify the attributes before constructing the record.

severity_number: Optional[SeverityNumber] = None,
body: AnyValue = None,
attributes: Optional[_ExtendedAttributes] = None,
exception: Optional[BaseException] = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overload is deprecated

Suggested change
exception: Optional[BaseException] = None,

Comment on lines +547 to +551
if isinstance(attributes, BoundedAttributes):
for key, value in exception_attributes_map.items():
if key not in attributes:
attributes[key] = value
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this branch?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch is there to preserve BoundedAttributes behavior.

record_exception = getattr(
record.log_record, "exception", None
)
if not isinstance(record, ReadWriteLogRecord):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not be relevant to this PR but do we know what this instance check is here? Doesn't emit expect a LogRecord and not a ReadWriteLogRecord? When would the latter be ever passed in?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadWriteLogRecord is an SDK-internal type, so (I guess) this branch is just a defensive fast path for already-materialized SDK records, while the public Logger.emit() API still expects callers to pass a LogRecord.

Signed-off-by: Israel Blancas <iblancasa@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

Add log exception support to Logger API/SDK

8 participants