Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POtel implementation base branch #3152

Draft
wants to merge 227 commits into
base: master
Choose a base branch
from
Draft

POtel implementation base branch #3152

wants to merge 227 commits into from

Conversation

sl0thentr0py
Copy link
Member

@sl0thentr0py sl0thentr0py commented Jun 10, 2024

Full state of CI: #3744

Contains:

Simple test

import sentry_sdk
from time import sleep

sentry_sdk.init(
    debug=True,
    traces_sample_rate=1.0,
    _experiments={"otel_powered_performance": True},
)

with sentry_sdk.start_span(description="sentry request"):
    sleep(0.1)
    with sentry_sdk.start_span(description="sentry db"):
        sleep(0.5)
        with sentry_sdk.start_span(description="sentry redis"):
            sleep(0.2)
    with sentry_sdk.start_span(description="sentry http"):
        sleep(1)

References

Misc

In OTel, this:

with tracer.start_as_current_span("parent") as parent:
    with tracer.start_span("child1"):
        pass
    with tracer.start_span("child2"):
        pass

is equivalent to

from opentelemetry import trace, context

parent = tracer.start_span("parent")

# Creates a Context object with parent set as current span
ctx = trace.set_span_in_context(parent)

# Set as the implicit current context
token = context.attach(ctx)

# Child will automatically be a child of parent
child1 = tracer.start_span("child1")
child1.end()

# Child will automatically be a child of parent
child2 = tracer.start_span("child2")
child2.end()

# Don't forget to detach or parent will remain the parent above this call stack
context.detach(token)
parent.end()

@sl0thentr0py sl0thentr0py requested a review from sentrivana June 10, 2024 19:00
@sl0thentr0py sl0thentr0py force-pushed the potel-base branch 2 times, most recently from f7f153c to 28effd6 Compare June 11, 2024 11:43
@sl0thentr0py sl0thentr0py force-pushed the potel-base branch 2 times, most recently from 16f9341 to 951477f Compare June 25, 2024 15:16
Copy link

codecov bot commented Jun 26, 2024

❌ 36 Tests Failed:

Tests completed Failed Passed Skipped
12663 36 12627 3340
View the top 3 failed tests by shortest run time
tests.integrations.aws_lambda.test_aws test_non_dict_event[python3.12-list_with_request_data]
Stack Traces | 0.184s run time
.../integrations/aws_lambda/test_aws.py:510: in test_non_dict_event
    envelope_items, response = run_lambda_function(
.../integrations/aws_lambda/test_aws.py:161: in inner
    response = run_lambda_function(
.../integrations/aws_lambda/client.py:324: in run_lambda_function
    response = client.invoke(
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:569: in _api_call
    return self._make_api_call(operation_name, kwargs)
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:1023: in _make_api_call
    raise error_class(parsed_response, operation_name)
E   botocore.errorfactory.ResourceConflictException: An error occurred (ResourceConflictException) when calling the Invoke operation: The operation cannot be performed at this time. The function is currently in the following state: Pending
tests.integrations.aws_lambda.test_aws test_non_dict_event[python3.12-empty_list]
Stack Traces | 0.19s run time
.../integrations/aws_lambda/test_aws.py:510: in test_non_dict_event
    envelope_items, response = run_lambda_function(
.../integrations/aws_lambda/test_aws.py:161: in inner
    response = run_lambda_function(
.../integrations/aws_lambda/client.py:324: in run_lambda_function
    response = client.invoke(
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:569: in _api_call
    return self._make_api_call(operation_name, kwargs)
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:1023: in _make_api_call
    raise error_class(parsed_response, operation_name)
E   botocore.errorfactory.ResourceConflictException: An error occurred (ResourceConflictException) when calling the Invoke operation: The operation cannot be performed at this time. The function is currently in the following state: Pending
tests.integrations.aws_lambda.test_aws test_non_dict_event[python3.12-float]
Stack Traces | 0.19s run time
.../integrations/aws_lambda/test_aws.py:510: in test_non_dict_event
    envelope_items, response = run_lambda_function(
.../integrations/aws_lambda/test_aws.py:161: in inner
    response = run_lambda_function(
.../integrations/aws_lambda/client.py:324: in run_lambda_function
    response = client.invoke(
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:569: in _api_call
    return self._make_api_call(operation_name, kwargs)
.tox/py3.9-aws_lambda/lib/python3.9....../site-packages/botocore/client.py:1023: in _make_api_call
    raise error_class(parsed_response, operation_name)
E   botocore.errorfactory.ResourceConflictException: An error occurred (ResourceConflictException) when calling the Invoke operation: The operation cannot be performed at this time. The function is currently in the following state: Pending

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

@sl0thentr0py sl0thentr0py changed the title Skeletons for new POTEL components New POTEL base branch Jul 9, 2024
@sl0thentr0py sl0thentr0py changed the title New POTEL base branch potel implementation base branch Jul 9, 2024
sl0thentr0py and others added 4 commits July 22, 2024 16:50
Add simple scope management whenever a context is attached

* create a new otel context `_SCOPES_KEY` that will hold a tuple of
  `(curent_scope, isolation_scope)`
* the `current_scope` will always be forked (like on every span creation/context update in practice)
  * note that this is on `attach`, so not on all copy-on-write context
    object creation but only on apis such as
    [`trace.use_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547)
    or [`tracer.start_as_current_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L329)
  * basically every otel `context` fork corresponds to our `current_scope` fork
* the `isolation_scope` currently will not be forked
  * these will later be updated, for instance when we update our top
    level scope apis that fork isolation scope, that will also have a
    corresponding change in this `attach` function
* only acts on `on_end` instead of both `on_start/on_end` as before
* store children spans in a dict mapping `span_id -> children`
* new dict only stores otel span objects and no sentry transaction/span objects so we save a bit of useless memory allocation
* I'm not using our current `Transaction/Span` classes at all to build the event because when we add our APIs later, we'll need to rip these out and we also avoid having to deal with the `instrumenter` problem
* if we get a root span (without parent), we recursively walk the dict and find the children and package up the transaction event and send it 
  * I didn't do it like JS because I think this way is better
  *  they [group an array of `finished_spans`](https://github.com/getsentry/sentry-javascript/blob/7e298036a21a5658f3eb9ba184165178c48d7ef8/packages/opentelemetry/src/spanExporter.ts#L132) every time a root span ends and I think this uses more cpu than what I did
  * and the dict like I used it doesn't take more space than the array either
* if we get a span with a parent we just update the dict to find the span later
* moved the common `is_sentry_span` logic to utils
szokeasaurusrex and others added 4 commits July 23, 2024 12:54
Remove `instrumenter` parameter from all functions that accept it
(details below), and modify tests to not pass the `instrumenter`
parameter to any functions that used to take it.

Also, delete `tests/tracing/test_noop_span.py`, which tests
functionality removed in this commit.

BREAKING CHANGE:

  - Remove `sentry_sdk.init`'s `instrumenter` kwarg.
  - Delete `sentry_sdk.contsts.INSTRUMENTER` class.
  - Remove `sentry_sdk.hub.Hub.start_span`'s `instrumenter` parameter.
  - Remove `sentry_sdk.hub.Hub.start_transaction`'s `instrumenter`
    parameter.
  - Remove `sentry_sdk.scope.Scope.start_transaction`'s `instrumenter`
    parameter.
  - Remove `sentry_sdk.scope.Scope.start_span`'s `instrumenter`
    parameter.
  - Remove `sentry_sdk.tracing.Span.start_child`'s `instrumenter`
    parameter.
  - Remove `sentry_sdk.tracing.NoOpSpan.start_child`'s
    `instrumenter` parameter.

Closes: #3321
Improved extraction of op, description, status and http_status for a Sentry span from an OpenTelemenetry span.

Fixes #3236

---------

Co-authored-by: Neel Shah <[email protected]>
Co-authored-by: Ivana Kellyerova <[email protected]>
@antonpirker antonpirker changed the title potel implementation base branch POtel implementation base branch Aug 5, 2024
szokeasaurusrex and others added 11 commits August 7, 2024 14:42
Also, remove any tests for `sentry_sdk.configure_scope`.

Since Strawberry's deprecated [Sentry tracing extensions](https://strawberry.rocks/docs/extensions/sentry-tracing) import `sentry_sdk.configure_scope`, importing `strawberry.extensions.tracing.SentryTracingExtension` (or `SentryTracingExtensionSync`) will result in an unhandled exception. Therefore, these imports, and any functionality associated with them, have also been removed. This itself is not a breaking change, as it is necessitated by the removal of `sentry_sdk.configure_scope`.

BREAKING CHANGE: Remove `sentry_sdk.configure_scope`.

Closes: #3402
Also, remove any tests that test `sentry_sdk.push_scope`.

BREAKING CHANGE: Remove `sentry_sdk.push_scope`.

Closes #3403
This change is a prerequisite for #3404.

BREAKING CHANGE: Remove `sentry_sdk.transport.HttpTransport`'s `hub_cls` attribute.
* Removed hub based functions from sessions implementation
* Removed scope manager
* Removed hub from tracing
* Removed hub from apidocs
* Updated migration guide
* Updated migration guide
* Skeletons for new components

* Add simple scope management whenever a context is attached

* create a new otel context `_SCOPES_KEY` that will hold a tuple of
  `(curent_scope, isolation_scope)`
* the `current_scope` will always be forked (like on every span creation/context update in practice)
  * note that this is on `attach`, so not on all copy-on-write context
    object creation but only on apis such as
    [`trace.use_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547)
    or [`tracer.start_as_current_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L329)
  * basically every otel `context` fork corresponds to our `current_scope` fork
* the `isolation_scope` currently will not be forked
  * these will later be updated, for instance when we update our top
    level scope apis that fork isolation scope, that will also have a
    corresponding change in this `attach` function

* Don't parse DSN twice

* wip

* Skeletons for new components

* Skeletons for new components

* Add simple scope management whenever a context is attached

* create a new otel context `_SCOPES_KEY` that will hold a tuple of
  `(curent_scope, isolation_scope)`
* the `current_scope` will always be forked (like on every span creation/context update in practice)
  * note that this is on `attach`, so not on all copy-on-write context
    object creation but only on apis such as
    [`trace.use_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547)
    or [`tracer.start_as_current_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L329)
  * basically every otel `context` fork corresponds to our `current_scope` fork
* the `isolation_scope` currently will not be forked
  * these will later be updated, for instance when we update our top
    level scope apis that fork isolation scope, that will also have a
    corresponding change in this `attach` function

* mypy fixes

* working span processor

* lint

* Port over op/description/status extraction

* defaultdict

* naive impl

* wip

* fix args

* wip

* remove extra docs

* Add simple scope management whenever a context is attached (#3159)

Add simple scope management whenever a context is attached

* create a new otel context `_SCOPES_KEY` that will hold a tuple of
  `(curent_scope, isolation_scope)`
* the `current_scope` will always be forked (like on every span creation/context update in practice)
  * note that this is on `attach`, so not on all copy-on-write context
    object creation but only on apis such as
    [`trace.use_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547)
    or [`tracer.start_as_current_span`](https://github.com/open-telemetry/opentelemetry-python/blob/ba22b165471bde2037620f2c850ab648a849fbc0/opentelemetry-api/src/opentelemetry/trace/__init__.py#L329)
  * basically every otel `context` fork corresponds to our `current_scope` fork
* the `isolation_scope` currently will not be forked
  * these will later be updated, for instance when we update our top
    level scope apis that fork isolation scope, that will also have a
    corresponding change in this `attach` function

* Implement new POTel span processor (#3223)

* only acts on `on_end` instead of both `on_start/on_end` as before
* store children spans in a dict mapping `span_id -> children`
* new dict only stores otel span objects and no sentry transaction/span objects so we save a bit of useless memory allocation
* I'm not using our current `Transaction/Span` classes at all to build the event because when we add our APIs later, we'll need to rip these out and we also avoid having to deal with the `instrumenter` problem
* if we get a root span (without parent), we recursively walk the dict and find the children and package up the transaction event and send it 
  * I didn't do it like JS because I think this way is better
  *  they [group an array of `finished_spans`](https://github.com/getsentry/sentry-javascript/blob/7e298036a21a5658f3eb9ba184165178c48d7ef8/packages/opentelemetry/src/spanExporter.ts#L132) every time a root span ends and I think this uses more cpu than what I did
  * and the dict like I used it doesn't take more space than the array either
* if we get a span with a parent we just update the dict to find the span later
* moved the common `is_sentry_span` logic to utils

* Basic test cases for potel (#3286)

* Proxy POTelSpan.set_data to underlying otel span attributes (#3297)

* ref(tracing): Simplify backwards-compat code (#3379)

With this change, we aim to simplify the backwards-compatibility code
for POTel tracing.

We do this as follows:
  - Remove `start_*` functions from `tracing`
  - Remove unused parameters from `tracing.POTelSpan.__init__`.
  - Make all parameters to `tracing.POTelSpan.__init__` kwarg-only.
  - Allow `tracing.POTelSpan.__init__` to accept arbitrary kwargs,
    which are all ignored, for compatibility with old `Span` interface.
  - Completely remove `start_inactive_span`, since inactive spans can
    be created by setting `active=False` when constructing a
    `POTelSpan`.

* New Scope implementation based on OTel Context (#3389)

* New `PotelScope` inherits from scope and reads the scope from the otel context key `SENTRY_SCOPES_KEY`
* New `isolation_scope` and `new_scope` context managers just use the context manager forking and yield with the scopes living on the above context key
  * isolation scope forking is done with the `SENTRY_FORK_ISOLATION_SCOPE_KEY` boolean context key

* Fix circular imports (#3431)

* Random tweaks (#3437)

* Origin improvements (#3432)

* Tweak OTel timestamp utils (#3436)

* Create spans on scope (#3442)

* Fill out more property/method stubs (#3441)

* Cleanup origin handling and defaults (#3445)

* add note to migration guide

* Attribute namespace for tags, measurements (#3448)

---------

Co-authored-by: Neel Shah <[email protected]>
Co-authored-by: Neel Shah <[email protected]>
Co-authored-by: Daniel Szoke <[email protected]>
antonpirker and others added 30 commits December 6, 2024 15:53
Make sure OK status is set, only when there has not been a error status set before.
Make sure there is a transaction name
Without this, internal otel logs (especially `logger.exception`s) will
show up as events / breadcrumbs in the payload.
python 3.7 doesnt work on ubuntu 24.04
The newly added feature flags add an error processor to `current_scope`
in their `setup_once`. This is actually an antipattern and shouldn't be
encouraged.
Either way, this PR sets up the scopes first since the integrations get
setup in the `Client` and require that scope to be correct.
* Add optional `parent_span` argument to `POTelSpan` constructor and fix
`start_child`
* `run_id` is reused for the top level pipeline, so make sure to close
that span or else we get orphans
* Don't use context manager enter/exit since we're doing manual span
management
* Set correct statuses while finishing the spans
Make AWS Lambda tests in POTel.
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.

4 participants