Sentry backend conventions for logging, tracing/spans, span/tag attribute naming, metrics tags, and the options system. Use when adding or editing Python in src/ that logs (logger.info/exception), records metrics (metrics.incr/timing with tags), instruments spans/transactions, calls sentry_sdk.set_tag/set_attribute or set_span_tag/set_span_data, or reads registered options with options.get(). Trigger on "add logging", "log an error", "add a metric", "add a span", "instrument tracing", "set an attribute", "add a tag", "read an option", "LOG005", "LOG011", or metrics tag cardinality questions.
Sentry uses a centralized options system where all options are registered in src/sentry/options/defaults.py with required default values.
# CORRECT: options.get() without default - registered default is used
from sentry import options
batch_size = options.get("deletions.group-hash-metadata.batch-size")
# WRONG: Redundant default value
batch_size = options.get("deletions.group-hash-metadata.batch-size", 1000)Important: Never add a default value to options.get() calls. All options are registered via register() in defaults.py which requires a default value. The options system always returns the registered default if no value is set, making a second default parameter redundant and potentially inconsistent.
import logging
from sentry import analytics
from sentry.analytics.events.feature_used import FeatureUsedEvent # does not exist, only for demonstration purposes
logger = logging.getLogger(__name__)
# Structured logging
logger.info(
"user.action.complete",
extra={
"user_id": user.id,
"action": "login",
"ip_address": request.META.get("REMOTE_ADDR"),
}
)
# IMPORTANT: LOG005 use exception() within an exception handler
# WRONG: Calling logger.error() when capturing exception
try:
risky_operation()
except ValidationError as e:
logger.error("error.invalid_payload")
# RIGHT: Use logger.exception() with a message when capturing an exception
try:
risky_operation()
except ValidationError:
logger.exception("error.invalid_payload")
# IMPORTANT: Avoid LOG011 - Never pre-format log messages with f-strings or .format()
# WRONG: Pre-formatting evaluates before logger call, even if logging is disabled
logger.info(f"User {user.id} completed {action}")
logger.info("User {} completed {}".format(user.id, action))
# RIGHT: Use logger's %-formatting for lazy evaluation
logger.info("%s.user.action.complete", PREFIX)
# ALSO RIGHT: Use structured logging with extra parameters only
logger.info(
"user.action.complete", extra={"user_id": user.id}
)
# Analytics event
analytics.record(
FeatureUsedEvent(
user_id=user.id,
organization_id=org.id,
feature="new-dashboard",
)
)Before inventing a key for sentry_sdk.set_tag/set_attribute, set_span_tag, or set_span_data, check whether OTel or Sentry already has a standard name for it in sentry_conventions.attributes.ATTRIBUTE_NAMES. Reusing a convention name keeps the attribute queryable and consistent with what other producers (SDKs, Relay) already emit for the same concept — a bespoke name fragments the same data across two keys.
from sentry_conventions.attributes import ATTRIBUTE_NAMES
# WRONG: inventing a name for a concept the conventions already cover
sentry_sdk.set_attribute("request_user_agent", user_agent)
# RIGHT: use the existing convention name
sentry_sdk.set_attribute(ATTRIBUTE_NAMES.USER_AGENT_ORIGINAL, user_agent)ATTRIBUTE_NAMES is generated from the OTel semantic conventions plus Sentry's own model (.venv/lib/python*/site-packages/sentry_conventions/attributes.py); grep it for candidate keywords before adding a new one. Only fall back to a custom key when the concept genuinely isn't covered, and prefer a namespaced, descriptive name over a generic one. A key kept behind a _test/POC suffix while a feature is unreleased is a separate, deliberate case — that's about hiding the field, not about picking its name.
Every distinct tag-value combination is a separate time series, so keep tags low-cardinality, meaningful, and minimal:
status, platform, reason) — never unbounded identifiers (IDs, emails, URLs, free text).The middleware (src/sentry/metrics/middleware.py) enforces this by denylisting tag keys that end in _id or that are exactly event/project/group. Such tags will not work: they're silently stripped by default, and raise BadMetricTags when SENTRY_METRICS_DISALLOW_BAD_TAGS is on (e.g. CI) — so a metric that looks fine locally can fail elsewhere.
metrics.incr("my.metric", tags={"project_id": project.id}) # WRONG: stripped / raises
metrics.incr("my.metric", tags={"platform": project.platform}) # RIGHT: bounded valuesA few keys are allowlisted despite the rule (see _NOT_BAD_TAGS); don't expand it to work around the constraint — pick a low-cardinality tag instead.
getsentry/sentry
25548ea
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.