-
Notifications
You must be signed in to change notification settings - Fork 79
feat: add example using Sentry V2 SDK #140
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
Open
gregbrowndev
wants to merge
18
commits into
temporalio:main
Choose a base branch
from
gregbrowndev:feat/sentry-v2-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+569
−291
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
fe65b3c
feat: add example using Sentry V2 SDK
gregbrowndev d69c035
wip: code review
gregbrowndev 1ed6b30
Merge branch 'temporalio:main' into feat/sentry-v2-example
gregbrowndev 5cd79d6
wip: improve readmes
gregbrowndev c88a03e
wip: replace v1 sentry example with v2
gregbrowndev 6381dd9
wip: add screenshot of Sentry error
gregbrowndev 19c9359
wip: clean up
gregbrowndev 46cd182
fix: lints and module imports
gregbrowndev d793b15
Merge branch 'main' into feat/sentry-v2-example
cretz 73af0dd
Merge remote-tracking branch 'upstream/main' into feat/sentry-v2-example
gregbrowndev 5cc8323
wip: test sentry integration
gregbrowndev d9b7222
wip: fix sentry integration
gregbrowndev 184922b
wip: refactor into workflow and activity modules
gregbrowndev 4c458f3
wip: fix metaclass conflict error
gregbrowndev f89fc54
wip: test the sentry interceptor
gregbrowndev f280b55
Merge branch 'main' into feat/sentry-v2-example
gregbrowndev aa88148
wip: fix uv.lock and typo
gregbrowndev 5a41fb8
fix: use __call__ in fake transport
gregbrowndev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,40 @@ | ||
# Sentry Sample | ||
|
||
This sample shows how to configure [Sentry](https://sentry.io) to intercept and capture errors from the Temporal SDK. | ||
This sample shows how to configure [Sentry](https://sentry.io) SDK (version 2) to intercept and capture errors from the Temporal SDK | ||
for workflows and activities. The integration adds some useful context to the errors, such as the activity type, task queue, etc. | ||
|
||
## Further details | ||
|
||
This is a small modification of the original example Sentry integration in this repo based on SDK v1. The integration | ||
didn't work properly with Sentry SDK v2 due to some internal changes in the Sentry SDK that broke the worker sandbox. | ||
Additionally, the v1 SDK has been deprecated and is only receiving security patches and will reach EOL some time in the future. | ||
If you still need to use Sentry SDK v1, check the original example at this [commit](https://github.com/temporalio/samples-python/blob/090b96d750bafc10d4aad5ad506bb2439c413d5e/sentry). | ||
|
||
## Running the Sample | ||
|
||
For this sample, the optional `sentry` dependency group must be included. To include, run: | ||
|
||
uv sync --group sentry | ||
uv sync --no-default-groups --dev --group sentry | ||
|
||
> Note: this integration breaks when `gevent` is installed (e.g. by the gevent sample) so make sure to only install | ||
> the `sentry` group and run the scripts below as described. | ||
To run, first see [README.md](../README.md) for prerequisites. Set `SENTRY_DSN` environment variable to the Sentry DSN. | ||
Then, run the following from the root directory to start the worker: | ||
|
||
export SENTRY_DSN= # You'll need a Sentry account to test against | ||
export ENVIRONMENT=dev | ||
uv run sentry/worker.py | ||
|
||
This will start the worker. Then, in another terminal, run the following to execute the workflow: | ||
|
||
uv run sentry/starter.py | ||
|
||
The workflow should complete with the hello result. If you alter the workflow or the activity to raise an | ||
`ApplicationError` instead, it should appear in Sentry. | ||
You should see the activity fail causing an error to be reported to Sentry. | ||
|
||
## Screenshot | ||
|
||
The screenshot below shows the extra tags and context included in the | ||
Sentry error from the exception thrown in the activity. | ||
|
||
 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from dataclasses import dataclass | ||
|
||
from temporalio import activity | ||
|
||
|
||
@dataclass | ||
class WorkingActivityInput: | ||
message: str | ||
|
||
|
||
@activity.defn | ||
async def working_activity(input: WorkingActivityInput) -> str: | ||
activity.logger.info("Running activity with parameter %s" % input) | ||
return "Success" | ||
|
||
|
||
@dataclass | ||
class BrokenActivityInput: | ||
message: str | ||
|
||
|
||
@activity.defn | ||
async def broken_activity(input: BrokenActivityInput) -> str: | ||
activity.logger.info("Running activity with parameter %s" % input) | ||
raise Exception("Activity failed!") |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,92 @@ | ||
import asyncio | ||
import logging | ||
import os | ||
from dataclasses import dataclass | ||
from datetime import timedelta | ||
|
||
import sentry_sdk | ||
from temporalio import activity, workflow | ||
from sentry_sdk.integrations.asyncio import AsyncioIntegration | ||
from sentry_sdk.types import Event, Hint | ||
from temporalio.client import Client | ||
from temporalio.worker import Worker | ||
from temporalio.worker.workflow_sandbox import ( | ||
SandboxedWorkflowRunner, | ||
SandboxRestrictions, | ||
) | ||
|
||
from sentry.activity import broken_activity, working_activity | ||
from sentry.interceptor import SentryInterceptor | ||
from sentry.workflow import SentryExampleWorkflow | ||
|
||
interrupt_event = asyncio.Event() | ||
|
||
@dataclass | ||
class ComposeGreetingInput: | ||
greeting: str | ||
name: str | ||
|
||
def before_send(event: Event, hint: Hint) -> Event | None: | ||
# Filter out __ShutdownRequested events raised by the worker's internals | ||
if str(hint.get("exc_info", [None])[0].__name__) == "_ShutdownRequested": | ||
return None | ||
|
||
@activity.defn | ||
async def compose_greeting(input: ComposeGreetingInput) -> str: | ||
activity.logger.info("Running activity with parameter %s" % input) | ||
return f"{input.greeting}, {input.name}!" | ||
return event | ||
|
||
|
||
@workflow.defn | ||
class GreetingWorkflow: | ||
@workflow.run | ||
async def run(self, name: str) -> str: | ||
workflow.logger.info("Running workflow with parameter %s" % name) | ||
return await workflow.execute_activity( | ||
compose_greeting, | ||
ComposeGreetingInput("Hello", name), | ||
start_to_close_timeout=timedelta(seconds=10), | ||
def initialise_sentry() -> None: | ||
sentry_dsn = os.environ.get("SENTRY_DSN") | ||
if not sentry_dsn: | ||
print( | ||
"SENTRY_DSN environment variable is not set. Sentry will not be initialized." | ||
) | ||
return | ||
|
||
environment = os.environ.get("ENVIRONMENT") | ||
sentry_sdk.init( | ||
dsn=sentry_dsn, | ||
environment=environment, | ||
integrations=[ | ||
AsyncioIntegration(), | ||
], | ||
attach_stacktrace=True, | ||
before_send=before_send, | ||
) | ||
print(f"Sentry SDK initialized for environment: {environment!r}") | ||
|
||
async def main(): | ||
# Uncomment the line below to see logging | ||
# logging.basicConfig(level=logging.INFO) | ||
|
||
async def main(): | ||
# Initialize the Sentry SDK | ||
sentry_sdk.init( | ||
dsn=os.environ.get("SENTRY_DSN"), | ||
) | ||
initialise_sentry() | ||
|
||
# Start client | ||
client = await Client.connect("localhost:7233") | ||
|
||
# Run a worker for the workflow | ||
worker = Worker( | ||
async with Worker( | ||
client, | ||
task_queue="sentry-task-queue", | ||
workflows=[GreetingWorkflow], | ||
activities=[compose_greeting], | ||
workflows=[SentryExampleWorkflow], | ||
activities=[broken_activity, working_activity], | ||
interceptors=[SentryInterceptor()], # Use SentryInterceptor for error reporting | ||
) | ||
|
||
await worker.run() | ||
workflow_runner=SandboxedWorkflowRunner( | ||
restrictions=SandboxRestrictions.default.with_passthrough_modules( | ||
"sentry_sdk" | ||
) | ||
), | ||
): | ||
# Wait until interrupted | ||
print("Worker started, ctrl+c to exit") | ||
await interrupt_event.wait() | ||
print("Shutting down") | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) | ||
# Note: "Addressing Concurrency Issues" section in Sentry docs recommends using | ||
# the AsyncioIntegration: "If you do concurrency with asyncio coroutines, make | ||
# sure to use the AsyncioIntegration which will clone the correct scope in your Tasks" | ||
# See https://docs.sentry.io/platforms/python/troubleshooting/ | ||
# | ||
# However, this captures all unhandled exceptions in the event loop. | ||
# So handle shutdown gracefully to avoid CancelledError and KeyboardInterrupt | ||
# exceptions being captured as errors. Sentry also captures the worker's | ||
# _ShutdownRequested exception, which is probably not useful. We've filtered this | ||
# out in Sentry's before_send function. | ||
loop = asyncio.new_event_loop() | ||
try: | ||
loop.run_until_complete(main()) | ||
except KeyboardInterrupt: | ||
interrupt_event.set() | ||
loop.run_until_complete(loop.shutdown_asyncgens()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It didn't capture anything for me until I've changed this from
scope.capture_exception()
tosentry_sdk.capture_exception()
Same for workflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kenzmed, did you see this in your own app?
The code sample works for me with a live Sentry backend:
If you're running it in your own app, it's probably because you have Sentry SDK v1 installed. This sample is for Sentry v2. Take a look at the original interceptor.
Keep in mind, the original interceptor doesn't work for Sentry SDK v2 and SDK v1 is EOL.