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
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
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
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
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)
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)
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)
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)
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)
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)
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))
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
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)
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
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
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)
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
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)
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)
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)
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"]