def trace_call(name, session, extra_attributes=None): if not HAS_OPENTELEMETRY_INSTALLED or not session: # Empty context manager. Users will have to check if the generated value is None or a span yield None return tracer = trace.get_tracer(__name__) # Set base attributes that we know for every trace created attributes = { "db.type": "spanner", "db.url": SpannerClient.DEFAULT_ENDPOINT, "db.instance": session._database.name, "net.host.name": SpannerClient.DEFAULT_ENDPOINT, } if extra_attributes: attributes.update(extra_attributes) with tracer.start_as_current_span(name, kind=trace.SpanKind.CLIENT, attributes=attributes) as span: try: yield span except GoogleAPICallError as error: if error.code is not None: span.set_status( Status(http_status_to_canonical_code(error.code))) elif error.grpc_status_code is not None: span.set_status( # OpenTelemetry's StatusCanonicalCode maps 1-1 with grpc status codes Status(StatusCanonicalCode(error.grpc_status_code.value[0]) )) raise
def test_http_status_to_canonical_code(self): for status_code, expected in ( (HTTPStatus.OK, StatusCanonicalCode.OK), (HTTPStatus.ACCEPTED, StatusCanonicalCode.OK), (HTTPStatus.IM_USED, StatusCanonicalCode.OK), (HTTPStatus.MULTIPLE_CHOICES, StatusCanonicalCode.OK), (HTTPStatus.BAD_REQUEST, StatusCanonicalCode.INVALID_ARGUMENT), (HTTPStatus.UNAUTHORIZED, StatusCanonicalCode.UNAUTHENTICATED), (HTTPStatus.FORBIDDEN, StatusCanonicalCode.PERMISSION_DENIED), (HTTPStatus.NOT_FOUND, StatusCanonicalCode.NOT_FOUND), ( HTTPStatus.UNPROCESSABLE_ENTITY, StatusCanonicalCode.INVALID_ARGUMENT, ), ( HTTPStatus.TOO_MANY_REQUESTS, StatusCanonicalCode.RESOURCE_EXHAUSTED, ), (HTTPStatus.NOT_IMPLEMENTED, StatusCanonicalCode.UNIMPLEMENTED), (HTTPStatus.SERVICE_UNAVAILABLE, StatusCanonicalCode.UNAVAILABLE), ( HTTPStatus.GATEWAY_TIMEOUT, StatusCanonicalCode.DEADLINE_EXCEEDED, ), ( HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, StatusCanonicalCode.INTERNAL, ), (600, StatusCanonicalCode.UNKNOWN), (99, StatusCanonicalCode.UNKNOWN), ): with self.subTest(status_code=status_code): actual = http_status_to_canonical_code(int(status_code)) self.assertEqual(actual, expected, status_code)
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201 span = req.env.get(_ENVIRON_SPAN_KEY) if not span or not span.is_recording(): return status = resp.status reason = None if resource is None: status = "404" reason = "NotFound" exc_type, exc, _ = sys.exc_info() if exc_type and not req_succeeded: if "HTTPNotFound" in exc_type.__name__: status = "404" reason = "NotFound" else: status = "500" reason = "{}: {}".format(exc_type.__name__, exc) status = status.split(" ")[0] try: status_code = int(status) except ValueError: pass finally: span.set_attribute("http.status_code", status_code) span.set_status( Status( canonical_code=http_status_to_canonical_code(status_code), description=reason, ))
def _instrumented_requests_call( method: str, url: str, call_wrapped, get_or_create_headers ): if context.get_value("suppress_instrumentation") or context.get_value( _SUPPRESS_REQUESTS_INSTRUMENTATION_KEY ): return call_wrapped() # See # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client method = method.upper() span_name = "HTTP {}".format(method) exception = None with get_tracer( __name__, __version__, tracer_provider ).start_as_current_span(span_name, kind=SpanKind.CLIENT) as span: span.set_attribute("component", "http") span.set_attribute("http.method", method.upper()) span.set_attribute("http.url", url) headers = get_or_create_headers() propagators.inject(type(headers).__setitem__, headers) token = context.attach( context.set_value(_SUPPRESS_REQUESTS_INSTRUMENTATION_KEY, True) ) try: result = call_wrapped() # *** PROCEED except Exception as exc: # pylint: disable=W0703 exception = exc result = getattr(exc, "response", None) finally: context.detach(token) if exception is not None: span.set_status( Status(_exception_to_canonical_code(exception)) ) span.record_exception(exception) if result is not None: span.set_attribute("http.status_code", result.status_code) span.set_attribute("http.status_text", result.reason) span.set_status( Status(http_status_to_canonical_code(result.status_code)) ) if span_callback is not None: span_callback(span, result) if exception is not None: raise exception.with_traceback(exception.__traceback__) return result
async def on_request_end( unused_session: aiohttp.ClientSession, trace_config_ctx: types.SimpleNamespace, params: aiohttp.TraceRequestEndParams, ): trace_config_ctx.span.set_status( Status(http_status_to_canonical_code(int(params.response.status)))) trace_config_ctx.span.set_attribute("http.status_code", params.response.status) trace_config_ctx.span.set_attribute("http.status_text", params.response.reason) _end_trace(trace_config_ctx)
def instrumented_request(self, method, url, *args, **kwargs): if context.get_value("suppress_instrumentation"): return wrapped(self, method, url, *args, **kwargs) # See # https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client try: parsed_url = urlparse(url) span_name = parsed_url.path except ValueError as exc: # Invalid URL span_name = "<Unparsable URL: {}>".format(exc) exception = None with get_tracer( __name__, __version__, tracer_provider ).start_as_current_span(span_name, kind=SpanKind.CLIENT) as span: span.set_attribute("component", "http") span.set_attribute("http.method", method.upper()) span.set_attribute("http.url", url) headers = kwargs.get("headers", {}) or {} propagators.inject(type(headers).__setitem__, headers) kwargs["headers"] = headers try: result = wrapped( self, method, url, *args, **kwargs ) # *** PROCEED except Exception as exc: # pylint: disable=W0703 exception = exc result = getattr(exc, "response", None) if exception is not None: span.set_status( Status(_exception_to_canonical_code(exception)) ) if result is not None: span.set_attribute("http.status_code", result.status_code) span.set_attribute("http.status_text", result.reason) span.set_status( Status(http_status_to_canonical_code(result.status_code)) ) if span_callback is not None: span_callback(span, result) if exception is not None: raise exception.with_traceback(exception.__traceback__) return result
def set_status_code(span, status_code): """Adds HTTP response attributes to span using the status_code argument.""" try: status_code = int(status_code) except ValueError: span.set_status( Status( StatusCanonicalCode.UNKNOWN, "Non-integer HTTP status: " + repr(status_code), )) else: span.set_attribute("http.status_code", status_code) span.set_status(Status(http_status_to_canonical_code(status_code)))
def create_span(name, attributes=None, client=None, job_ref=None): """Creates a ContextManager for a Span to be exported to the configured exporter. If no configuration exists yields None. Args: name (str): Name that will be set for the span being created attributes (Optional[dict]): Additional attributes that pertain to the specific API call (i.e. not a default attribute) client (Optional[google.cloud.bigquery.client.Client]): Pass in a Client object to extract any attributes that may be relevant to it and add them to the created spans. job_ref (Optional[google.cloud.bigquery.job._AsyncJob]) Pass in a _AsyncJob object to extract any attributes that may be relevant to it and add them to the created spans. Yields: opentelemetry.trace.Span: Yields the newly created Span. Raises: google.api_core.exceptions.GoogleAPICallError: Raised if a span could not be yielded or issue with call to OpenTelemetry. """ global _warned_telemetry final_attributes = _get_final_span_attributes(attributes, client, job_ref) if not HAS_OPENTELEMETRY: if not _warned_telemetry: logger.debug( "This service is instrumented using OpenTelemetry. " "OpenTelemetry could not be imported; please " "add opentelemetry-api and opentelemetry-instrumentation " "packages in order to get BigQuery Tracing data." ) _warned_telemetry = True yield None return tracer = trace.get_tracer(__name__) # yield new span value with tracer.start_as_current_span(name=name, attributes=final_attributes) as span: try: yield span except GoogleAPICallError as error: if error.code is not None: span.set_status(Status(http_status_to_canonical_code(error.code))) raise
def add_response_attributes(span, start_response_status, response_headers): # pylint: disable=unused-argument """Adds HTTP response attributes to span using the arguments passed to a PEP3333-conforming start_response callable.""" status_code, status_text = start_response_status.split(" ", 1) span.set_attribute("http.status_text", status_text) try: status_code = int(status_code) except ValueError: span.set_status( Status( StatusCanonicalCode.UNKNOWN, "Non-integer HTTP status: " + repr(status_code), )) else: span.set_attribute("http.status_code", status_code) span.set_status(Status(http_status_to_canonical_code(status_code)))
def _finish_tracing_callback(future, span): status_code = None description = None exc = future.exception() if exc: if isinstance(exc, HTTPError): status_code = exc.code description = "{}: {}".format(type(exc).__name__, exc) else: status_code = future.result().code if status_code is not None: span.set_attribute("http.status_code", status_code) span.set_status( Status( canonical_code=http_status_to_canonical_code(status_code), description=description, )) span.end()
def _finish_span(tracer, handler, error=None): status_code = handler.get_status() reason = getattr(handler, "_reason") finish_args = (None, None, None) ctx = getattr(handler, _HANDLER_CONTEXT_KEY, None) if error: if isinstance(error, tornado.web.HTTPError): status_code = error.status_code if not ctx and status_code == 404: ctx = _start_span(tracer, handler, time_ns()) if status_code != 404: finish_args = ( type(error), error, getattr(error, "__traceback__", None), ) status_code = 500 reason = None if not ctx: return if ctx.span.is_recording(): if reason: ctx.span.set_attribute("http.status_text", reason) ctx.span.set_attribute("http.status_code", status_code) ctx.span.set_status( Status( canonical_code=http_status_to_canonical_code(status_code), description=reason, )) ctx.activation.__exit__(*finish_args) context.detach(ctx.token) delattr(handler, _HANDLER_CONTEXT_KEY)