예제 #1
0
def test_dotted_span_type_conversion(zuqa_client):
    zuqa_client.begin_transaction("test")
    with capture_span("foo", "type"):
        with capture_span("bar", "type.subtype"):
            with capture_span("baz", "type.subtype.action"):
                with capture_span("bazzinga", "type.subtype.action.more"):
                    pass
    zuqa_client.end_transaction("test", "OK")
    spans = zuqa_client.events[SPAN]

    assert spans[0]["name"] == "bazzinga"
    assert spans[0]["type"] == "type"
    assert spans[0]["subtype"] == "subtype"
    assert spans[0]["action"] == "action"

    assert spans[1]["name"] == "baz"
    assert spans[1]["type"] == "type"
    assert spans[1]["subtype"] == "subtype"
    assert spans[1]["action"] == "action"

    assert spans[2]["name"] == "bar"
    assert spans[2]["type"] == "type"
    assert spans[2]["subtype"] == "subtype"
    assert spans[2]["action"] is None

    assert spans[3]["name"] == "foo"
    assert spans[3]["type"] == "type"
    assert spans[3]["subtype"] is None
    assert spans[3]["action"] is None
예제 #2
0
def test_logging_filter_span(zuqa_client):
    transaction = zuqa_client.begin_transaction("test")
    with capture_span("test") as span:
        f = LoggingFilter()
        record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252,
                                   "dummy_msg", [], None)
        f.filter(record)
        assert record.zuqa_transaction_id == transaction.id
        assert record.zuqa_trace_id == transaction.trace_parent.trace_id
        assert record.zuqa_span_id == span.id
        assert record.zuqa_labels

    # Capture too many spans so we start dropping
    for i in range(10):
        with capture_span("drop"):
            pass

    # Test logging with DroppedSpan
    with capture_span("drop") as span:
        record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252,
                                   "dummy_msg2", [], None)
        f.filter(record)
        assert record.zuqa_transaction_id == transaction.id
        assert record.zuqa_trace_id == transaction.trace_parent.trace_id
        assert record.zuqa_span_id is None
        assert record.zuqa_labels
예제 #3
0
 def call(self, module, method, wrapped, instance, args, kwargs):
     collection = instance.collection
     signature = ".".join([collection.full_name, "cursor.refresh"])
     with capture_span(
             signature,
             span_type="db",
             span_subtype="mongodb",
             span_action="query",
             extra={
                 "destination": {
                     "service": {
                         "name": "mongodb",
                         "resource": "mongodb",
                         "type": "db"
                     }
                 }
             },
     ) as span:
         response = wrapped(*args, **kwargs)
         if span.context and instance.address:
             host, port = instance.address
             span.context["destination"]["address"] = host
             span.context["destination"]["port"] = port
         else:
             pass
         return response
def test_requests_instrumentation_via_session(instrument, zuqa_client,
                                              waiting_httpserver):
    waiting_httpserver.serve_content("")
    url = waiting_httpserver.url + "/hello_world"
    zuqa_client.begin_transaction("transaction.test")
    with capture_span("test_request", "test"):
        s = requests.Session()
        s.get(url, allow_redirects=False)
    zuqa_client.end_transaction("MyView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])
    assert spans[0]["name"].startswith("GET 127.0.0.1:")
    assert url == spans[0]["context"]["http"]["url"]

    assert constants.TRACEPARENT_HEADER_NAME in waiting_httpserver.requests[
        0].headers
    trace_parent = TraceParent.from_string(
        waiting_httpserver.requests[0].headers[
            constants.TRACEPARENT_HEADER_NAME])
    assert trace_parent.trace_id == transactions[0]["trace_id"]

    # this should be the span id of `requests`, not of urllib3
    assert trace_parent.span_id == spans[0]["id"]
    assert trace_parent.trace_options.recorded
예제 #5
0
 def call(self, module, method, wrapped, instance, args, kwargs):
     cls_name, method_name = method.split(".", 1)
     signature = ".".join([instance.full_name, method_name])
     nodes = instance.database.client.nodes
     if nodes:
         host, port = list(nodes)[0]
     else:
         host, port = None, None
     destination_info = {
         "address": host,
         "port": port,
         "service": {
             "name": "mongodb",
             "resource": "mongodb",
             "type": "db"
         },
     }
     with capture_span(
             signature,
             span_type="db",
             span_subtype="mongodb",
             span_action="query",
             leaf=True,
             extra={"destination": destination_info},
     ):
         return wrapped(*args, **kwargs)
def test_pymemcache_hash_client(instrument, zuqa_client):
    zuqa_client.begin_transaction("transaction.test")
    host = os.environ.get("MEMCACHED_HOST", "localhost")
    with capture_span("test_pymemcache", "test"):
        conn = pymemcache.client.hash.HashClient([(host, 11211)])
        conn.set("mykey", "a")
        assert b"a" == conn.get("mykey")
        assert {"mykey": b"a"} == conn.get_many(["mykey", "myotherkey"])
    zuqa_client.end_transaction("BillingView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])

    expected_signatures = {
        "test_pymemcache",
        "HashClient.set",
        "HashClient.get",
        "HashClient.get_many",
        "Client.set",
        "Client.get",
        "Client.get_many",
    }

    assert {t["name"] for t in spans} == expected_signatures

    assert len(spans) == 7
def test_rq_patches_redis(instrument, zuqa_client, redis_conn):
    # Let's go ahead and change how something important works
    redis_conn._pipeline = partial(StrictRedis.pipeline, redis_conn)

    zuqa_client.begin_transaction("transaction.test")
    with capture_span("test_pipeline", "test"):
        # conn = redis.StrictRedis()
        pipeline = redis_conn._pipeline()
        pipeline.rpush("mykey", "a", "b")
        pipeline.expire("mykey", 1000)
        pipeline.execute()
    zuqa_client.end_transaction("MyView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])

    assert spans[0]["name"] in ("StrictPipeline.execute", "Pipeline.execute")
    assert spans[0]["type"] == "db"
    assert spans[0]["subtype"] == "redis"
    assert spans[0]["action"] == "query"
    assert spans[0]["context"]["destination"] == {
        "address": os.environ.get("REDIS_HOST", "localhost"),
        "port": int(os.environ.get("REDIS_PORT", 6379)),
        "service": {
            "name": "redis",
            "resource": "redis",
            "type": "db"
        },
    }

    assert spans[1]["name"] == "test_pipeline"
    assert spans[1]["type"] == "test"

    assert len(spans) == 2
def test_requests_instrumentation(instrument, zuqa_client, waiting_httpserver):
    waiting_httpserver.serve_content("")
    url = waiting_httpserver.url + "/hello_world"
    parsed_url = compat.urlparse.urlparse(url)
    zuqa_client.begin_transaction("transaction.test")
    with capture_span("test_request", "test"):
        requests.get(url, allow_redirects=False)
    zuqa_client.end_transaction("MyView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])
    assert spans[0]["name"].startswith("GET 127.0.0.1:")
    assert spans[0]["type"] == "external"
    assert spans[0]["subtype"] == "http"
    assert url == spans[0]["context"]["http"]["url"]
    assert spans[0]["context"]["destination"]["service"] == {
        "name": "http://127.0.0.1:%d" % parsed_url.port,
        "resource": "127.0.0.1:%d" % parsed_url.port,
        "type": "external",
    }

    assert constants.TRACEPARENT_HEADER_NAME in waiting_httpserver.requests[
        0].headers
    trace_parent = TraceParent.from_string(
        waiting_httpserver.requests[0].headers[
            constants.TRACEPARENT_HEADER_NAME])
    assert trace_parent.trace_id == transactions[0]["trace_id"]

    # this should be the span id of `requests`, not of urllib3
    assert trace_parent.span_id == spans[0]["id"]
    assert trace_parent.trace_options.recorded
예제 #9
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        request_object = args[1] if len(args) > 1 else kwargs["req"]

        method = request_object.get_method()
        host = request_host(request_object)

        url = sanitize_url(request_object.get_full_url())
        destination = url_to_destination(url)
        signature = method.upper() + " " + host

        transaction = execution_context.get_transaction()

        with capture_span(
            signature,
            span_type="external",
            span_subtype="http",
            extra={"http": {"url": url}, "destination": destination},
            leaf=True,
        ) as span:
            # if urllib has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True)
            )
            self._set_disttracing_headers(request_object, trace_parent, transaction)
            return wrapped(*args, **kwargs)
예제 #10
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        signature = "psycopg2.connect"

        host = kwargs.get("host")
        if host:
            signature += " " + compat.text_type(host)

            port = kwargs.get("port")
            if port:
                port = str(port)
                if int(port) != default_ports.get("postgresql"):
                    host += ":" + port
            signature += " " + compat.text_type(host)
        else:
            # Parse connection string and extract host/port
            pass
        destination_info = {
            "address": kwargs.get("host", "localhost"),
            "port": int(kwargs.get("port", default_ports.get("postgresql"))),
            "service": {
                "name": "postgresql",
                "resource": "postgresql",
                "type": "db"
            },
        }
        with capture_span(
                signature,
                span_type="db",
                span_subtype="postgresql",
                span_action="connect",
                extra={"destination": destination_info},
        ):
            return PGConnectionProxy(wrapped(*args, **kwargs),
                                     destination_info=destination_info)
예제 #11
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if len(args) > 0:
            wrapped_name = str(args[0])
        else:
            wrapped_name = self.get_wrapped_name(wrapped, instance, method)

        with capture_span(wrapped_name, span_type="db", span_subtype="redis", span_action="query", leaf=True):
            return wrapped(*args, **kwargs)
    def call(self, module, method, wrapped, instance, args, kwargs):
        name = self.get_wrapped_name(wrapped, instance, method)

        # Since HashClient uses Client/PooledClient for the actual calls, we
        # don't need to get address/port info for that class
        address, port = None, None
        if getattr(instance, "server", None):
            if isinstance(instance.server, (list, tuple)):
                # Address/port are a tuple
                address, port = instance.server
            else:
                # Server is a UNIX domain socket
                address = instance.server
        destination = {
            "address": address,
            "port": port,
            "service": {
                "name": "memcached",
                "resource": "memcached",
                "type": "cache"
            },
        }

        if "PooledClient" in name:
            # PooledClient calls out to Client for the "work", but only once,
            # so we don't care about the "duplicate" spans from Client in that
            # case
            with capture_span(
                    name,
                    span_type="cache",
                    span_subtype="memcached",
                    span_action="query",
                    extra={"destination": destination},
                    leaf=True,
            ):
                return wrapped(*args, **kwargs)
        else:
            with capture_span(
                    name,
                    span_type="cache",
                    span_subtype="memcached",
                    span_action="query",
                    extra={"destination": destination},
            ):
                return wrapped(*args, **kwargs)
예제 #13
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "method" in kwargs:
            method = kwargs["method"]
        else:
            method = args[0]

        headers = None
        if "headers" in kwargs:
            headers = kwargs["headers"]
            if headers is None:
                headers = {}
                kwargs["headers"] = headers

        host = instance.host

        if instance.port != default_ports.get(instance.scheme):
            host += ":" + str(instance.port)

        if "url" in kwargs:
            url = kwargs["url"]
        else:
            url = args[1]

        signature = method.upper() + " " + host

        url = "%s://%s%s" % (instance.scheme, host, url)
        destination = url_to_destination(url)

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={
                    "http": {
                        "url": url
                    },
                    "destination": destination
                },
                leaf=True,
        ) as span:
            # if urllib3 has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            if headers is not None:
                # It's possible that there are only dropped spans, e.g. if we started dropping spans.
                # In this case, the transaction.id is used
                parent_id = leaf_span.id if leaf_span else transaction.id
                trace_parent = transaction.trace_parent.copy_from(
                    span_id=parent_id,
                    trace_options=TracingOptions(recorded=True))
                self._set_disttracing_headers(headers, trace_parent,
                                              transaction)
            return wrapped(*args, **kwargs)
예제 #14
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        name = getattr(instance, "name", None)

        if not name:
            name = "<template string>"
        with capture_span(name,
                          span_type="template",
                          span_subtype="django",
                          span_action="render"):
            return wrapped(*args, **kwargs)
예제 #15
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "template_name" in kwargs:
            name = kwargs["template_name"]
        else:
            name = args[0]

        with capture_span(name,
                          span_type="template",
                          span_subtype="tornado",
                          span_action="render"):
            return wrapped(*args, **kwargs)
예제 #16
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        signature = ".".join([module, method])

        if len(args) == 1:
            signature += " " + str(args[0])

        with capture_span(signature,
                          span_type="db",
                          span_subtype="sqlite",
                          span_action="connect"):
            return SQLiteConnectionProxy(wrapped(*args, **kwargs))
예제 #17
0
def test_leaf_tracing(tracer):
    tracer.begin_transaction("transaction.test")

    with capture_span("root", "custom"):
        with capture_span("child1-leaf", "custom", leaf=True):

            # These two spans should not show up
            with capture_span("ignored-child1", "custom", leaf=True, duration=0.01):
                pass

            with capture_span("ignored-child2", "custom", leaf=False, duration=0.01):
                pass

    tracer.end_transaction(None, "transaction")

    spans = tracer.events[SPAN]

    assert len(spans) == 2

    signatures = {"root", "child1-leaf"}
    assert {t["name"] for t in spans} == signatures
예제 #18
0
def test_structlog_processor_span(zuqa_client):
    transaction = zuqa_client.begin_transaction("test")
    with capture_span("test") as span:
        event_dict = {}
        new_dict = structlog_processor(None, None, event_dict)
        assert new_dict["transaction.id"] == transaction.id
        assert new_dict["trace.id"] == transaction.trace_parent.trace_id
        assert new_dict["span.id"] == span.id

    # Capture too many spans so we start dropping
    for i in range(10):
        with capture_span("drop"):
            pass

    # Test logging with DroppedSpan
    with capture_span("drop") as span:
        event_dict = {}
        new_dict = structlog_processor(None, None, event_dict)
        assert new_dict["transaction.id"] == transaction.id
        assert new_dict["trace.id"] == transaction.trace_parent.trace_id
        assert "span.id" not in new_dict
    def call(self, module, method, wrapped, instance, args, kwargs):
        name = self.get_wrapped_name(wrapped, instance, method)
        context = {}
        if method == "Cluster.connect":
            span_action = "connect"
            if hasattr(instance,
                       "contact_points_resolved"):  # < cassandra-driver 3.18
                host = instance.contact_points_resolved[0]
                port = instance.port
            else:
                host = instance.endpoints_resolved[0].address
                port = instance.endpoints_resolved[0].port
        else:
            hosts = list(instance.hosts)
            if hasattr(hosts[0], "endpoint"):
                host = hosts[0].endpoint.address
                port = hosts[0].endpoint.port
            else:
                # < cassandra-driver 3.18
                host = hosts[0].address
                port = instance.cluster.port
            span_action = "query"
            query = args[0] if args else kwargs.get("query")
            if hasattr(query, "query_string"):
                query_str = query.query_string
            elif hasattr(query, "prepared_statement") and hasattr(
                    query.prepared_statement, "query"):
                query_str = query.prepared_statement.query
            elif isinstance(query, compat.string_types):
                query_str = query
            else:
                query_str = None
            if query_str:
                name = extract_signature(query_str)
                context["db"] = {"type": "sql", "statement": query_str}
        context["destination"] = {
            "address": host,
            "port": port,
            "service": {
                "name": "cassandra",
                "resource": "cassandra",
                "type": "db"
            },
        }

        with capture_span(name,
                          span_type="db",
                          span_subtype="cassandra",
                          span_action=span_action,
                          extra=context):
            return wrapped(*args, **kwargs)
예제 #20
0
def test_pylibmc(instrument, zuqa_client):
    zuqa_client.begin_transaction("transaction.test")
    host = os.environ.get("MEMCACHED_HOST", "localhost")
    with capture_span("test_memcached", "test"):
        conn = pylibmc.Client([host + ":11211"])
        conn.set("mykey", "a")
        assert "a" == conn.get("mykey")
        assert {"mykey": "a"} == conn.get_multi(["mykey", "myotherkey"])
    zuqa_client.end_transaction("BillingView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])

    expected_signatures = {
        "test_memcached", "Client.set", "Client.get", "Client.get_multi"
    }

    assert {t["name"] for t in spans} == expected_signatures

    assert spans[0]["name"] == "Client.set"
    assert spans[0]["type"] == "cache"
    assert spans[0]["subtype"] == "memcached"
    assert spans[0]["action"] == "query"
    assert spans[0]["parent_id"] == spans[3]["id"]

    assert spans[1]["name"] == "Client.get"
    assert spans[1]["type"] == "cache"
    assert spans[1]["subtype"] == "memcached"
    assert spans[1]["action"] == "query"
    assert spans[1]["parent_id"] == spans[3]["id"]
    assert spans[1]["context"]["destination"] == {
        "address": host,
        "port": 11211,
        "service": {
            "name": "memcached",
            "resource": "memcached",
            "type": "cache"
        },
    }

    assert spans[2]["name"] == "Client.get_multi"
    assert spans[2]["type"] == "cache"
    assert spans[2]["subtype"] == "memcached"
    assert spans[2]["action"] == "query"
    assert spans[2]["parent_id"] == spans[3]["id"]

    assert spans[3]["name"] == "test_memcached"
    assert spans[3]["type"] == "test"

    assert len(spans) == 4
예제 #21
0
def test_automatic_log_record_factory_install(zuqa_client):
    """
    Use the zuqa_client fixture to load the client, which in turn installs
    the log_record_factory. Check to make sure it happened.
    """
    transaction = zuqa_client.begin_transaction("test")
    with capture_span("test") as span:
        record_factory = logging.getLogRecordFactory()
        record = record_factory(__name__, logging.DEBUG, __file__, 252,
                                "dummy_msg", [], None)
        assert record.zuqa_transaction_id == transaction.id
        assert record.zuqa_trace_id == transaction.trace_parent.trace_id
        assert record.zuqa_span_id == span.id
        assert record.zuqa_labels
예제 #22
0
 def _trace_sql(self, method, sql, params):
     signature = extract_signature(sql)
     with capture_span(
             signature,
             span_type="db",
             span_subtype="sqlite",
             span_action="query",
             extra={"db": {
                 "type": "sql",
                 "statement": sql
             }},
     ):
         if params is None:
             return method(sql)
         else:
             return method(sql, params)
예제 #23
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "operation_name" in kwargs:
            operation_name = kwargs["operation_name"]
        else:
            operation_name = args[0]

        target_endpoint = instance._endpoint.host
        parsed_url = urlparse.urlparse(target_endpoint)
        if "." in parsed_url.hostname:
            service = parsed_url.hostname.split(".", 2)[0]
        else:
            service = parsed_url.hostname

        signature = "{}:{}".format(service, operation_name)

        with capture_span(signature, "aws", leaf=True, span_subtype=service, span_action=operation_name):
            return wrapped(*args, **kwargs)
def test_redis_client(instrument, zuqa_client, redis_conn):
    zuqa_client.begin_transaction("transaction.test")
    with capture_span("test_redis_client", "test"):
        redis_conn.rpush("mykey", "a", "b")
        redis_conn.expire("mykey", 1000)
    zuqa_client.end_transaction("MyView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])

    expected_signatures = {"test_redis_client", "RPUSH", "EXPIRE"}

    assert {t["name"] for t in spans} == expected_signatures

    assert spans[0]["name"] == "RPUSH"
    assert spans[0]["type"] == "db"
    assert spans[0]["subtype"] == "redis"
    assert spans[0]["action"] == "query"
    assert spans[0]["context"]["destination"] == {
        "address": os.environ.get("REDIS_HOST", "localhost"),
        "port": int(os.environ.get("REDIS_PORT", 6379)),
        "service": {
            "name": "redis",
            "resource": "redis",
            "type": "db"
        },
    }

    assert spans[1]["name"] == "EXPIRE"
    assert spans[1]["type"] == "db"
    assert spans[1]["subtype"] == "redis"
    assert spans[1]["action"] == "query"
    assert spans[1]["context"]["destination"] == {
        "address": os.environ.get("REDIS_HOST", "localhost"),
        "port": int(os.environ.get("REDIS_PORT", 6379)),
        "service": {
            "name": "redis",
            "resource": "redis",
            "type": "db"
        },
    }

    assert spans[2]["name"] == "test_redis_client"
    assert spans[2]["type"] == "test"

    assert len(spans) == 3
def test_span_only_dropped(instrument, zuqa_client, waiting_httpserver):
    """test that urllib instrumentation does not fail if no parent span can be found"""
    waiting_httpserver.serve_content("")
    url = waiting_httpserver.url + "/hello_world"
    transaction_object = zuqa_client.begin_transaction("transaction")
    for i in range(2):
        with capture_span("test", "test"):
            urlopen(url)
    zuqa_client.end_transaction("bla", "OK")
    trace_parent_1 = TraceParent.from_string(
        waiting_httpserver.requests[0].headers[
            constants.TRACEPARENT_HEADER_NAME])
    trace_parent_2 = TraceParent.from_string(
        waiting_httpserver.requests[1].headers[
            constants.TRACEPARENT_HEADER_NAME])

    assert trace_parent_1.span_id != transaction_object.id
    # second request should use transaction id as span id because there is no span
    assert trace_parent_2.span_id == transaction_object.id
예제 #26
0
 def call(self, module, method, wrapped, instance, args, kwargs):
     collection = instance._BulkOperationBuilder__bulk.collection
     signature = ".".join([collection.full_name, "bulk.execute"])
     with capture_span(
             signature,
             span_type="db",
             span_subtype="mongodb",
             span_action="query",
             extra={
                 "destination": {
                     "service": {
                         "name": "mongodb",
                         "resource": "mongodb",
                         "type": "db"
                     }
                 }
             },
     ):
         return wrapped(*args, **kwargs)
예제 #27
0
 def call(self, module, method, wrapped, instance, args, kwargs):
     wrapped_name = self.get_wrapped_name(wrapped, instance, method)
     address, port = get_address_port_from_instance(instance)
     destination = {
         "address": address,
         "port": port,
         "service": {
             "name": "memcached",
             "resource": "memcached",
             "type": "cache"
         },
     }
     with capture_span(
             wrapped_name,
             span_type="cache",
             span_subtype="memcached",
             span_action="query",
             extra={"destination": destination},
     ):
         return wrapped(*args, **kwargs)
예제 #28
0
 def call(self, module, method, wrapped, instance, args, kwargs):
     name = self.get_wrapped_name(wrapped, instance, method)
     address, port = None, None
     if instance.servers:
         address, port = instance.servers[0].address
     destination = {
         "address": address,
         "port": port,
         "service": {
             "name": "memcached",
             "resource": "memcached",
             "type": "cache"
         },
     }
     with capture_span(name,
                       span_type="cache",
                       span_subtype="memcached",
                       span_action="query",
                       extra={"destination": destination}):
         return wrapped(*args, **kwargs)
예제 #29
0
 def _trace_sql(self, method, sql, params, action=QUERY_ACTION):
     sql_string = self._bake_sql(sql)
     if action == EXEC_ACTION:
         signature = sql_string + "()"
     else:
         signature = self.extract_signature(sql_string)
     with capture_span(
         signature,
         span_type="db",
         span_subtype=self.provider_name,
         span_action=action,
         extra={"db": {"type": "sql", "statement": sql_string}, "destination": self._self_destination_info},
         skip_frames=1,
     ) as span:
         if params is None:
             result = method(sql)
         else:
             result = method(sql, params)
         # store "rows affected", but only for DML queries like insert/update/delete
         if span and self.rowcount not in (-1, None) and signature.startswith(self.DML_QUERIES):
             span.update_context("db", {"rows_affected": self.rowcount})
         return result
def test_urllib3(instrument, zuqa_client, waiting_httpserver):
    waiting_httpserver.serve_content("")
    url = waiting_httpserver.url + "/hello_world"
    parsed_url = urlparse.urlparse(url)
    zuqa_client.begin_transaction("transaction")
    expected_sig = "GET {0}".format(parsed_url.netloc)
    with capture_span("test_name", "test_type"):
        pool = urllib3.PoolManager(timeout=0.1)

        url = "http://{0}/hello_world".format(parsed_url.netloc)
        r = pool.request("GET", url)

    zuqa_client.end_transaction("MyView")

    transactions = zuqa_client.events[TRANSACTION]
    spans = zuqa_client.spans_for_transaction(transactions[0])

    expected_signatures = {"test_name", expected_sig}

    assert {t["name"] for t in spans} == expected_signatures

    assert len(spans) == 2

    assert spans[0]["name"] == expected_sig
    assert spans[0]["type"] == "external"
    assert spans[0]["subtype"] == "http"
    assert spans[0]["context"]["http"]["url"] == url
    assert spans[0]["context"]["destination"]["service"] == {
        "name": "http://127.0.0.1:%d" % parsed_url.port,
        "resource": "127.0.0.1:%d" % parsed_url.port,
        "type": "external",
    }
    assert spans[0]["parent_id"] == spans[1]["id"]

    assert spans[1]["name"] == "test_name"
    assert spans[1]["type"] == "test_type"
    assert spans[1]["parent_id"] == transactions[0]["id"]