Skip to content

Added error correlation headers.#491

Open
danielmorell wants to merge 8 commits intomasterfrom
added/cross-project-error-correlation
Open

Added error correlation headers.#491
danielmorell wants to merge 8 commits intomasterfrom
added/cross-project-error-correlation

Conversation

@danielmorell
Copy link
Collaborator

Description of the change

Adds support for the baggage request header, enabling propagation of rollbar.session.id and rollbar.execution.scope.id.

Frameworks included are:

  • Django
  • FastAPI
  • Flask
  • Pyramid
  • Starlette

The session_id attribute is persisted across the entire request. Every log message, trace, or exception reported will include the same session_id.

If the baggage hearder is not included in a request, a new session_id will be generated, and will persist for the lifecycle of the request.

The session_id is stored in either thread local or an async safe contextvar depending on the framework.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Maintenance
  • New release

Related issues

  • SDK-578

Checklists

Development

  • Lint rules pass locally
  • The code changed/added as part of this pull request has been covered with tests
  • All tests related to the changed code pass in development

Code review

  • This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached
  • "Ready for review" label attached to the PR and reviewers assigned
  • Issue from task tracker has a link to this pull request
  • Changes have been reviewed by at least one other engineer

@danielmorell danielmorell requested a review from brianr March 14, 2026 00:43
@brianr brianr requested review from shankj3 and waltjones March 14, 2026 02:03
@@ -51,6 +53,7 @@ def rollbar_tween_factory(pyramid_handler, registry):

def rollbar_tween(request):
# for testing out the integration
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this comment got separated from the try block it references.

break

if not baggage_header:
return _build_new_session_attributes()
Copy link
Contributor

Choose a reason for hiding this comment

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

This layer should not try to create a session id if not present in the header.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link

@shankj3 shankj3 Mar 17, 2026

Choose a reason for hiding this comment

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

Ah yes, when I updated the ticket & dmed you I included that change, apologies for not calling it out more:

If a baggage header is not present, one should be created, and a rollbar.execution.scope.id should be created as a hex representation of a 128 bit number. If rollbar.session.id is not present, the key should not be created.

It'd be the rollbar.execution.scope.id that would be created on the fly, rollbar.session.id can be null

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This has been updated to generate rollbar.execution.scope.id instead of rollbar.session.id. I also updated the logic to always create rollbar.execution.scope.id even if rollbar.session.id exists but rollbar.execution.scope.id does not. This was not the case previously.

request = _session_data_from_request(data)
if request is None:
return
set_current_session(request.get('headers', None))
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we get here because no middleware had set a current session, but the request is present via the rollbar payload, so we look at the headers there. That makes sense, though I'm curious whether this is to cover unsupported frameworks, or supported ones when the middleware wasn't installed.

I think calling set_current_session here introduces a bug though. Unlike in the middleware, nothing calls reset_current_session, and on the next request (from a different session) get_current_session above will succeed with the stale session that was set here.

This can be avoided if session.py provides a method to return the session_data from the headers without needing to set and get current session.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That makes sense. I have updated this, as well as added some logic to prevent overwriting any existing attributes. This is mostly to prevent bugs incase our future selves don't notice we were completely overwriting the data['attributes'] key.

headers = request.get('headers', {})
elif hasattr(request, 'headers'):
headers = request.headers
set_current_session(headers)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally this could go in middleware.py, allowing reset_current_session to be used, and keeping the responsibility of store_current_request more focused.

This function already returns request, so it seems straightforward to move this logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have moved it to asgi/middleware.py as well as starlette/middleware.py so that it can apply to all ASGI frameworks that utilize the middleware.

@waltjones
Copy link
Contributor

@danielmorell nice PR! I'll defer to @shankj3 on approval.

Copy link

@shankj3 shankj3 left a comment

Choose a reason for hiding this comment

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

This is looking great, thanks Daniel! If we can address the potential bugs and remove the creation of session ids in favor the the execution scope id, I think we'll be ready to ship.

Will the request interception be in a separate PR?

@brianr brianr removed their request for review March 20, 2026 20:00
@@ -9,6 +9,7 @@
from unittest import mock
Copy link

Choose a reason for hiding this comment

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

One thing I noticed when testing flask, we don't actually hook into the request lifecycle. So for my test route, which looks like this

@app.route("/boom_nested")
def boom_nested():
    from nested_error import raise_error
    rollbar.report_message("Running a message")

    raise_error()

If no baggage headers are passed, the execution id for the message here, then the exception thrown from raise-error, are different, even though they are in the same execution context.

What do you think about providing a new function for flask config, that would take in the flask app as an argument then hook into app.before_request and app.teardown_request? Then we could use the same logic as other frameworks, with set_current_session and reset_current_session

Copy link

Choose a reason for hiding this comment

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

I'm also seeing the same problem as with flask, where if you don't pass baggage headers, the rollbar.execution.scope.id is different for each error within a request. This was my little test app, it might be missing something:

import os
import rollbar
from fastapi import FastAPI
from rollbar.contrib.fastapi import add_to

rollbar.init(
    access_token=os.getenv("ROLLBAR_TOKEN", "POST_SERVER_ITEM_ACCESS_TOKEN"),
    environment=os.getenv("ROLLBAR_ENV", "development"),
)

app = FastAPI(title="rollbar-fastapi-test")
add_to(app)

@app.get("/boom_nested")
async def boom_nested():
    from nested_error import raise_error
    rollbar.report_message("Error") # has a different request.execution.scope.id
    raise_error() # raises exc that has a different request.execution.scope.id

self.app = create_app()
init_rollbar(self.app)
self.client = self.app.test_client()
reset_current_session()
Copy link

Choose a reason for hiding this comment

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

Why do we need to reset current session? Is something leaking between requests?

return _build_new_scope_attributes()

# Always ensure we have an execution scope ID, even if the baggage header is present but doesn't contain it.
if not has_scope_id:
Copy link

Choose a reason for hiding this comment

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

One last comment here - we should move this out of the core session code, and into the middleware. The reason for this is that we want a single rollbar.execution.scope.id for the lifespan of a request, and in the future, forwarded to other outgoing requests.

If the originating request does'nt have the rollbar.execution.scope.id, then this will be a unique value for every rollbar message sent as the request is processed, which doesn't give us much of a signal.

If this scope id was built once, in the middleware when a request is received and saved to thread / async storage, then we will get that signal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants