-
Notifications
You must be signed in to change notification settings - Fork 682
feat(boto3): Add common OTel AWS client attributes #7481
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
Changes from all commits
8518c62
7bf92dd
606c630
74083ab
7e5ba86
6868ffd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,15 @@ | ||
| from typing import TYPE_CHECKING | ||
| from urllib.parse import urlsplit | ||
|
|
||
| import sentry_sdk | ||
| from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS | ||
| from sentry_sdk.integrations import DidNotEnable | ||
| from sentry_sdk.integrations.boto3.consts import IDENTIFIER, ORIGIN | ||
| from sentry_sdk.integrations.boto3.consts import ( | ||
| AWS_RPC_SYSTEM_NAME, | ||
| DEFAULT_PORTS, | ||
| IDENTIFIER, | ||
| ORIGIN, | ||
| ) | ||
| from sentry_sdk.traces import NoOpStreamedSpan, StreamedSpan | ||
| from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, Span | ||
| from sentry_sdk.tracing_utils import ( | ||
|
|
@@ -31,6 +37,55 @@ | |
| raise DidNotEnable("botocore not installed") | ||
|
|
||
|
|
||
| def _set_span_attributes( | ||
| span: "Union[Span, StreamedSpan]", attributes: "Attributes" | ||
| ) -> None: | ||
| if isinstance(span, StreamedSpan): | ||
| span.set_attributes(attributes) | ||
| return | ||
|
|
||
| for key, value in attributes.items(): | ||
| span.set_data(key, value) | ||
|
|
||
|
|
||
| def _get_server_attributes(endpoint_url: "Optional[str]") -> "Attributes": | ||
| if not endpoint_url: | ||
| return {} | ||
|
|
||
| try: | ||
| parsed_url = urlsplit(endpoint_url) | ||
|
alexander-alderman-webb marked this conversation as resolved.
|
||
| if parsed_url.scheme not in DEFAULT_PORTS or not parsed_url.hostname: | ||
| return {} | ||
|
|
||
| # `server.port` is only defined together with `server.address`. | ||
| # Infer the effective port when the configured HTTP(S) endpoint omits it. | ||
| # https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ | ||
| return { | ||
| SPANDATA.SERVER_ADDRESS: parsed_url.hostname, | ||
| SPANDATA.SERVER_PORT: parsed_url.port or DEFAULT_PORTS[parsed_url.scheme], | ||
| } | ||
|
|
||
| except (TypeError, UnicodeError, ValueError): | ||
| # invalid client metadata must not prevent the AWS call from running. | ||
| return {} | ||
|
pabloDeputter marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def _get_client_attributes( | ||
| ctx: "AwsCallContext", | ||
| ) -> "Attributes": | ||
| attributes: "Attributes" = {} | ||
|
|
||
| # `rpc.service` is deprecated in OTel, but js still uses it. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They likely still use it because it's listed as a valid attribute in our conventions. Not something for this pull request, but it might be worth raising this in our Slack channel for the conventions to see if this is something that we should look to deprecate as well.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was gonna make a collection of all new attributes that will be added during this project (including all the service-specific as well) to check which ones contain sensitive data and whether they already map to existing data collection categories. So I'll add |
||
| if ctx.service_id: | ||
| attributes[SPANDATA.RPC_SERVICE] = ctx.service_id | ||
|
|
||
| if ctx.region_name: | ||
| attributes[SPANDATA.CLOUD_REGION] = ctx.region_name | ||
|
|
||
| attributes.update(_get_server_attributes(ctx.endpoint_url)) | ||
| return attributes | ||
|
|
||
|
|
||
| def _start_client_span( | ||
| ctx: "AwsCallContext", | ||
| ) -> "Optional[Union[Span, StreamedSpan]]": | ||
|
|
@@ -43,17 +98,27 @@ def _start_client_span( | |
| # e.g. "aws.unknown.GetObject" | ||
| service_name = ctx.service_id_hyphenized or "unknown" | ||
| span_name = f"aws.{service_name}.{ctx.operation_name}" | ||
| attributes: "Attributes" = { | ||
| SPANDATA.RPC_METHOD: ctx.operation_name, | ||
| SPANDATA.RPC_SYSTEM_NAME: AWS_RPC_SYSTEM_NAME, | ||
| } | ||
| with capture_internal_exceptions(): | ||
| attributes.update(_get_client_attributes(ctx)) | ||
| span_op = OP.HTTP_CLIENT | ||
| span_origin = ORIGIN | ||
|
|
||
| if has_span_streaming_enabled(client.options): | ||
| if sentry_sdk.traces.get_current_span() is None: | ||
| return None | ||
|
|
||
| attributes: "Attributes" = { | ||
| SPANDATA.SENTRY_OP: OP.HTTP_CLIENT, | ||
| SPANDATA.SENTRY_ORIGIN: ORIGIN, | ||
| } | ||
| if ctx.service_id: | ||
| attributes[SPANDATA.RPC_METHOD] = f"{ctx.service_id}/{ctx.operation_name}" | ||
| # `start_span()` evaluates `ignore_spans` against the initial attributes. | ||
| # https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#rpc-client-span | ||
| attributes.update( | ||
| { | ||
| SPANDATA.SENTRY_OP: span_op, | ||
| SPANDATA.SENTRY_ORIGIN: span_origin, | ||
| } | ||
| ) | ||
| return sentry_sdk.traces.start_span( | ||
| name=span_name, | ||
| attributes=attributes, | ||
|
|
@@ -64,9 +129,11 @@ def _start_client_span( | |
|
|
||
| span = sentry_sdk.start_span( | ||
| name=span_name, | ||
| op=OP.HTTP_CLIENT, | ||
| origin=ORIGIN, | ||
| op=span_op, | ||
| origin=span_origin, | ||
| ) | ||
| with capture_internal_exceptions(): | ||
| _set_span_attributes(span, attributes) | ||
| with capture_internal_exceptions(): | ||
| if ctx.service_id_hyphenized: | ||
| span.set_tag("aws.service_id", ctx.service_id_hyphenized) | ||
|
|
@@ -112,8 +179,8 @@ def _instrument_streaming_body( | |
| # unrelated new spans attach to the stream span since it's the current span. | ||
| active=False, | ||
| attributes={ | ||
| "sentry.op": OP.HTTP_CLIENT_STREAM, | ||
| "sentry.origin": ORIGIN, | ||
| SPANDATA.SENTRY_OP: OP.HTTP_CLIENT_STREAM, | ||
| SPANDATA.SENTRY_ORIGIN: ORIGIN, | ||
| }, | ||
| ) | ||
| else: | ||
|
|
@@ -211,6 +278,9 @@ def _set_request_attributes( | |
| with capture_internal_exceptions(): | ||
| parsed_url = parse_url(request.url, sanitize=False) | ||
|
|
||
| # overwrite server attributes when actual request URL is resolved. | ||
| _set_span_attributes(span, _get_server_attributes(request.url)) | ||
|
|
||
| if isinstance(span, StreamedSpan): | ||
| span.set_attributes(get_url_attributes(client, parsed_url)) | ||
| if request.method is not None: | ||
|
|
@@ -264,10 +334,9 @@ def _sentry_request_created( | |
| fresh `AWSRequest` on every retry. | ||
| https://github.com/boto/botocore/blob/f9195c79ea2bf46350dd320d2a0bf3db7da0b460/botocore/endpoint.py#L178-L202 | ||
| """ | ||
| from sentry_sdk.integrations.boto3 import Boto3Integration | ||
|
|
||
| client = sentry_sdk.get_client() | ||
| if client.get_integration(Boto3Integration) is None: | ||
| if client.get_integration("boto3") is None: | ||
| return | ||
|
|
||
| with capture_internal_exceptions(): | ||
|
|
@@ -294,10 +363,8 @@ def _sentry_request_created( | |
| def _sentry_before_sign( | ||
| request: "AWSRequest", signature_version: "Any", **kwargs: "Any" | ||
| ) -> None: | ||
| from sentry_sdk.integrations.boto3 import Boto3Integration | ||
|
|
||
| client = sentry_sdk.get_client() | ||
| if client.get_integration(Boto3Integration) is None: | ||
| if client.get_integration("boto3") is None: | ||
| return | ||
|
|
||
| with capture_internal_exceptions(): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,12 @@ | ||
| IDENTIFIER = "boto3" | ||
| ORIGIN = f"auto.http.{IDENTIFIER}" | ||
|
|
||
| # value is used by `rpc.system` (deprecated in OTel, but we still support it for now) and `rpc.system.name` | ||
| # https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/#aws-sdk-spans | ||
| AWS_RPC_SYSTEM_NAME = "aws-api" | ||
|
|
||
| # default ports for HTTP and HTTPS | ||
| DEFAULT_PORTS = { | ||
| "http": 80, | ||
| "https": 443, | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.