예제 #1
0
def test_tracer_wrap_factory_nested():
    # it should use a wrap_factory if defined even in nested tracing
    writer = DummyWriter()
    tracer = Tracer()
    tracer.writer = writer

    def wrap_executor(tracer, fn, args, kwargs, span_name=None, service=None, resource=None, span_type=None):
        with tracer.trace('wrap.overwrite') as span:
            span.set_tag('args', args)
            span.set_tag('kwargs', kwargs)
            return fn(*args, **kwargs)

    @tracer.wrap()
    def wrapped_function(param, kw_param=None):
        eq_(42, param)
        eq_(42, kw_param)

    # set the custom wrap factory after the wrapper has been called
    tracer.configure(wrap_executor=wrap_executor)

    # call the function expecting that the custom tracing wrapper is used
    with tracer.trace('wrap.parent', service='webserver'):
        wrapped_function(42, kw_param=42)

    eq_(writer.spans[0].name, 'wrap.parent')
    eq_(writer.spans[0].service, 'webserver')

    eq_(writer.spans[1].name, 'wrap.overwrite')
    eq_(writer.spans[1].service, 'webserver')
    eq_(writer.spans[1].get_tag('args'), '(42,)')
    eq_(writer.spans[1].get_tag('kwargs'), '{\'kw_param\': 42}')
예제 #2
0
def test_tracer_wrap_factory():
    # it should use a wrap_factory if defined
    writer = DummyWriter()
    tracer = Tracer()
    tracer.writer = writer

    def wrap_executor(tracer,
                      fn,
                      args,
                      kwargs,
                      span_name=None,
                      service=None,
                      resource=None,
                      span_type=None):
        with tracer.trace('wrap.overwrite') as span:
            span.set_tag('args', args)
            span.set_tag('kwargs', kwargs)
            return fn(*args, **kwargs)

    @tracer.wrap()
    def wrapped_function(param, kw_param=None):
        eq_(42, param)
        eq_(42, kw_param)

    # set the custom wrap factory after the wrapper has been called
    tracer.configure(wrap_executor=wrap_executor)

    # call the function expecting that the custom tracing wrapper is used
    wrapped_function(42, kw_param=42)
    eq_(writer.spans[0].name, 'wrap.overwrite')
    eq_(writer.spans[0].get_tag('args'), '(42,)')
    eq_(writer.spans[0].get_tag('kwargs'), '{\'kw_param\': 42}')
예제 #3
0
 def test_detect_agentless_env_with_lambda(self):
     assert _in_aws_lambda()
     assert not _has_aws_lambda_agent_extension()
     tracer = Tracer()
     assert isinstance(tracer.writer, LogWriter)
     tracer.configure(enabled=True)
     assert isinstance(tracer.writer, LogWriter)
예제 #4
0
 def test_configure_keeps_api_hostname_and_port(self):
     tracer = Tracer()  # use real tracer with real api
     assert 'localhost' == tracer.writer.api.hostname
     assert 8126 == tracer.writer.api.port
     tracer.configure(hostname='127.0.0.1', port=8127)
     assert '127.0.0.1' == tracer.writer.api.hostname
     assert 8127 == tracer.writer.api.port
     tracer.configure(priority_sampling=True)
     assert '127.0.0.1' == tracer.writer.api.hostname
     assert 8127 == tracer.writer.api.port
예제 #5
0
 def test_configure_keeps_api_hostname_and_port(self):
     tracer = Tracer() # use real tracer with real api
     eq_('localhost', tracer.writer.api.hostname)
     eq_(8126, tracer.writer.api.port)
     tracer.configure(hostname='127.0.0.1', port=8127)
     eq_('127.0.0.1', tracer.writer.api.hostname)
     eq_(8127, tracer.writer.api.port)
     tracer.configure(priority_sampling=True)
     eq_('127.0.0.1', tracer.writer.api.hostname)
     eq_(8127, tracer.writer.api.port)
예제 #6
0
 def test_configure_keeps_api_hostname_and_port(self):
     tracer = Tracer()  # use real tracer with real api
     eq_('localhost', tracer.writer.api.hostname)
     eq_(8126, tracer.writer.api.port)
     tracer.configure(hostname='127.0.0.1', port=8127)
     eq_('127.0.0.1', tracer.writer.api.hostname)
     eq_(8127, tracer.writer.api.port)
     tracer.configure(priority_sampling=True)
     eq_('127.0.0.1', tracer.writer.api.hostname)
     eq_(8127, tracer.writer.api.port)
예제 #7
0
    def test_configure_dogstatsd_url_host_port(self):
        tracer = Tracer()
        tracer.configure(dogstatsd_url="foo:1234")
        assert tracer.writer.dogstatsd.host == "foo"
        assert tracer.writer.dogstatsd.port == 1234

        tracer = Tracer()
        writer = AgentWriter("http://localhost:8126")
        tracer.configure(writer=writer, dogstatsd_url="foo:1234")
        assert tracer.writer.dogstatsd.host == "foo"
        assert tracer.writer.dogstatsd.port == 1234
예제 #8
0
    def test_configure_dogstatsd_url_socket(self):
        tracer = Tracer()
        tracer.configure(dogstatsd_url="unix:///foo.sock")
        assert tracer.writer.dogstatsd.host is None
        assert tracer.writer.dogstatsd.port is None
        assert tracer.writer.dogstatsd.socket_path == "/foo.sock"

        tracer = Tracer()
        writer = AgentWriter("http://localhost:8126")
        tracer.configure(writer=writer, dogstatsd_url="unix:///foo.sock")
        assert tracer.writer.dogstatsd.host is None
        assert tracer.writer.dogstatsd.port is None
        assert tracer.writer.dogstatsd.socket_path == "/foo.sock"
예제 #9
0
    def test_detect_agent_config_with_lambda_extension(self):
        def mock_os_path_exists(path):
            return path == "/opt/extensions/datadog-agent"

        assert _in_aws_lambda()

        with mock.patch("os.path.exists", side_effect=mock_os_path_exists):
            assert _has_aws_lambda_agent_extension()

            tracer = Tracer()
            assert isinstance(tracer.writer, AgentWriter)
            assert tracer.writer._sync_mode

            tracer.configure(enabled=False)
            assert isinstance(tracer.writer, AgentWriter)
            assert tracer.writer._sync_mode
예제 #10
0
class TestTornadoSettings(TornadoTestCase):
    """
    Ensure that Tornado web application properly configures the given tracer.
    """
    def get_app(self):
        # Override with a real tracer
        self.tracer = Tracer()
        super(TestTornadoSettings, self).get_app()

    def get_settings(self):
        # update tracer settings
        return {
            "datadog_trace": {
                "default_service": "custom-tornado",
                "tags": {
                    "env": "production",
                    "debug": "false"
                },
                "enabled": False,
                "agent_hostname": "dd-agent.service.consul",
                "agent_port": 8126,
                "settings": {
                    "FILTERS": [
                        TestFilter(),
                    ],
                },
            },
        }

    def test_tracer_is_properly_configured(self):
        # the tracer must be properly configured
        assert self.tracer._tags.get("env") == "production"
        assert self.tracer._tags.get("debug") == "false"
        assert self.tracer.enabled is False
        assert self.tracer.agent_trace_url == "http://dd-agent.service.consul:8126"

        writer = DummyWriter()
        self.tracer.configure(enabled=True, writer=writer)
        with self.tracer.trace("keep"):
            pass
        spans = writer.pop()
        assert len(spans) == 1

        with self.tracer.trace("drop"):
            pass
        spans = writer.pop()
        assert len(spans) == 0
예제 #11
0
class TestWorkers(TestCase):
    """
    Ensures that a workers interacts correctly with the main thread. These are part
    of integration tests so real calls are triggered.
    """
    def _decode(self, payload):
        """
        Helper function that decodes data based on the given Encoder.
        """
        if isinstance(self.api._encoder, JSONEncoder):
            return json.loads(payload)
        elif isinstance(self.api._encoder, MsgpackEncoder):
            return msgpack.unpackb(payload, encoding='utf-8')

    def setUp(self):
        """
        Create a tracer with running workers, while spying the ``_put()`` method to
        keep trace of triggered API calls.
        """
        # create a new tracer
        self.tracer = Tracer()
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

    def tearDown(self):
        """
        Stop running worker
        """
        self._wait_thread_flush()

    def _wait_thread_flush(self):
        """
        Helper that waits for the thread flush
        """
        self.tracer.writer.stop()
        self.tracer.writer.join(None)

    def _get_endpoint_payload(self, calls, endpoint):
        """
        Helper to retrieve the endpoint call from a concurrent
        trace or service call.
        """
        for call, _ in calls:
            if endpoint in call[0]:
                return call[0], self._decode(call[1])

        return None, None

    @skipUnless(os.environ.get(
        'TEST_DATADOG_INTEGRATION_UDS', False
    ), 'You should have a running trace agent on a socket and set TEST_DATADOG_INTEGRATION_UDS=1 env variable'
                )
    def test_worker_single_trace_uds(self):
        self.tracer.configure(uds_path='/tmp/ddagent/trace.sock')
        # Write a first trace so we get a _worker
        self.tracer.trace('client.testing').finish()
        worker = self.tracer.writer
        worker._log_error_status = mock.Mock(
            worker._log_error_status,
            wraps=worker._log_error_status,
        )
        self.tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        # Check that no error was logged
        assert worker._log_error_status.call_count == 0

    def test_worker_single_trace_uds_wrong_socket_path(self):
        self.tracer.configure(uds_path='/tmp/ddagent/nosockethere')
        # Write a first trace so we get a _worker
        self.tracer.trace('client.testing').finish()
        worker = self.tracer.writer
        worker._log_error_status = mock.Mock(
            worker._log_error_status,
            wraps=worker._log_error_status,
        )
        self.tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        # Check that no error was logged
        assert worker._log_error_status.call_count == 1

    def test_worker_single_trace(self):
        # create a trace block and send it using the transport system
        tracer = self.tracer
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.4/traces')
        assert endpoint == '/v0.4/traces'
        assert len(payload) == 1
        assert len(payload[0]) == 1
        assert payload[0][0]['name'] == 'client.testing'

    # DEV: If we can make the writer flushing deterministic for the case of tests, then we can re-enable this
    @skip(
        'Writer flush intervals are impossible to time correctly to make this test not flaky'
    )
    def test_worker_multiple_traces(self):
        # make a single send() if multiple traces are created before the flush interval
        tracer = self.tracer
        tracer.trace('client.testing').finish()
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.4/traces')
        assert endpoint == '/v0.4/traces'
        assert len(payload) == 2
        assert len(payload[0]) == 1
        assert len(payload[1]) == 1
        assert payload[0][0]['name'] == 'client.testing'
        assert payload[1][0]['name'] == 'client.testing'

    def test_worker_single_trace_multiple_spans(self):
        # make a single send() if a single trace with multiple spans is created before the flush
        tracer = self.tracer
        parent = tracer.trace('client.testing')
        tracer.trace('client.testing').finish()
        parent.finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.4/traces')
        assert endpoint == '/v0.4/traces'
        assert len(payload) == 1
        assert len(payload[0]) == 2
        assert payload[0][0]['name'] == 'client.testing'
        assert payload[0][1]['name'] == 'client.testing'

    def test_worker_http_error_logging(self):
        # Tests the logging http error logic
        tracer = self.tracer
        self.tracer.writer.api = FlawedAPI(Tracer.DEFAULT_HOSTNAME,
                                           Tracer.DEFAULT_PORT)
        tracer.trace('client.testing').finish()

        log = logging.getLogger('ddtrace.internal.writer')
        log_handler = MockedLogHandler(level='DEBUG')
        log.addHandler(log_handler)

        self._wait_thread_flush()
        assert tracer.writer._last_error_ts < time.time()

        logged_errors = log_handler.messages['error']
        assert len(logged_errors) == 1
        assert 'Failed to send traces to Datadog Agent at localhost:8126: ' \
            'HTTP error status 400, reason Bad Request, message Content-Type:' \
            in logged_errors[0]

    def test_worker_filter_request(self):
        self.tracer.configure(settings={
            FILTERS_KEY: [FilterRequestsOnUrl(r'http://example\.com/health')]
        })
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

        span = self.tracer.trace('testing.filteredurl')
        span.set_tag(http.URL, 'http://example.com/health')
        span.finish()
        span = self.tracer.trace('testing.nonfilteredurl')
        span.set_tag(http.URL, 'http://example.com/api/resource')
        span.finish()
        self._wait_thread_flush()

        # Only the second trace should have been sent
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.4/traces')
        assert endpoint == '/v0.4/traces'
        assert len(payload) == 1
        assert payload[0][0]['name'] == 'testing.nonfilteredurl'
예제 #12
0
class TestWorkers(TestCase):
    """
    Ensures that a workers interacts correctly with the main thread. These are part
    of integration tests so real calls are triggered.
    """
    def _decode(self, payload):
        """
        Helper function that decodes data based on the given Encoder.
        """
        if isinstance(self.api._encoder, JSONEncoder):
            return json.loads(payload)
        elif isinstance(self.api._encoder, MsgpackEncoder):
            return msgpack.unpackb(payload, encoding='utf-8')

    def setUp(self):
        """
        Create a tracer with running workers, while spying the ``_put()`` method to
        keep trace of triggered API calls.
        """
        # create a new tracer
        self.tracer = Tracer()
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

    def tearDown(self):
        """
        Stop running worker
        """
        self.tracer.writer._worker.stop()

    def _wait_thread_flush(self):
        """
        Helper that waits for the thread flush
        """
        self.tracer.writer._worker.stop()
        self.tracer.writer._worker.join()

    def _get_endpoint_payload(self, calls, endpoint):
        """
        Helper to retrieve the endpoint call from a concurrent
        trace or service call.
        """
        for call, _ in calls:
            if endpoint in call[0]:
                return call[0], self._decode(call[1])

        return None, None

    def test_worker_single_trace(self):
        # create a trace block and send it using the transport system
        tracer = self.tracer
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(len(payload[0]), 1)
        eq_(payload[0][0]['name'], 'client.testing')

    def test_worker_multiple_traces(self):
        # make a single send() if multiple traces are created before the flush interval
        tracer = self.tracer
        tracer.trace('client.testing').finish()
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 2)
        eq_(len(payload[0]), 1)
        eq_(len(payload[1]), 1)
        eq_(payload[0][0]['name'], 'client.testing')
        eq_(payload[1][0]['name'], 'client.testing')

    def test_worker_single_trace_multiple_spans(self):
        # make a single send() if a single trace with multiple spans is created before the flush
        tracer = self.tracer
        parent = tracer.trace('client.testing')
        child = tracer.trace('client.testing').finish()
        parent.finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(len(payload[0]), 2)
        eq_(payload[0][0]['name'], 'client.testing')
        eq_(payload[0][1]['name'], 'client.testing')

    def test_worker_single_service(self):
        # service must be sent correctly
        tracer = self.tracer
        tracer.set_service_info('client.service', 'django', 'web')
        tracer.trace('client.testing').finish()

        # expect a call for traces and services
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 2)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/services')
        eq_(endpoint, '/v0.3/services')
        eq_(len(payload.keys()), 1)
        eq_(payload['client.service'], {'app': 'django', 'app_type': 'web'})

    def test_worker_service_called_multiple_times(self):
        # service must be sent correctly
        tracer = self.tracer
        tracer.set_service_info('backend', 'django', 'web')
        tracer.set_service_info('database', 'postgres', 'db')
        tracer.trace('client.testing').finish()

        # expect a call for traces and services
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 2)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/services')
        eq_(endpoint, '/v0.3/services')
        eq_(len(payload.keys()), 2)
        eq_(payload['backend'], {'app': 'django', 'app_type': 'web'})
        eq_(payload['database'], {'app': 'postgres', 'app_type': 'db'})

    def test_worker_http_error_logging(self):
        # Tests the logging http error logic
        tracer = self.tracer
        self.tracer.writer.api = FlawedAPI(Tracer.DEFAULT_HOSTNAME, Tracer.DEFAULT_PORT)
        tracer.trace('client.testing').finish()

        log = logging.getLogger("ddtrace.writer")
        log_handler = MockedLogHandler(level='DEBUG')
        log.addHandler(log_handler)

        # sleeping 1.01 secs to prevent writer from exiting before logging
        time.sleep(1.01)
        self._wait_thread_flush()
        assert tracer.writer._worker._last_error_ts < time.time()

        logged_errors = log_handler.messages['error']
        eq_(len(logged_errors), 1)
        ok_('failed_to_send traces to Agent: HTTP error status 400, reason Bad Request, message Content-Type:'
            in logged_errors[0])

    def test_worker_filter_request(self):
        self.tracer.configure(settings={FILTERS_KEY: [FilterRequestsOnUrl(r'http://example\.com/health')]})
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

        span = self.tracer.trace('testing.filteredurl')
        span.set_tag(http.URL, 'http://example.com/health')
        span.finish()
        span = self.tracer.trace('testing.nonfilteredurl')
        span.set_tag(http.URL, 'http://example.com/api/resource')
        span.finish()
        self._wait_thread_flush()

        # Only the second trace should have been sent
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(payload[0][0]['name'], 'testing.nonfilteredurl')
예제 #13
0
class TestWorkers(TestCase):
    """
    Ensures that a workers interacts correctly with the main thread. These are part
    of integration tests so real calls are triggered.
    """
    def _decode(self, payload):
        """
        Helper function that decodes data based on the given Encoder.
        """
        if isinstance(self.api._encoder, JSONEncoder):
            return json.loads(payload)
        elif isinstance(self.api._encoder, MsgpackEncoder):
            return msgpack.unpackb(payload, encoding='utf-8')

    def setUp(self):
        """
        Create a tracer with running workers, while spying the ``_put()`` method to
        keep trace of triggered API calls.
        """
        # create a new tracer
        self.tracer = Tracer()
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

    def tearDown(self):
        """
        Stop running worker
        """
        self.tracer.writer._worker.stop()

    def _wait_thread_flush(self):
        """
        Helper that waits for the thread flush
        """
        self.tracer.writer._worker.stop()
        self.tracer.writer._worker.join()

    def _get_endpoint_payload(self, calls, endpoint):
        """
        Helper to retrieve the endpoint call from a concurrent
        trace or service call.
        """
        for call, _ in calls:
            if endpoint in call[0]:
                return call[0], self._decode(call[1])

        return None, None

    def test_worker_single_trace(self):
        # create a trace block and send it using the transport system
        tracer = self.tracer
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(len(payload[0]), 1)
        eq_(payload[0][0]['name'], 'client.testing')

    # DEV: If we can make the writer flushing deterministic for the case of tests, then we can re-enable this
    @skip(
        'Writer flush intervals are impossible to time correctly to make this test not flaky'
    )
    def test_worker_multiple_traces(self):
        # make a single send() if multiple traces are created before the flush interval
        tracer = self.tracer
        tracer.trace('client.testing').finish()
        tracer.trace('client.testing').finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 2)
        eq_(len(payload[0]), 1)
        eq_(len(payload[1]), 1)
        eq_(payload[0][0]['name'], 'client.testing')
        eq_(payload[1][0]['name'], 'client.testing')

    def test_worker_single_trace_multiple_spans(self):
        # make a single send() if a single trace with multiple spans is created before the flush
        tracer = self.tracer
        parent = tracer.trace('client.testing')
        child = tracer.trace('client.testing').finish()
        parent.finish()

        # one send is expected
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(len(payload[0]), 2)
        eq_(payload[0][0]['name'], 'client.testing')
        eq_(payload[0][1]['name'], 'client.testing')

    def test_worker_single_service(self):
        # service must be sent correctly
        tracer = self.tracer
        tracer.set_service_info('client.service', 'django', 'web')
        tracer.trace('client.testing').finish()

        # expect a call for traces and services
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 2)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/services')
        eq_(endpoint, '/v0.3/services')
        eq_(len(payload.keys()), 1)
        eq_(payload['client.service'], {'app': 'django', 'app_type': 'web'})

    def test_worker_service_called_multiple_times(self):
        # service must be sent correctly
        tracer = self.tracer
        tracer.set_service_info('backend', 'django', 'web')
        tracer.set_service_info('database', 'postgres', 'db')
        tracer.trace('client.testing').finish()

        # expect a call for traces and services
        self._wait_thread_flush()
        eq_(self.api._put.call_count, 2)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/services')
        eq_(endpoint, '/v0.3/services')
        eq_(len(payload.keys()), 2)
        eq_(payload['backend'], {'app': 'django', 'app_type': 'web'})
        eq_(payload['database'], {'app': 'postgres', 'app_type': 'db'})

    def test_worker_http_error_logging(self):
        # Tests the logging http error logic
        tracer = self.tracer
        self.tracer.writer.api = FlawedAPI(Tracer.DEFAULT_HOSTNAME,
                                           Tracer.DEFAULT_PORT)
        tracer.trace('client.testing').finish()

        log = logging.getLogger("ddtrace.writer")
        log_handler = MockedLogHandler(level='DEBUG')
        log.addHandler(log_handler)

        # sleeping 1.01 secs to prevent writer from exiting before logging
        time.sleep(1.01)
        self._wait_thread_flush()
        assert tracer.writer._worker._last_error_ts < time.time()

        logged_errors = log_handler.messages['error']
        eq_(len(logged_errors), 1)
        ok_('failed_to_send traces to Agent: HTTP error status 400, reason Bad Request, message Content-Type:'
            in logged_errors[0])

    def test_worker_filter_request(self):
        self.tracer.configure(settings={
            FILTERS_KEY: [FilterRequestsOnUrl(r'http://example\.com/health')]
        })
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

        span = self.tracer.trace('testing.filteredurl')
        span.set_tag(http.URL, 'http://example.com/health')
        span.finish()
        span = self.tracer.trace('testing.nonfilteredurl')
        span.set_tag(http.URL, 'http://example.com/api/resource')
        span.finish()
        self._wait_thread_flush()

        # Only the second trace should have been sent
        eq_(self.api._put.call_count, 1)
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, '/v0.3/traces')
        eq_(endpoint, '/v0.3/traces')
        eq_(len(payload), 1)
        eq_(payload[0][0]['name'], 'testing.nonfilteredurl')
예제 #14
0
class TestWorkers(TestCase):
    """
    Ensures that a workers interacts correctly with the main thread. These are part
    of integration tests so real calls are triggered.
    """
    def setUp(self):
        """
        Create a tracer with running workers, while spying the ``_put()`` method to
        keep trace of triggered API calls.
        """
        # create a new tracer
        self.tracer = Tracer()
        # spy the send() method
        self.api = self.tracer.writer.api
        self.api._put = mock.Mock(self.api._put, wraps=self.api._put)

    def tearDown(self):
        """
        Stop running worker
        """
        self._wait_thread_flush()

    def _wait_thread_flush(self):
        """
        Helper that waits for the thread flush
        """
        self.tracer.writer.flush_queue()
        self.tracer.writer.stop()
        self.tracer.writer.join(None)

    def _get_endpoint_payload(self, calls, endpoint):
        """
        Helper to retrieve the endpoint call from a concurrent
        trace or service call.
        """
        for call, _ in calls:
            if endpoint in call[0]:
                return call[0], self.api._encoder._decode(call[1])

        return None, None

    @pytest.mark.skipif(AGENT_VERSION == "v5" or AGENT_VERSION == "testagent",
                        reason="Agent doesn't support UDS")
    def test_worker_single_trace_uds(self):
        self.tracer.configure(uds_path="/tmp/ddagent/trace.sock")
        # Write a first trace so we get a _worker
        self.tracer.trace("client.testing").finish()
        worker = self.tracer.writer
        worker._log_error_status = mock.Mock(
            worker._log_error_status,
            wraps=worker._log_error_status,
        )
        self.tracer.trace("client.testing").finish()

        # one send is expected
        self._wait_thread_flush()
        # Check that no error was logged
        assert worker._log_error_status.call_count == 0

    def test_worker_single_trace_uds_wrong_socket_path(self):
        self.tracer.configure(uds_path="/tmp/ddagent/nosockethere")
        # Write a first trace so we get a _worker
        self.tracer.trace("client.testing").finish()
        worker = self.tracer.writer
        worker._log_error_status = mock.Mock(
            worker._log_error_status,
            wraps=worker._log_error_status,
        )
        self.tracer.trace("client.testing").finish()

        # one send is expected
        self._wait_thread_flush()
        # Check that no error was logged
        assert worker._log_error_status.call_count == 1

    def test_worker_single_trace(self):
        # create a trace block and send it using the transport system
        tracer = self.tracer
        tracer.trace("client.testing").finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, "/v0.4/traces")
        assert endpoint == "/v0.4/traces"
        assert len(payload) == 1
        assert len(payload[0]) == 1
        assert payload[0][0][b"name"] == b"client.testing"

    # DEV: If we can make the writer flushing deterministic for the case of tests, then we can re-enable this
    @skip(
        "Writer flush intervals are impossible to time correctly to make this test not flaky"
    )
    def test_worker_multiple_traces(self):
        # make a single send() if multiple traces are created before the flush interval
        tracer = self.tracer
        tracer.trace("client.testing").finish()
        tracer.trace("client.testing").finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, "/v0.4/traces")
        assert endpoint == "/v0.4/traces"
        assert len(payload) == 2
        assert len(payload[0]) == 1
        assert len(payload[1]) == 1
        assert payload[0][0][b"name"] == b"client.testing"
        assert payload[1][0][b"name"] == b"client.testing"

    def test_worker_single_trace_multiple_spans(self):
        # make a single send() if a single trace with multiple spans is created before the flush
        tracer = self.tracer
        parent = tracer.trace("client.testing")
        tracer.trace("client.testing").finish()
        parent.finish()

        # one send is expected
        self._wait_thread_flush()
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, "/v0.4/traces")
        assert endpoint == "/v0.4/traces"
        assert len(payload) == 1
        assert len(payload[0]) == 2
        assert payload[0][0][b"name"] == b"client.testing"
        assert payload[0][1][b"name"] == b"client.testing"

    @pytest.mark.skipif(AGENT_VERSION == "testagent",
                        reason="Test agent doesn't support json API.")
    def test_worker_http_error_logging(self):
        # Tests the logging http error logic
        tracer = self.tracer
        self.tracer.writer.api = FlawedAPI(Tracer.DEFAULT_HOSTNAME,
                                           Tracer.DEFAULT_PORT)
        tracer.trace("client.testing").finish()

        log = logging.getLogger("ddtrace.internal.writer")
        log_handler = MockedLogHandler(level="DEBUG")
        log.addHandler(log_handler)

        self._wait_thread_flush()
        assert tracer.writer._last_error_ts < monotonic()

        logged_errors = log_handler.messages["error"]
        assert len(logged_errors) == 1
        assert (
            "Failed to send traces to Datadog Agent at http://localhost:8126: "
            "HTTP error status 400, reason Bad Request, message Content-Type:"
            in logged_errors[0])

    def test_worker_filter_request(self):
        self.tracer.configure(settings={
            FILTERS_KEY: [FilterRequestsOnUrl(r"http://example\.com/health")]
        })
        # spy the send() method
        span = self.tracer.trace("testing.filteredurl")
        span.set_tag(http.URL, "http://example.com/health")
        span.finish()
        span = self.tracer.trace("testing.nonfilteredurl")
        span.set_tag(http.URL, "http://example.com/api/resource")
        span.finish()
        self._wait_thread_flush()

        # Only the second trace should have been sent
        assert self.api._put.call_count == 1
        # check and retrieve the right call
        endpoint, payload = self._get_endpoint_payload(
            self.api._put.call_args_list, "/v0.4/traces")
        assert endpoint == "/v0.4/traces"
        assert len(payload) == 1
        assert payload[0][0][b"name"] == b"testing.nonfilteredurl"
예제 #15
0
import ddtrace.filters
from ddtrace.tracer import Tracer

tracer = Tracer()
tracer.configure(
    settings={"FILTERS": [ddtrace.filters.FilterRequestsOnUrl(r".+/-/health/$"),],}
)