예제 #1
0
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
예제 #2
0
 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)
예제 #3
0
    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
예제 #5
0
 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)
예제 #6
0
    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
예제 #7
0
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)))
예제 #8
0
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
예제 #9
0
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)))
예제 #10
0
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)