Skip to content

Sentry Integration¤

Sentry.io is a platform for error-tracking and performance monitoring. Sentry's Python SDK enables automatic reporting of errors, exceptions and identifies performance issues.

Tip

For the quick start with Sentry.io, you can read the official documentation.

Sentry sends events (errors or transactions) to sentry.io together with additional event data (such as timestamps, server names, web browser names, stack traces, etc) and attachments (config or log files that are related to an error event). Similar events are grouped into issues. Every event has its fingerprint that Sentry uses to group events together. You can also add custom tags to each event and then filter on them. To create a trail of events that happened prior to an issue, Sentry uses breadcrumbs, which are very similar to logs, but can record more rich structured data.

Transactions are used for performance monitoring. A transaction represents the operation you want to measure or track, like a page load, page navigation, or asynchronous task. Transaction events are grouped by the transaction name. Moreover, you can monitor child tasks within a single transaction by creating child spans.

Configuration¤

When the Sentry Service is integrated to the ASAB microservice, it can be configured to send events to Sentry.io workspace.

After you create a new project in Sentry.io, DSN (data source name) is generated. You can either set the environment variable or fulfill DSN in the configuration.

You can set DSN in the configuration directly:

configuration file
[sentry]
data_source_name=https://<public key>@<secret key>.ingest.sentry.io/<project id>

You can provide DSN as environment variable (which is safer, in general) in a .env file.

.env
export SENTRY_DSN=https://<public key>@<secret key>.ingest.sentry.io/<project id>

Then use this variable in docker-compose.yaml file.

docker-compose.yaml
my-asab-service:
    image: my.asab.based.microservice
    ...
    environment:
    - SENTRY_DSN=${SENTRY_DSN}

In the configuration file, [sentry] section may be empty, but it has to be there.

configuration file
[sentry]

Other options available for sentry:

[sentry]
environment=production_hogwarts  # will be visible as a tag 'environment'

[sentry:logging]
breadcrumbs=info  # logging level for capturing breadcrumbs
events=notice  # logging level for capturing events

Tip

If the application is properly containerized, other tags for Sentry.io are created automatically (using Manifest), such as: appclass, release, server_name, service_id, instance_id, node_id, site_id.

Integration¤

Sentry service is dependent on Python sentry_sdk library.

my_app.py
import asab

class MyApplication(asab.Application):
    def __init__(self):
        super().__init__()
        if "sentry" in asab.Config.sections():
            import asab.sentry as asab_sentry
            self.SentryService = asab_sentry.SentryService(self)

After the service is initialized:

  • all uncaught exceptions are sent as events
  • all logging messages with priority ERROR or higher are sent as events, messages with priority INFO or higher are sent as breadcrumbs

Capturing errors¤

As mentioned above, uncaught exceptions and errors are sent automatically to Sentry.

To capture caught exception and send it as an event, use capture_exception() method.

try:
    call_collapsing_function()
except Exception as e:
    sentry_service.capture_exception(e)

To capture a custom message and send it as an event, use capture_message() method.

if required_variable is None:
    sentry_service.capture_message("required_variable was not set")

For grouping issues together and filtering, you can add custom tags. Tags are set only within the current scope (method, class, module).

def my_function():
    sentry_service.set_tag("method", "my_function")
    sentry_service.set_tags({"foo": "bar", "fooz": "buzz"})

Info

Tag names and values cannot be arbitrary strings.

Tag keys can have a maximum length of 32 characters and can contain only letters (a-zA-Z), numbers (0-9), underscores (_), periods (.), colons (:), and dashes (-).

Tag values can have a maximum length of 200 characters and they cannot contain the newline (\n) character.

If you try to add a tag with invalid format, it won't be set and error message will be displayed.

Performance monitoring¤

To create new transaction for performance monitoring, use the context manager transaction:

with sentry_service.transaction("speed test", "test sleeping"):
    time.sleep(1.0)

To create a child span, use the context manager span:

with sentry_svc.transaction("speed test", "multiple tasks"):
    prepare_task1()
    with sentry_svc.span("task", "task1"):
        task1()
    prepare_task2()
    with sentry_svc.span("task", "task2"):
        task2()
    finalize()

Reference¤

asab.sentry.SentryService ¤

Bases: Service

Service for Sentry SDK integration.

Sentry is an error tracking and performance monitoring platform. When the service is initialized and data_source_name is set, all unhandled exceptions, error log messages are sent as Events to sentry.io, together with lines of code where the error happened, structured data and values of variables.

Configuration:

[sentry]
data_source_name=... ; DSN of the project
environment=... ; default: 'not specified'

Examples:

class MyApp(asab.Application):
        async def initialize(self):
                if "sentry" in asab.Config.sections():
                        import asab.sentry
                        self.SentryService = asab.sentry.SentryService(self)
Source code in asab/sentry/service.py
class SentryService(asab.Service):
	"""
	Service for Sentry SDK integration.

	Sentry is an error tracking and performance monitoring platform.
	When the service is initialized and `data_source_name` is set,
	all unhandled exceptions, error log messages are sent as Events to sentry.io,
	together with lines of code where the error happened, structured data and values of variables.

	Configuration:
	```ini
	[sentry]
	data_source_name=... ; DSN of the project
	environment=... ; default: 'not specified'
	```

	Examples:

	```python
	class MyApp(asab.Application):
		async def initialize(self):
			if "sentry" in asab.Config.sections():
				import asab.sentry
				self.SentryService = asab.sentry.SentryService(self)
	```
	"""

	def __init__(self, app: asab.Application, service_name: str = "asab.SentryService"):
		super().__init__(app, service_name)


		# DATA SOURCE NAME (DSN)
		# format: https://<public key>@o<secret key>.ingest.sentry.io/<project id>
		# DSN is automatically generated when new project is created
		# and can be modified: Settings > Client Keys (DSN) > Key Details
		# Specification: either in configuration '[sentry] data_source_name', $SENTRY_DSN environment variable as a fallback
		self.DataSourceName = asab.Config.get("sentry", "data_source_name", fallback="")
		if len(self.DataSourceName) == 0:
			self.DataSourceName = os.getenv("SENTRY_DSN", "")
		if len(self.DataSourceName) == 0:
			# We do not need Sentry enabled in development - empty DSN leads to failure
			L.critical("Data source name is not set. Specify it in configuration: '[sentry] data_source_name'.")
			raise SystemExit("Exit due to a critical configuration error.")


		# LOGGING LEVELS
		# by default, LOG_NOTICE+ are sent to breadcrumbs, ERROR+ to events
		levels = {
			"debug": logging.DEBUG,
			"info": logging.INFO,
			"notice": asab.LOG_NOTICE,
			"warning": logging.WARNING,
			"error": logging.ERROR,
			"critical": logging.CRITICAL
		}

		self.LoggingBreadCrumbsLevel = levels.get(asab.Config.get("sentry:logging", "breadcrumbs").lower())
		self.LoggingEventsLevel = levels.get(asab.Config.get("sentry:logging", "events").lower())

		# RELEASE
		# Release can be obtained from MANIFEST.json if exists
		manifest = {}
		manifest_path = asab.Config.get("general", "manifest", fallback="")
		if manifest_path == "":
			if os.path.isfile("/app/MANIFEST.json"):
				manifest_path = "/app/MANIFEST.json"
			elif os.path.isfile("/MANIFEST.json"):
				manifest_path = "/MANIFEST.json"
			elif os.path.isfile("MANIFEST.json"):
				manifest_path = "MANIFEST.json"

		if len(manifest_path) != 0:
			try:
				with open(manifest_path) as f:
					manifest = json.load(f)
			except Exception as e:
				L.exception("Error when reading manifest for reason {}".format(e))

		self.Release = "{appname}:{version}".format(  # e.g. 'LMIOParsecApplication:v23.40-alpha'
			appname=app.__class__.__name__,
			version=manifest.get("version", "<none>")
		)

		self.NodeId = os.getenv("NODE_ID", None)  # e.g. "lmio-box-testing-1"
		self.ServiceId = os.getenv("SERVICE_ID", None)  # e.g. "lmio-service"
		self.InstanceId = os.getenv("INSTANCE_ID", None)  # e.g. "lmio-service-01"
		self.SiteId = os.getenv("SITE_ID", None)

		# PERFORMANCE MONITORING
		# traces sample rate: percentage of captured events
		# prevents overcrowding when deployed to production
		# default: 100%
		self.TracesSampleRate = asab.Config.getfloat("sentry", "traces_sample_rate")
		assert 0 <= self.TracesSampleRate <= 1.0, "Traces sample rate must be between 0 and 1."


		# INITIALIZATION
		sentry_sdk.init(
			dsn=self.DataSourceName,
			integrations=[
				sentry_sdk.integrations.aiohttp.AioHttpIntegration(),
				sentry_sdk.integrations.asyncio.AsyncioIntegration(),
				sentry_sdk.integrations.logging.LoggingIntegration(
					level=self.LoggingBreadCrumbsLevel,  # logging level sent to breadcrumbs
					event_level=self.LoggingEventsLevel,  # logging level sent to events
				),
			],
			traces_sample_rate=self.TracesSampleRate,  # percentage of captured events
			environment=self.SiteId if self.SiteId is not None else "not specified",
			release=self.Release,  # version of the microservice, e.g., v23.40-alpha
			auto_session_tracking=True,  # session info about interaction between user and app
			debug=False,  # ...sends many irrelevant messages
			max_value_length=8192,  # longer messages are truncated, the default value (1024) is too short
		)

		# ADDITIONAL GLOBAL TAGS
		# These tags will be set manually or automatically by Remote Control
		if self.NodeId:
			sentry_sdk.set_tag("node_id", self.NodeId)
		if self.ServiceId:
			sentry_sdk.set_tag("service_id", self.ServiceId)
		if self.InstanceId:
			sentry_sdk.set_tag("instance_id", self.InstanceId)
		if self.SiteId:
			sentry_sdk.set_tag("site_id", self.SiteId)
			sentry_sdk.set_tag("environment", self.SiteId)

		sentry_sdk.set_tag("appclass", app.__class__.__name__)  # e.g. 'LMIOParsecApplication'

		L.info("is ready.")  # for debugging, visible only if argument '-v' is set


	def capture_exception(self, error=None, scope=None, **scope_args):
		"""
		Capture caught exception and send it to Sentry.

		Args:
			error (str, optional): Error message that will be sent. If not specified, the one currently held in `sys.exc_info()` is sent.

		Examples:
		```python
		try:
			call_collapsing_function()
		except Exception as e:
			sentry_svc.capture_exception(e)
		```
		"""
		return sentry_sdk.capture_exception(error=error, scope=scope, **scope_args)

	def capture_message(self, message, level=None, scope=None, **scope_args):
		"""
		Send textual information to Sentry.

		Args:
			message (str):  Text message that will be sent.
		"""
		return sentry_sdk.capture_message(message=message, level=level, scope=scope, **scope_args)

	def set_tag(self, key: str, value: typing.Union[str, int]) -> None:
		"""
		Add custom tag to the current scope.

		Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

		Tag is set only for the current scope (function, method, class, module).

		Args:
			key (str): Tag key. Tag keys can have a maximum length of 32 characters and can contain only letters (a-zA-Z), numbers (0-9), underscores (_), periods (.), colons (:), and dashes (-).
			value: Tag value. Tag values can have a maximum length of 200 characters and they cannot contain the newline (`\\n`) character.
		"""

		# Check key format
		if not (0 < len(key) <= 32):
			L.error("Tag key '{}' is too long.".format(key))
			return

		key_pattern = re.compile("^[a-zA-Z0-9_.:-]+$")
		if key_pattern.match(key) is None:
			L.error("Tag '{}' contains invalid characters.".format(key))
			return

		# Check value format
		if not (0 < len(value) <= 200):
			L.error("Tag value '{}' is too long.".format(value))
			return

		if "\n" in value:
			L.error("Tag value {} contains '\\n' character.")
			return

		return sentry_sdk.set_tag(key, value)

	def set_tags(self, tags_dict: dict) -> None:
		"""
		Add multiple custom tags to the current scope.

		Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

		Tags are set only for the current scope (function, method, class, module).

		Args:
			tags_dict (dict): Dictionary of tag keys and values.
		"""
		for key, value in tags_dict.items():
			self.set_tag(key, value)

	def transaction(self, span_operation: str, span_name: str):
		"""
		Start a new transaction.

		Transactions are used for performance monitoring.

		This method is used as a context manager.

		Args:
			span_operation (str): Displayed span operation name that cannot be filtered, e.g., 'task'.
			span_name (str): Displayed span name that can be filtered.

		Returns:
			Transaction: A context manager that measures time operation of the task inside.

		Examples:
		```python
		with sentry_svc.transaction("speed test", "test sleeping"):
			time.sleep(1.0)
		```
		"""
		return sentry_sdk.start_transaction(op=span_operation, name=span_name)

	def span(self, operation: str, description: str):
		"""
		Create a child span within custom transaction.

		This method is used as a context manager.

		Args:
			operation (str): Displayed span operation name that cannot be filtered, e.g., 'task'.
			description (str): Displayed span name that can be filtered.

		Returns:
			Span: A context manager that measures time operation of the task inside.

		Examples:
		```python
		with sentry_svc.transaction("speed test", "multiple tasks"):
			prepare_task1()
			with sentry_svc.span("task", "task1"):
				task1()
			prepare_task2()
			with sentry_svc.span("task", "task2"):
				task2()
			finalize()
		```
		"""
		return sentry_sdk.start_span(op=operation, description=description)


	def set_context(self, section_name: str, context: dict) -> None:
		"""
		Set custom context within the current scope.

		Context data are related to the current user and the environment.
		They are useful when you have all the data available at a single point in time,
		not well suited to data that is collected over time.

		Args:
			section_name (str): A unique name for the context.
			context (dict): Key-value paired context dictionary.

		Examples:

		```python
		sentry_service.set_context("environment variables", {"VAR": os.getenv("VAR")})
		```

		!!! note
			We prefer using logging messages to setting custom context.
		"""
		return sentry_sdk.set_context(section_name, context)

capture_exception(error=None, scope=None, **scope_args) ¤

Capture caught exception and send it to Sentry.

Parameters:

Name Type Description Default
error str

Error message that will be sent. If not specified, the one currently held in sys.exc_info() is sent.

None

Examples:

try:
        call_collapsing_function()
except Exception as e:
        sentry_svc.capture_exception(e)

Source code in asab/sentry/service.py
def capture_exception(self, error=None, scope=None, **scope_args):
	"""
	Capture caught exception and send it to Sentry.

	Args:
		error (str, optional): Error message that will be sent. If not specified, the one currently held in `sys.exc_info()` is sent.

	Examples:
	```python
	try:
		call_collapsing_function()
	except Exception as e:
		sentry_svc.capture_exception(e)
	```
	"""
	return sentry_sdk.capture_exception(error=error, scope=scope, **scope_args)

capture_message(message, level=None, scope=None, **scope_args) ¤

Send textual information to Sentry.

Parameters:

Name Type Description Default
message str

Text message that will be sent.

required
Source code in asab/sentry/service.py
def capture_message(self, message, level=None, scope=None, **scope_args):
	"""
	Send textual information to Sentry.

	Args:
		message (str):  Text message that will be sent.
	"""
	return sentry_sdk.capture_message(message=message, level=level, scope=scope, **scope_args)

set_context(section_name, context) ¤

Set custom context within the current scope.

Context data are related to the current user and the environment. They are useful when you have all the data available at a single point in time, not well suited to data that is collected over time.

Parameters:

Name Type Description Default
section_name str

A unique name for the context.

required
context dict

Key-value paired context dictionary.

required

Examples:

sentry_service.set_context("environment variables", {"VAR": os.getenv("VAR")})

Note

We prefer using logging messages to setting custom context.
Source code in asab/sentry/service.py
def set_context(self, section_name: str, context: dict) -> None:
	"""
	Set custom context within the current scope.

	Context data are related to the current user and the environment.
	They are useful when you have all the data available at a single point in time,
	not well suited to data that is collected over time.

	Args:
		section_name (str): A unique name for the context.
		context (dict): Key-value paired context dictionary.

	Examples:

	```python
	sentry_service.set_context("environment variables", {"VAR": os.getenv("VAR")})
	```

	!!! note
		We prefer using logging messages to setting custom context.
	"""
	return sentry_sdk.set_context(section_name, context)

set_tag(key, value) ¤

Add custom tag to the current scope.

Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

Tag is set only for the current scope (function, method, class, module).

Parameters:

Name Type Description Default
key str

Tag key. Tag keys can have a maximum length of 32 characters and can contain only letters (a-zA-Z), numbers (0-9), underscores (_), periods (.), colons (:), and dashes (-).

required
value Union[str, int]

Tag value. Tag values can have a maximum length of 200 characters and they cannot contain the newline (\n) character.

required
Source code in asab/sentry/service.py
def set_tag(self, key: str, value: typing.Union[str, int]) -> None:
	"""
	Add custom tag to the current scope.

	Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

	Tag is set only for the current scope (function, method, class, module).

	Args:
		key (str): Tag key. Tag keys can have a maximum length of 32 characters and can contain only letters (a-zA-Z), numbers (0-9), underscores (_), periods (.), colons (:), and dashes (-).
		value: Tag value. Tag values can have a maximum length of 200 characters and they cannot contain the newline (`\\n`) character.
	"""

	# Check key format
	if not (0 < len(key) <= 32):
		L.error("Tag key '{}' is too long.".format(key))
		return

	key_pattern = re.compile("^[a-zA-Z0-9_.:-]+$")
	if key_pattern.match(key) is None:
		L.error("Tag '{}' contains invalid characters.".format(key))
		return

	# Check value format
	if not (0 < len(value) <= 200):
		L.error("Tag value '{}' is too long.".format(value))
		return

	if "\n" in value:
		L.error("Tag value {} contains '\\n' character.")
		return

	return sentry_sdk.set_tag(key, value)

set_tags(tags_dict) ¤

Add multiple custom tags to the current scope.

Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

Tags are set only for the current scope (function, method, class, module).

Parameters:

Name Type Description Default
tags_dict dict

Dictionary of tag keys and values.

required
Source code in asab/sentry/service.py
def set_tags(self, tags_dict: dict) -> None:
	"""
	Add multiple custom tags to the current scope.

	Tags are key-value string pairs that are both indexed and searchable. They can help you quickly both access related events and view the tag distribution for a set of events.

	Tags are set only for the current scope (function, method, class, module).

	Args:
		tags_dict (dict): Dictionary of tag keys and values.
	"""
	for key, value in tags_dict.items():
		self.set_tag(key, value)

span(operation, description) ¤

Create a child span within custom transaction.

This method is used as a context manager.

Parameters:

Name Type Description Default
operation str

Displayed span operation name that cannot be filtered, e.g., 'task'.

required
description str

Displayed span name that can be filtered.

required

Returns:

Name Type Description
Span

A context manager that measures time operation of the task inside.

Examples:

with sentry_svc.transaction("speed test", "multiple tasks"):
        prepare_task1()
        with sentry_svc.span("task", "task1"):
                task1()
        prepare_task2()
        with sentry_svc.span("task", "task2"):
                task2()
        finalize()

Source code in asab/sentry/service.py
def span(self, operation: str, description: str):
	"""
	Create a child span within custom transaction.

	This method is used as a context manager.

	Args:
		operation (str): Displayed span operation name that cannot be filtered, e.g., 'task'.
		description (str): Displayed span name that can be filtered.

	Returns:
		Span: A context manager that measures time operation of the task inside.

	Examples:
	```python
	with sentry_svc.transaction("speed test", "multiple tasks"):
		prepare_task1()
		with sentry_svc.span("task", "task1"):
			task1()
		prepare_task2()
		with sentry_svc.span("task", "task2"):
			task2()
		finalize()
	```
	"""
	return sentry_sdk.start_span(op=operation, description=description)

transaction(span_operation, span_name) ¤

Start a new transaction.

Transactions are used for performance monitoring.

This method is used as a context manager.

Parameters:

Name Type Description Default
span_operation str

Displayed span operation name that cannot be filtered, e.g., 'task'.

required
span_name str

Displayed span name that can be filtered.

required

Returns:

Name Type Description
Transaction

A context manager that measures time operation of the task inside.

Examples:

with sentry_svc.transaction("speed test", "test sleeping"):
        time.sleep(1.0)

Source code in asab/sentry/service.py
def transaction(self, span_operation: str, span_name: str):
	"""
	Start a new transaction.

	Transactions are used for performance monitoring.

	This method is used as a context manager.

	Args:
		span_operation (str): Displayed span operation name that cannot be filtered, e.g., 'task'.
		span_name (str): Displayed span name that can be filtered.

	Returns:
		Transaction: A context manager that measures time operation of the task inside.

	Examples:
	```python
	with sentry_svc.transaction("speed test", "test sleeping"):
		time.sleep(1.0)
	```
	"""
	return sentry_sdk.start_transaction(op=span_operation, name=span_name)