Skip to content

chore(event formatter): add event formatter to sentry backend#119250

Open
shayna-ch wants to merge 19 commits into
masterfrom
shayna-ch/formatter
Open

chore(event formatter): add event formatter to sentry backend#119250
shayna-ch wants to merge 19 commits into
masterfrom
shayna-ch/formatter

Conversation

@shayna-ch

@shayna-ch shayna-ch commented Jul 8, 2026

Copy link
Copy Markdown
Member

Adds event formatter module to sentry backend.
how it works: serialized event → adapter → model → formatter → text

adapter.py

  • this takes a serialized event from sentry and turns it into an event object (and its components like breadcrumbs).

formatter.py

  • this takes the event object from the adapter and turns it into text (markdown or xml)

limits.py

  • holds char/count limits for stacktraces, breadcrumbs, etc...
  • based of seers limits

models.py

  • has the object definitions for event and event components
  • based off seer's event details model

sections.py

  • defines what each block of text contains, plus the section order and the format_issue entry point

Example Formatted Outputs

Markdown:

## Title
ValueError: invalid literal for int()

## Exception
ValueError: invalid literal for int() with base 10: 'abc'
**Handled:** No
parse in app/utils.py [Line 12] (In app)
    return int(value)  <-- SUSPECT LINE
vars: value="'abc'"
------
handle_request in app/views.py [Line 88] (In app)
    qty = request.GET['qty']
    return int(qty)  <-- SUSPECT LINE

## Breadcrumbs
[info] auth: user logged in
[warning] cart: qty parsed from query string

## Request
GET https://shop.example.com/api/checkout
{'qty': 'abc'}

## Tags
**environment:** production
**transaction:** /api/checkout

## User
**ID:** 42
**Email:** jane@example.com
**IP:** 203.0.113.5

XML:

<title>
ValueError: invalid literal for int()
</title>

<exception>
ValueError: invalid literal for int() with base 10: 'abc'
<handled>No</handled>
<code lang="">parse in app/utils.py [Line 12] (In app)
    return int(value)  <-- SUSPECT LINE
vars: value="'abc'"
------
handle_request in app/views.py [Line 88] (In app)
    qty = request.GET['qty']
    return int(qty)  <-- SUSPECT LINE</code>
</exception>

<breadcrumbs>
[info] auth: user logged in
[warning] cart: qty parsed from query string
</breadcrumbs>

<request>
GET https://shop.example.com/api/checkout
<code>{'qty': 'abc'}</code>
</request>

<tags>
<environment>production</environment>
<transaction>/api/checkout</transaction>
</tags>

<user>
<id>42</id>
<email>jane@example.com</email>
<ip>203.0.113.5</ip>
</user>

resolves ID-1699

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jul 8, 2026
@shayna-ch shayna-ch changed the title formatter chore(event formatter): add event formatter to sentry backend Jul 9, 2026
@shayna-ch

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread src/sentry/issues/formatting/sections.py
@shayna-ch

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread src/sentry/issues/formatting/adapter.py
Comment thread src/sentry/issues/formatting/adapter.py Outdated
@shayna-ch

Copy link
Copy Markdown
Member Author

bugbot run

Comment thread src/sentry/issues/formatting/adapter.py
@shayna-ch

Copy link
Copy Markdown
Member Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7328e65. Configure here.

@shayna-ch shayna-ch marked this pull request as ready for review July 13, 2026 17:30
@shayna-ch shayna-ch requested review from a team as code owners July 13, 2026 17:30
Comment thread src/sentry/issues/formatting/sections.py
@linear-code

linear-code Bot commented Jul 13, 2026

Copy link
Copy Markdown

ID-1699

Comment thread tests/sentry/issues/formatting/test_scaffold.py Outdated
Comment thread src/sentry/issues/formatting/adapter.py Outdated
)


def _breadcrumb(v: Mapping[str, Any]) -> Breadcrumb:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we really need so many helpers? a lot of these helpers seem to be parsing unknown Anys, which is helpful, but all the types are fully optional anyways, so it doesn't provide much information.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

true, removed the ones that didn't have extra logic and moved to aliases

)

# Enables the shared issue/event formatter module (markdown/xml output for LLMs).
register(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is this option for?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

just so i can test locally while I implement the surfaces. eventually I will prob move it to a feature flag

]


def format_issue(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i'm having trouble reviewing this since it's hard to tell how this is going to be used (e.g. are we really taking in a fully unknown Mapping[str, Any] every time we use this?). do you have an example of where we'd be calling format_issue?

Comment thread src/sentry/issues/formatting/adapter.py Outdated
Comment thread src/sentry/issues/formatting/formatter.py
Comment on lines +67 to +72
def field(self, key: str, value: str) -> str:
tag = slug(key)
return f"<{tag}>{value}</{tag}>"

def code_block(self, text: str) -> str:
return f"<code>{text}</code>"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The XmlFormatter does not escape special characters like < and > in event data, producing invalid XML for stack traces with generics.
Severity: HIGH

Suggested Fix

Apply an XML escaping function, such as xml.sax.saxutils.escape, to all dynamic content before it is embedded within XML tags in the XmlFormatter's field(), code_block(), and block() methods.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/issues/formatting/formatter.py#L67-L72

Potential issue: The `XmlFormatter` class does not escape special XML characters like
`<`, `>`, and `&`. When formatting event data that contains these characters, such as
stack traces from languages like Java or C++ that use generic types (e.g., `Map<String,
List<Integer>>`), the formatter produces syntactically invalid XML. This will cause
parsing errors for downstream consumers that expect valid XML, such as the intended LLM
integration. The issue affects the `field()`, `code_block()`, and `block()` methods.

package: str | None = None
line_no: int | None = Field(None, alias="lineNo")
col_no: int | None = Field(None, alias="colNo")
context: list[tuple[int, str | None]] = [] # [(line_no, source_line), ...]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The Frame.context field is not optional, but serialized events can contain "context": null. This will cause a ValidationError and crash the issue formatting process.
Severity: HIGH

Suggested Fix

Modify the Frame.context field definition to allow None. Change context: list[tuple[int, str | None]] = [] to context: list[tuple[int, str | None]] | None = None. This will make Pydantic correctly handle null values for this field during deserialization.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/sentry/issues/formatting/models.py#L27

Potential issue: The `Frame.context` field in `src/sentry/issues/formatting/models.py`
is defined as a non-optional list. However, Sentry events can contain frames where the
`context` is `null`, especially for code without available source context (e.g.,
minified JavaScript or native code). When `format_issue()` attempts to parse such an
event, Pydantic will raise a `ValidationError` because `null` is not a valid list. Since
there is no error handling around the model parsing in `event_response_to_model()`, this
exception will propagate uncaught, causing the entire issue formatting pipeline to crash
for that event.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b4b49a0. Configure here.

Comment thread src/sentry/issues/formatting/sections.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants