-
Notifications
You must be signed in to change notification settings - Fork 589
Description
How do you use Sentry?
Self-hosted/on-premise
Version
1.22.2
Steps to Reproduce
I use the Opentelemetry integration to send my Opentelemetry spans to Sentry. My code is manually instrumented with the opentelemetry sdk.
I noticed that if an otel span has an error status, the status is not propagated to Sentry's transaction/span trace context.
Below is a script that demonstrates the issue. It needs the following requirements to work:
opentelemetry-api==1.17.0
opentelemetry-exporter-jaeger==1.17.0
opentelemetry-sdk==1.17.0
sentry-sdk==1.22.2
The script reports two transactions to sentry: one with opentelemetry sdk, and another one with sentry sdk.
My expectations are that the transactions' trace context would be similar, however the opentelemetry-created transaction does not have a status set.
import json
import sentry_sdk
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.propagate import set_global_textmap
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
from opentelemetry.trace import Status, StatusCode
from sentry_sdk.integrations.opentelemetry import SentryPropagator, SentrySpanProcessor
tracer = trace.get_tracer("main")
class ConsoleTransport(sentry_sdk.Transport):
"""
A sentry transport that stores events in memory and sends them to stdout
instead of sending them to Sentry
"""
def __init__(self):
super().__init__()
self.transactions = []
def capture_envelope(self, envelope):
for item in envelope.items:
if item.data_category != "transaction":
continue
transaction = item.get_transaction_event()
print("captured transaction:")
print(json.dumps(transaction, indent=4))
self.transactions.append(item.get_transaction_event())
def find_transaction(self, name):
for transaction in self.transactions:
if transaction.get("transaction") == name:
return transaction
def configure_sentry(transport):
sentry_sdk.init(
# fake dsn to enable tracing
dsn="https://86b3b511153cdac55b3331da87366e18@example.com/1",
traces_sample_rate=1.0,
transport=transport,
instrumenter="otel",
)
def configure_opentelemetry():
provider = TracerProvider()
# Sentry integration
provider.add_span_processor(SentrySpanProcessor())
set_global_textmap(SentryPropagator())
# Console exporter to verify that the otel span has an error status
exporter = ConsoleSpanExporter(
formatter=lambda span: f"opentelemetry span:\n {span.to_json()}\n"
)
processor = SimpleSpanProcessor(exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
def create_otel_transaction():
"""
Capture an example transaction with opentelemetry sdk
:return:
"""
with tracer.start_as_current_span(name="otel_transaction") as span:
try:
raise ValueError("from otel transaction")
except ValueError as exc:
span.record_exception(exc)
span.set_status(Status(StatusCode.ERROR))
# At this point I expect that sentry's transaction
# would also have an error status
def create_sentry_transaction():
"""
Capture an example transaction with sentry sdk
"""
with sentry_sdk.start_transaction(
name="sentry_transaction", instrumenter="otel"
) as transaction:
try:
raise ValueError("from sentry transaction")
except ValueError:
transaction.set_status("unknown_error")
def main():
sentry_transport = ConsoleTransport()
configure_sentry(sentry_transport)
configure_opentelemetry()
create_otel_transaction()
create_sentry_transaction()
otel_transaction = sentry_transport.find_transaction("otel_transaction")
sentry_transaction = sentry_transport.find_transaction("sentry_transaction")
sentry_transaction_status = sentry_transaction["contexts"]["trace"].get("status")
otel_transaction_status = otel_transaction["contexts"]["trace"].get("status")
print(f"Sentry-created transaction status: {sentry_transaction_status}")
print(f"Otel-created transaction status: {otel_transaction_status}")
if __name__ == "__main__":
main()Expected Result
Sentry's Opentelemetry integration propagates the span status to the Sentry transaction trace context, and Sentry classifies the transaction as an error in the user interface.
Actual Result
The transaction created from an opentelemetry span does not have a status set.
Example script output:
captured sentry transaction:
{
"type": "transaction",
"transaction": "otel_transaction",
"transaction_info": {
"source": "custom"
},
"contexts": {
"otel": {
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.17.0",
"service.name": "unknown_service"
}
},
"trace": {
"trace_id": "3c27001c81f1e5f2be8fabebb94ef671",
"span_id": "d251cd410abfd884",
"parent_span_id": null,
"op": "otel_transaction",
"description": null
},
"runtime": {
"name": "CPython",
"version": "3.11.1",
"build": "3.11.1 (main, Jan 2 2023, 14:37:00) [Clang 14.0.0 (clang-1400.0.29.202)]"
}
},
"tags": {},
"timestamp": "2023-05-13T09:39:13.480122Z",
"start_timestamp": "2023-05-13T09:39:13.471644Z",
"spans": [],
"measurements": {},
"event_id": "7031c540e1384cd2ba48699bcf95a075",
"extra": {
"sys.argv": [
"/Users/danielkono/personal/python/sentry-transaction-mrrp/main2.py"
]
},
"environment": "production",
"server_name": "danielkono-o",
"sdk": {
"name": "sentry.python",
"version": "1.22.2",
"packages": [
{
"name": "pypi:sentry-sdk",
"version": "1.22.2"
}
],
"integrations": [
"argv",
"atexit",
"dedupe",
"excepthook",
"logging",
"modules",
"redis",
"stdlib",
"threading"
]
},
"platform": "python"
}
opentelemetry span:
{
"name": "otel_transaction",
"context": {
"trace_id": "0x3c27001c81f1e5f2be8fabebb94ef671",
"span_id": "0xd251cd410abfd884",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2023-05-13T07:39:13.471644Z",
"end_time": "2023-05-13T07:39:13.480122Z",
"status": {
"status_code": "ERROR"
},
"attributes": {},
"events": [
{
"name": "exception",
"timestamp": "2023-05-13T07:39:13.480099Z",
"attributes": {
"exception.type": "ValueError",
"exception.message": "from otel transaction",
"exception.stacktrace": "Traceback (most recent call last):\n File \"/Users/danielkono/personal/python/sentry-transaction-mrrp/main2.py\", line 85, in create_otel_transaction\n raise ValueError(\"from otel transaction\")\nValueError: from otel transaction\n",
"exception.escaped": "False"
}
}
],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.17.0",
"service.name": "unknown_service"
},
"schema_url": ""
}
}
captured sentry transaction:
{
"type": "transaction",
"transaction": "sentry_transaction",
"transaction_info": {
"source": "custom"
},
"contexts": {
"trace": {
"trace_id": "dd7179ed52b84d58bb59bacec4d07e36",
"span_id": "b2d81c64dc73e927",
"parent_span_id": null,
"op": null,
"description": null,
"status": "internal_error"
},
"runtime": {
"name": "CPython",
"version": "3.11.1",
"build": "3.11.1 (main, Jan 2 2023, 14:37:00) [Clang 14.0.0 (clang-1400.0.29.202)]"
}
},
"tags": {},
"timestamp": "2023-05-13T07:39:13.483194Z",
"start_timestamp": "2023-05-13T07:39:13.483116Z",
"spans": [],
"measurements": {},
"event_id": "892dda351c974b0fa73f8baaa17cf8a1",
"extra": {
"sys.argv": [
"/Users/danielkono/personal/python/sentry-transaction-mrrp/main2.py"
]
},
"environment": "production",
"server_name": "danielkono-o",
"sdk": {
"name": "sentry.python",
"version": "1.22.2",
"packages": [
{
"name": "pypi:sentry-sdk",
"version": "1.22.2"
}
],
"integrations": [
"argv",
"atexit",
"dedupe",
"excepthook",
"logging",
"modules",
"redis",
"stdlib",
"threading"
]
},
"platform": "python"
}
Sentry transaction status: internal_error
Otel transaction status: None