def trace_before_publish(*args, **kwargs): # `before_task_publish` signal doesn't propagate the task instance so # we need to retrieve it from the Celery Registry to access the `Pin`. The # `Task` instance **does not** include any information about the current # execution, so it **must not** be used to retrieve `request` data. task_name = kwargs.get('sender') task = registry.tasks.get(task_name) task_id = retrieve_task_id(kwargs) # safe-guard to avoid crashes in case the signals API # changes in Celery if task is None or task_id is None: log.debug( 'unable to extract the Task and the task_id. This version of Celery may not be supported.' ) return # propagate the `Span` in the current task Context pin = Pin.get_from(task) or Pin.get_from(task.app) if pin is None: return # apply some tags here because most of the data is not available # in the task_after_publish signal service = config.celery['producer_service_name'] span = pin.tracer.trace(c.PRODUCER_ROOT_SPAN, service=service, resource=task_name) span.set_tag(c.TASK_TAG_KEY, c.TASK_APPLY_ASYNC) span.set_tag('celery.id', task_id) span.set_tags(tags_from_context(kwargs)) # Note: adding tags from `traceback` or `state` calls will make an # API call to the backend for the properties so we should rely # only on the given `Context` attach_span(task, task_id, span, is_publish=True)
def get_tracer_and_connect(self): tracer = get_dummy_tracer() Pin.get_from(mongoengine.connect).clone(tracer=tracer).onto( mongoengine.connect) mongoengine.connect(port=MONGO_CONFIG['port']) return tracer
def setUp(self): super(TestRedisPatch, self).setUp() patch() r = redis.Redis(port=self.TEST_PORT) r.flushall() Pin.override(r, service=self.TEST_SERVICE, tracer=self.tracer) self.r = r
def test_patch_unpatch(self): tracer = get_dummy_tracer() writer = tracer.writer # Test patch idempotence patch() patch() client = pymongo.MongoClient(port=MONGO_CONFIG['port']) Pin.get_from(client).clone(tracer=tracer).onto(client) client['testdb'].drop_collection('whatever') spans = writer.pop() assert spans, spans assert len(spans) == 1 # Test unpatch unpatch() client = pymongo.MongoClient(port=MONGO_CONFIG['port']) client['testdb'].drop_collection('whatever') spans = writer.pop() assert not spans, spans # Test patch again patch() client = pymongo.MongoClient(port=MONGO_CONFIG['port']) Pin.get_from(client).clone(tracer=tracer).onto(client) client['testdb'].drop_collection('whatever') spans = writer.pop() assert spans, spans assert len(spans) == 1
def test_fetch_no_analytics(self): """ Confirm fetch* methods do not have analytics sample rate metric """ with self.override_config( 'dbapi2', dict(analytics_enabled=True) ): cursor = self.cursor cursor.rowcount = 0 cursor.fetchone.return_value = '__result__' pin = Pin('pin_name', tracer=self.tracer) traced_cursor = FetchTracedCursor(cursor, pin) assert '__result__' == traced_cursor.fetchone('arg_1', kwarg1='kwarg1') span = self.tracer.writer.pop()[0] self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY)) cursor = self.cursor cursor.rowcount = 0 cursor.fetchall.return_value = '__result__' pin = Pin('pin_name', tracer=self.tracer) traced_cursor = FetchTracedCursor(cursor, pin) assert '__result__' == traced_cursor.fetchall('arg_1', kwarg1='kwarg1') span = self.tracer.writer.pop()[0] self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY)) cursor = self.cursor cursor.rowcount = 0 cursor.fetchmany.return_value = '__result__' pin = Pin('pin_name', tracer=self.tracer) traced_cursor = FetchTracedCursor(cursor, pin) assert '__result__' == traced_cursor.fetchmany('arg_1', kwarg1='kwarg1') span = self.tracer.writer.pop()[0] self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
def test_cursor_override(self): """Test overriding the tracer with our own.""" conn, cur = self.test_conn Pin.override(cur, tracer=self.test_tracer) with conn: cur.execute( "INSERT INTO {} (a, b) VALUES (1, 'aa');".format(TEST_TABLE)) cur.execute('SELECT * FROM {};'.format(TEST_TABLE)) spans = self.test_tracer.writer.pop() assert len(spans) == 2 # check all the metadata assert spans[0].service == 'vertica' assert spans[0].span_type == 'sql' assert spans[0].name == 'vertica.query' assert spans[0].get_metric('db.rowcount') == -1 query = "INSERT INTO test_table (a, b) VALUES (1, 'aa');" assert spans[0].resource == query assert spans[0].get_tag('out.host') == '127.0.0.1' assert spans[0].get_tag('out.port') == '5433' assert spans[1].resource == 'SELECT * FROM test_table;'
def test_configuration_routine(self): """Ensure that the integration routines can be configured.""" routine_config = dict(patch={ 'vertica_python.vertica.connection.Connection': dict(routines=dict(cursor=dict( operation_name='get_cursor', trace_enabled=True, ), ), ), }, ) # Make a copy of the vertica config first before we merge our settings over # DEV: First argument gets merged into the second copy = deepmerge(config.vertica, dict()) overrides = deepmerge(routine_config, copy) with self.override_config('vertica', overrides): patch() import vertica_python test_tracer = get_dummy_tracer() conn = vertica_python.connect(**VERTICA_CONFIG) Pin.override(conn, service='mycustomservice', tracer=test_tracer) conn.cursor() # should be traced now conn.close() spans = test_tracer.writer.pop() assert len(spans) == 1 assert spans[0].name == 'get_cursor' assert spans[0].service == 'mycustomservice'
def test_blueprint_add_url_rule(self): """ When we call ``flask.Blueprint.add_url_rule`` When the ``Blueprint`` has a ``Pin`` attached We clone the Blueprint's ``Pin`` to the view When the ``Blueprint`` does not have a ``Pin`` attached We do not attach a ``Pin`` to the func """ # When the Blueprint has a Pin attached bp = flask.Blueprint('pinned', __name__) Pin(service='flask-bp', tracer=self.tracer).onto(bp) @bp.route('/') def test_view(): pass # Assert the view func has a `Pin` attached with the Blueprint's service name pin = Pin.get_from(test_view) self.assertIsNotNone(pin) self.assertEqual(pin.service, 'flask-bp') # When the Blueprint does not have a Pin attached bp = flask.Blueprint('not-pinned', __name__) @bp.route('/') def test_view(): pass # Assert the view does not have a `Pin` attached pin = Pin.get_from(test_view) self.assertIsNone(pin)
def test_patch_unpatch(self): tracer = get_dummy_tracer() writer = tracer.writer # Test patch idempotence patch() patch() r = redis.Redis(port=REDIS_CONFIG['port']) Pin.get_from(r).clone(tracer=tracer).onto(r) r.get('key') spans = writer.pop() assert spans, spans assert len(spans) == 1 # Test unpatch unpatch() r = redis.Redis(port=REDIS_CONFIG['port']) r.get('key') spans = writer.pop() assert not spans, spans # Test patch again patch() r = redis.Redis(port=REDIS_CONFIG['port']) Pin.get_from(r).clone(tracer=tracer).onto(r) r.get('key') spans = writer.pop() assert spans, spans assert len(spans) == 1
def test_route_success(self): """ Tests request was a success with the expected span tags """ response = molten_client() spans = self.tracer.writer.pop() self.assertEqual(response.status_code, 200) # TestResponse from TestClient is wrapper around Response so we must # access data property self.assertEqual(response.data, '"Hello 24 year old named Jim!"') span = spans[0] self.assertEqual(span.service, 'molten') self.assertEqual(span.name, 'molten.request') self.assertEqual(span.resource, 'GET /hello/{name}/{age}') self.assertEqual(span.get_tag('http.method'), 'GET') self.assertEqual(span.get_tag(http.URL), 'http://127.0.0.1:8000/hello/Jim/24') self.assertEqual(span.get_tag('http.status_code'), '200') assert http.QUERY_STRING not in span.meta # See test_resources below for specifics of this difference if MOLTEN_VERSION >= (0, 7, 2): self.assertEqual(len(spans), 18) else: self.assertEqual(len(spans), 16) # test override of service name Pin.override(molten, service=self.TEST_SERVICE) response = molten_client() spans = self.tracer.writer.pop() self.assertEqual(spans[0].service, 'molten-patch')
def test_blueprint_request_pin_override(self): """ When making a request to a Blueprint's endpoint When we attach a ``Pin`` to the Blueprint We create the expected spans """ bp = flask.Blueprint('bp', __name__) Pin.override(bp, service='flask-bp', tracer=self.tracer) @bp.route('/') def test(): return 'test' self.app.register_blueprint(bp) # Request the endpoint self.client.get('/') # Only extract the span we care about # DEV: Making a request creates a bunch of lifecycle spans, # ignore them, we test them elsewhere span = self.find_span_by_name(self.get_spans(), 'bp.test') self.assertEqual(span.service, 'flask-bp') self.assertEqual(span.name, 'bp.test') self.assertEqual(span.resource, '/') self.assertEqual(span.meta, dict())
def make_client(self, mock_socket_values, **kwargs): tracer = get_dummy_tracer() Pin.override(pymemcache, tracer=tracer) self.client = pymemcache.client.base.Client((TEST_HOST, TEST_PORT), **kwargs) self.client.sock = MockSocket(list(mock_socket_values)) return self.client
def test_patch_unpatch(self): tracer = get_dummy_tracer() writer = tracer.writer # Test patch idempotence patch() patch() r = self._get_test_client() Pin.get_from(r).clone(tracer=tracer).onto(r) r.get('key') spans = writer.pop() assert spans, spans assert len(spans) == 1 # Test unpatch unpatch() r = self._get_test_client() r.get('key') spans = writer.pop() assert not spans, spans # Test patch again patch() r = self._get_test_client() Pin.get_from(r).clone(tracer=tracer).onto(r) r.get('key') spans = writer.pop() assert spans, spans assert len(spans) == 1
def setUp(self): super(TestRedisPatch, self).setUp() patch() r = self._get_test_client() r.flushall() Pin.override(r, service=self.TEST_SERVICE, tracer=self.tracer) self.r = r
def setUp(self): patch() # prevent cache effects when using Template('code...') jinja2.environment._spontaneous_environments.clear() # provide a dummy tracer self.tracer = get_dummy_tracer() Pin.override(jinja2.environment.Environment, tracer=self.tracer)
def test_patch_unpatch(self): # Test patch idempotence patch() patch() tracer = get_dummy_tracer() Pin.get_from(Cluster).clone(tracer=tracer).onto(Cluster) session = Cluster(port=CASSANDRA_CONFIG['port']).connect(self.TEST_KEYSPACE) session.execute(self.TEST_QUERY) spans = tracer.writer.pop() assert spans, spans assert len(spans) == 1 # Test unpatch unpatch() session = Cluster(port=CASSANDRA_CONFIG['port']).connect(self.TEST_KEYSPACE) session.execute(self.TEST_QUERY) spans = tracer.writer.pop() assert not spans, spans # Test patch again patch() Pin.get_from(Cluster).clone(tracer=tracer).onto(Cluster) session = Cluster(port=CASSANDRA_CONFIG['port']).connect(self.TEST_KEYSPACE) session.execute(self.TEST_QUERY) spans = tracer.writer.pop() assert spans, spans
def test_execute_metadata(self): """Metadata related to an `execute` call should be captured.""" conn, cur = self.test_conn Pin.override(cur, tracer=self.test_tracer) with conn: cur.execute( "INSERT INTO {} (a, b) VALUES (1, 'aa');".format(TEST_TABLE)) cur.execute('SELECT * FROM {};'.format(TEST_TABLE)) spans = self.test_tracer.writer.pop() assert len(spans) == 2 # check all the metadata assert spans[0].service == 'vertica' assert spans[0].span_type == 'sql' assert spans[0].name == 'vertica.query' assert spans[0].get_metric('db.rowcount') == -1 query = "INSERT INTO test_table (a, b) VALUES (1, 'aa');" assert spans[0].resource == query assert spans[0].get_tag('out.host') == '127.0.0.1' assert spans[0].get_tag('out.port') == '5433' assert spans[0].get_tag('db.name') == 'docker' assert spans[0].get_tag('db.user') == 'dbadmin' assert spans[1].resource == 'SELECT * FROM test_table;'
def _traced_session(self): tracer = get_dummy_tracer() # pin the global Cluster to test if they will conflict Pin(service='not-%s' % self.TEST_SERVICE).onto(Cluster) self.cluster = Cluster(port=CASSANDRA_CONFIG['port']) Pin(service=self.TEST_SERVICE, tracer=tracer).onto(self.cluster) return self.cluster.connect(self.TEST_KEYSPACE), tracer
def get_client(self): url = '%s:%s' % (cfg['host'], cfg['port']) client = pylibmc.Client([url]) client.flush_all() Pin.get_from(client).clone(tracer=self.tracer).onto(client) return client, self.tracer
def test_same_tracer(self): """Ensure same tracer reference is used by the pin on pymemache and Clients. """ client = pymemcache.client.base.Client((TEST_HOST, TEST_PORT)) self.assertEqual( Pin.get_from(client).tracer, Pin.get_from(pymemcache).tracer)
def get_tracer_and_connect(self): tracer = get_dummy_tracer() # Set a connect-level service, to check that we properly override it Pin(service='not-%s' % self.TEST_SERVICE).onto(mongoengine.connect) client = mongoengine.connect(port=MONGO_CONFIG['port']) Pin(service=self.TEST_SERVICE, tracer=tracer).onto(client) return tracer
def test_pin_config(self): # ensure `Pin` has a configuration object that can be modified obj = self.Obj() Pin.override(obj, service='metrics') pin = Pin.get_from(obj) assert pin._config is not None pin._config['distributed_tracing'] = True assert pin._config['distributed_tracing'] is True
def patch_conn(conn): tags = {t: getattr(conn, a) for t, a in CONN_ATTR_BY_TAG.items() if getattr(conn, a, '') != ''} pin = Pin(service='mysql', app='mysql', app_type=AppTypes.db, tags=tags) # grab the metadata from the conn wrapped = TracedConnection(conn, pin=pin) pin.onto(wrapped) return wrapped
def test_pin(self): # ensure a Pin can be attached to an instance obj = self.Obj() pin = Pin(service='metrics') pin.onto(obj) got = Pin.get_from(obj) assert got.service == pin.service assert got is pin
def test_cant_pin_with_slots(self): # ensure a Pin can't be attached if the __slots__ is defined class Obj(object): __slots__ = ['value'] obj = Obj() obj.value = 1 Pin(service='metrics').onto(obj) got = Pin.get_from(obj) assert got is None
def trace_spans(self): conn, _ = yield from self._get_conn_and_tracer() Pin.get_from(conn).clone(service='db', tracer=self.tracer).onto(conn) cursor = yield from conn.cursor() yield from cursor.execute('select \'foobar\'') rows = yield from cursor.fetchall() assert rows return self.get_spans()
def setUp(self): super(CeleryBaseTestCase, self).setUp() # instrument Celery and create an app with Broker and Result backends patch() self.pin = Pin(service='celery-unittest', tracer=self.tracer) self.app = Celery('celery.test_app', broker=BROKER_URL, backend=BACKEND_URL) # override pins to use our Dummy Tracer Pin.override(self.app, tracer=self.tracer)
def test_service_name_override(self): client = self.make_client( [b'STORED\r\n', b'VALUE key 0 5\r\nvalue\r\nEND\r\n']) Pin.override(client, service='testsvcname') client.set(b'key', b'value', noreply=False) result = client.get(b'key') assert _str(result) == 'value' spans = self.get_spans() self.assertEqual(spans[0].service, 'testsvcname') self.assertEqual(spans[1].service, 'testsvcname')
def test_override_missing(self): # ensure overriding an instance doesn't override the Class class A(object): pass a = A() assert Pin.get_from(a) is None Pin.override(a, service='metrics') assert Pin.get_from(a).service == 'metrics' b = A() assert Pin.get_from(b) is None
def setUp(self): super(TestKombuPatch, self).setUp() conn = kombu.Connection('amqp://*****:*****@127.0.0.1:{p}//'.format(p=self.TEST_PORT)) conn.connect() producer = conn.Producer() Pin.override(producer, service=self.TEST_SERVICE, tracer=self.tracer) self.conn = conn self.producer = producer patch()