Beispiel #1
0
def test_should_not_collect_count():
    requests_store = TransactionsStore(lambda: [],
                                       collect_frequency=5,
                                       max_queue_size=5)
    requests_store._transactions = 4 * [1]

    assert not requests_store.should_collect()
def test_should_collect_count():
    requests_store = TransactionsStore(lambda: [],
                                       collect_frequency=5,
                                       max_queue_length=5)
    requests_store._transactions = 6 * [1]
    requests_store._last_collect -= 3

    assert requests_store.should_collect()
Beispiel #3
0
def test_tag_transaction():
    requests_store = TransactionsStore(lambda: [], 99999)
    t = requests_store.begin_transaction("test")
    elasticapm.tag(foo='bar')
    requests_store.end_transaction(200, 'test')

    assert t.tags == {'foo': 'bar'}
    transaction_dict = t.to_dict()
    assert transaction_dict['context']['tags'] == {'foo': 'bar'}
Beispiel #4
0
def test_tag_transaction():
    requests_store = TransactionsStore(lambda: [], 99999)
    t = requests_store.begin_transaction("test")
    elasticapm.tag(foo="bar")
    requests_store.end_transaction(200, "test")

    assert t.tags == {"foo": "bar"}
    transaction_dict = t.to_dict()
    assert transaction_dict["context"]["tags"] == {"foo": "bar"}
Beispiel #5
0
    def __init__(self, config=None, **defaults):
        # configure loggers first
        cls = self.__class__
        self.logger = logging.getLogger('%s.%s' % (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger('elasticapm.errors')
        self.state = ClientState()

        self.instrumentation_store = None
        self.processors = []
        self.filter_exception_types_dict = {}
        self._send_timer = None
        self._transports = {}
        self._service_info = None

        self.config = Config(config, default_dict=defaults)
        if self.config.errors:
            for msg in self.config.errors.values():
                self.error_logger.error(msg)
            self.config.disable_send = True
            return

        self._transport_class = import_string(self.config.transport_class)

        for exc_to_filter in (self.config.filter_exception_types or []):
            exc_to_filter_type = exc_to_filter.split(".")[-1]
            exc_to_filter_module = ".".join(exc_to_filter.split(".")[:-1])
            self.filter_exception_types_dict[exc_to_filter_type] = exc_to_filter_module

        self.processors = [import_string(p) for p in self.config.processors] if self.config.processors else []

        self.instrumentation_store = TransactionsStore(
            lambda: self._get_stack_info_for_trace(
                stacks.iter_stack_frames(),
                library_frame_context_lines=self.config.source_lines_span_library_frames,
                in_app_frame_context_lines=self.config.source_lines_span_app_frames,
                with_locals=self.config.collect_local_variables in ('all', 'transactions'),
                locals_processor_func=lambda local_var: varmap(lambda k, v: shorten(
                    v,
                    list_length=self.config.local_var_list_max_length,
                    string_length=self.config.local_var_max_length,
                ), local_var)
            ),
            collect_frequency=self.config.flush_interval,
            sample_rate=self.config.transaction_sample_rate,
            max_spans=self.config.transaction_max_spans,
            max_queue_size=self.config.max_queue_size,
            ignore_patterns=self.config.transactions_ignore_patterns,
        )
        self.include_paths_re = stacks.get_path_regex(self.config.include_paths) if self.config.include_paths else None
        self.exclude_paths_re = stacks.get_path_regex(self.config.exclude_paths) if self.config.exclude_paths else None
        compat.atexit_register(self.close)
Beispiel #6
0
def test_tag_with_non_string_value():
    requests_store = TransactionsStore(lambda: [], 99999)
    t = requests_store.begin_transaction("test")
    elasticapm.tag(foo=1)
    requests_store.end_transaction(200, 'test')
    assert t.tags == {'foo': '1'}
Beispiel #7
0
def test_should_not_collect_time():
    requests_store = TransactionsStore(lambda: [], collect_frequency=5)
    requests_store._last_collect -= 3

    assert not requests_store.should_collect()
Beispiel #8
0
def test_get_transaction_clear():
    requests_store = TransactionsStore(lambda: [], 99999)
    t = requests_store.begin_transaction("test")
    assert t == get_transaction(clear=True)
    assert get_transaction() is None
Beispiel #9
0
def test_get_transaction():
    requests_store = TransactionsStore(lambda: [], 99999)
    t = requests_store.begin_transaction("test")
    assert t == get_transaction()
Beispiel #10
0
def transaction_store():
    mock_get_frames = Mock()

    frames = [{
        'function': 'something_expensive',
        'abs_path':
        '/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/views.py',
        'lineno': 52,
        'module': 'tests.contrib.django.testapp.views',
        'filename': 'tests/contrib/django/testapp/views.py'
    }, {
        'function': '_resolve_lookup',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
        'lineno': 789,
        'module': 'django.template.base',
        'filename': 'django/template/base.py'
    }, {
        'function': 'resolve',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
        'lineno': 735,
        'module': 'django.template.base',
        'filename': 'django/template/base.py'
    }, {
        'function': 'resolve',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
        'lineno': 585,
        'module': 'django.template.base',
        'filename': 'django/template/base.py'
    }, {
        'lineno':
        4,
        'filename':
        u'/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/templates/list_fish.html'
    }, {
        'function': 'render',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/defaulttags.py',
        'lineno': 4,
        'module': 'django.template.defaulttags',
        'filename': 'django/template/defaulttags.py'
    }, {
        'function': 'render_node',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/debug.py',
        'lineno': 78,
        'module': 'django.template.debug',
        'filename': 'django/template/debug.py'
    }, {
        'function': 'render',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
        'lineno': 840,
        'module': 'django.template.base',
        'filename': 'django/template/base.py'
    }, {
        'function': 'instrumented_test_render',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/utils.py',
        'lineno': 85,
        'module': 'django.test.utils',
        'filename': 'django/test/utils.py'
    }, {
        'function': 'render',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
        'lineno': 140,
        'module': 'django.template.base',
        'filename': 'django/template/base.py'
    }, {
        'function': 'rendered_content',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py',
        'lineno': 82,
        'module': 'django.template.response',
        'filename': 'django/template/response.py'
    }, {
        'function': 'render',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py',
        'lineno': 105,
        'module': 'django.template.response',
        'filename': 'django/template/response.py'
    }, {
        'function': 'get_response',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/core/handlers/base.py',
        'lineno': 137,
        'module': 'django.core.handlers.base',
        'filename': 'django/core/handlers/base.py'
    }, {
        'function': '__call__',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
        'lineno': 109,
        'module': 'django.test.client',
        'filename': 'django/test/client.py'
    }, {
        'function': 'request',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
        'lineno': 426,
        'module': 'django.test.client',
        'filename': 'django/test/client.py'
    }, {
        'function': 'get',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
        'lineno': 280,
        'module': 'django.test.client',
        'filename': 'django/test/client.py'
    }, {
        'function': 'get',
        'abs_path':
        '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
        'lineno': 473,
        'module': 'django.test.client',
        'filename': 'django/test/client.py'
    }, {
        'function': 'test_template_name_as_view',
        'abs_path':
        '/var/parent-elasticapm/elasticapm/tests/contrib/django/django_tests.py',
        'lineno': 710,
        'module': 'tests.contrib.django.django_tests',
        'filename': 'tests/contrib/django/django_tests.py'
    }]

    mock_get_frames.return_value = frames
    return TransactionsStore(mock_get_frames, 99999)
Beispiel #11
0
class Client(object):
    """
    The base ElasticAPM client, which handles communication over the
    HTTP API to the APM Server.

    Will read default configuration from the environment variable
    ``ELASTIC_APM_APP_NAME`` and ``ELASTIC_APM_SECRET_TOKEN``
    if available. ::

    >>> from elasticapm import Client

    >>> # Read configuration from environment
    >>> client = Client()

    >>> # Configure the client manually
    >>> client = Client(
    >>>     include_paths=['my.package'],
    >>>     service_name='myapp',
    >>>     secret_token='secret_token',
    >>> )

    >>> # Record an exception
    >>> try:
    >>>     1/0
    >>> except ZeroDivisionError:
    >>>     ident = client.capture_exception()
    >>>     print ("Exception caught; reference is %%s" %% ident)
    """
    logger = logging.getLogger('elasticapm')

    def __init__(self, config=None, **defaults):
        # configure loggers first
        cls = self.__class__
        self.logger = logging.getLogger('%s.%s' %
                                        (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger('elasticapm.errors')
        self.state = ClientState()

        self.instrumentation_store = None
        self.processors = []
        self.filter_exception_types_dict = {}
        self._send_timer = None
        self._transports = {}
        self._service_info = None

        self.config = Config(config, default_dict=defaults)
        if self.config.errors:
            for msg in self.config.errors.values():
                self.error_logger.error(msg)
            self.config.disable_send = True

        self._transport_class = import_string(self.config.transport_class)

        for exc_to_filter in (self.config.filter_exception_types or []):
            exc_to_filter_type = exc_to_filter.split(".")[-1]
            exc_to_filter_module = ".".join(exc_to_filter.split(".")[:-1])
            self.filter_exception_types_dict[
                exc_to_filter_type] = exc_to_filter_module

        self.processors = [import_string(p) for p in self.config.processors
                           ] if self.config.processors else []

        if platform.python_implementation() == 'PyPy':
            # PyPy introduces a `_functools.partial.__call__` frame due to our use
            # of `partial` in AbstractInstrumentedModule
            skip_modules = ('elasticapm.', '_functools')
        else:
            skip_modules = ('elasticapm.', )

        def frames_collector_func():
            return self._get_stack_info_for_trace(
                stacks.iter_stack_frames(skip_top_modules=skip_modules),
                library_frame_context_lines=self.config.
                source_lines_span_library_frames,
                in_app_frame_context_lines=self.config.
                source_lines_span_app_frames,
                with_locals=self.config.collect_local_variables
                in ('all', 'transactions'),
                locals_processor_func=lambda local_var: varmap(
                    lambda k, v: shorten(
                        v,
                        list_length=self.config.local_var_list_max_length,
                        string_length=self.config.local_var_max_length,
                    ), local_var))

        self.instrumentation_store = TransactionsStore(
            frames_collector_func=frames_collector_func,
            collect_frequency=self.config.flush_interval,
            sample_rate=self.config.transaction_sample_rate,
            max_spans=self.config.transaction_max_spans,
            span_frames_min_duration=self.config.span_frames_min_duration_ms,
            max_queue_size=self.config.max_queue_size,
            ignore_patterns=self.config.transactions_ignore_patterns,
        )
        self.include_paths_re = stacks.get_path_regex(
            self.config.include_paths) if self.config.include_paths else None
        self.exclude_paths_re = stacks.get_path_regex(
            self.config.exclude_paths) if self.config.exclude_paths else None
        compat.atexit_register(self.close)

    def get_handler(self, name):
        return import_string(name)

    def capture(self,
                event_type,
                date=None,
                context=None,
                custom=None,
                stack=None,
                handled=True,
                **kwargs):
        """
        Captures and processes an event and pipes it off to Client.send.
        """
        if event_type == 'Exception':
            # never gather log stack for exceptions
            stack = False
        data = self._build_msg_for_logging(event_type,
                                           date=date,
                                           context=context,
                                           custom=custom,
                                           stack=stack,
                                           handled=handled,
                                           **kwargs)

        if data:
            url = self.config.server_url + constants.ERROR_API_PATH
            self.send(url, **data)
            return data['errors'][0]['id']

    def capture_message(self, message=None, param_message=None, **kwargs):
        """
        Creates an event from ``message``.

        >>> client.capture_message('My event just happened!')
        """
        return self.capture('Message',
                            message=message,
                            param_message=param_message,
                            **kwargs)

    def capture_exception(self, exc_info=None, handled=True, **kwargs):
        """
        Creates an event from an exception.

        >>> try:
        >>>     exc_info = sys.exc_info()
        >>>     client.capture_exception(exc_info)
        >>> finally:
        >>>     del exc_info

        If exc_info is not provided, or is set to True, then this method will
        perform the ``exc_info = sys.exc_info()`` and the requisite clean-up
        for you.
        """
        return self.capture('Exception',
                            exc_info=exc_info,
                            handled=handled,
                            **kwargs)

    def send(self, url, **data):
        """
        Encodes and sends data to remote URL using configured transport
        :param url: URL of endpoint
        :param data: dictionary of data to send
        """
        if self.config.disable_send or self._filter_exception_type(data):
            return

        payload = self.encode(data)

        headers = {
            'Content-Type': 'application/json',
            'Content-Encoding': 'deflate',
            'User-Agent': 'elasticapm-python/%s' % elasticapm.VERSION,
        }

        if self.config.secret_token:
            headers['Authorization'] = "Bearer %s" % self.config.secret_token

        if not self.state.should_try():
            message = self._get_log_message(payload)
            self.error_logger.error(message)
            return
        try:
            self._send_remote(url=url, data=payload, headers=headers)
        except Exception as e:
            self.handle_transport_fail(exception=e)

    def encode(self, data):
        """
        Serializes ``data`` into a raw string.
        """
        return zlib.compress(json.dumps(data).encode('utf8'))

    def decode(self, data):
        """
        Unserializes a string, ``data``.
        """
        return json.loads(zlib.decompress(data).decode('utf8'))

    def begin_transaction(self, transaction_type):
        """Register the start of a transaction on the client
        """
        return self.instrumentation_store.begin_transaction(transaction_type)

    def end_transaction(self, name=None, result=''):
        transaction = self.instrumentation_store.end_transaction(result, name)
        if self.instrumentation_store.should_collect():
            self._collect_transactions()
        if not self._send_timer:
            # send first batch of data after config._wait_to_first_send
            self._start_send_timer(timeout=min(self.config._wait_to_first_send,
                                               self.config.flush_interval))
        return transaction

    def close(self):
        self._collect_transactions()
        if self._send_timer:
            self._stop_send_timer()
        for url, transport in list(self._transports.items()):
            transport.close()
            self._transports.pop(url)

    def handle_transport_success(self, **kwargs):
        """
        Success handler called by the transport
        """
        if kwargs.get('url'):
            self.logger.info('Logged error at ' + kwargs['url'])
        self.state.set_success()

    def handle_transport_fail(self, exception=None, **kwargs):
        """
        Failure handler called by the transport
        """
        if isinstance(exception, TransportException):
            message = self._get_log_message(exception.data)
            self.error_logger.error(exception.args[0])
        else:
            # stdlib exception
            message = str(exception)
        self.error_logger.error('Failed to submit message: %r',
                                message,
                                exc_info=getattr(exception, 'print_trace',
                                                 True))
        self.state.set_fail()

    def _collect_transactions(self):
        self._stop_send_timer()
        transactions = []
        if self.instrumentation_store:
            for transaction in self.instrumentation_store.get_all():
                for processor in self.processors:
                    transaction = processor(self, transaction)
                transactions.append(transaction)
        if not transactions:
            return

        data = self._build_msg({
            'transactions': transactions,
        })

        api_path = constants.TRANSACTIONS_API_PATH

        self.send(self.config.server_url + api_path, **data)
        self._start_send_timer()

    def _start_send_timer(self, timeout=None):
        timeout = timeout or self.config.flush_interval
        self._send_timer = threading.Timer(timeout, self._collect_transactions)
        self._send_timer.start()

    def _stop_send_timer(self):
        if self._send_timer and self._send_timer.is_alive(
        ) and not self._send_timer == threading.current_thread():
            self._send_timer.cancel()
            self._send_timer.join()

    def _send_remote(self, url, data, headers=None):
        if headers is None:
            headers = {}
        parsed = compat.urlparse.urlparse(url)
        transport = self._get_transport(parsed)
        if transport.async_mode:
            transport.send_async(
                data,
                headers,
                success_callback=self.handle_transport_success,
                fail_callback=self.handle_transport_fail)
        else:
            url = transport.send(data,
                                 headers,
                                 timeout=self.config.server_timeout)
            self.handle_transport_success(url=url)

    def get_service_info(self):
        if self._service_info:
            return self._service_info
        language_version = platform.python_version()
        if hasattr(sys, 'pypy_version_info'):
            runtime_version = '.'.join(map(str, sys.pypy_version_info[:3]))
        else:
            runtime_version = language_version
        result = {
            'name': keyword_field(self.config.service_name),
            'environment': keyword_field(self.config.environment),
            'version': keyword_field(self.config.service_version),
            'agent': {
                'name': 'python',
                'version': elasticapm.VERSION,
            },
            'language': {
                'name': 'python',
                'version': keyword_field(platform.python_version()),
            },
            'runtime': {
                'name': keyword_field(platform.python_implementation()),
                'version': keyword_field(runtime_version),
            }
        }
        if self.config.framework_name:
            result['framework'] = {
                'name': keyword_field(self.config.framework_name),
                'version': keyword_field(self.config.framework_version),
            }
        self._service_info = result
        return result

    def get_process_info(self):
        return {
            'pid': os.getpid(),
            'ppid': os.getppid() if hasattr(os, 'getppid') else None,
            'argv': sys.argv,
            'title':
            None,  # Note: if we implement this, the value needs to be wrapped with keyword_field
        }

    def get_system_info(self):
        return {
            'hostname': keyword_field(socket.gethostname()),
            'architecture': platform.machine(),
            'platform': platform.system().lower(),
        }

    def _build_msg(self, data=None, **kwargs):
        data = data or {}
        data['service'] = self.get_service_info()
        data['process'] = self.get_process_info()
        data['system'] = self.get_system_info()
        data.update(**kwargs)
        return data

    def _build_msg_for_logging(self,
                               event_type,
                               date=None,
                               context=None,
                               custom=None,
                               stack=None,
                               handled=True,
                               **kwargs):
        """
        Captures, processes and serializes an event into a dict object
        """
        transaction = get_transaction()
        if transaction:
            transaction_context = deepcopy(transaction.context)
        else:
            transaction_context = {}
        event_data = {}
        if custom is None:
            custom = {}
        if not date:
            date = datetime.datetime.utcnow()
        if stack is None:
            stack = self.config.auto_log_stacks
        if context:
            transaction_context.update(context)
            context = transaction_context
        else:
            context = transaction_context
        event_data['context'] = context
        if transaction and transaction.tags:
            context['tags'] = deepcopy(transaction.tags)

        # if '.' not in event_type:
        # Assume it's a builtin
        event_type = 'elasticapm.events.%s' % event_type

        handler = self.get_handler(event_type)
        result = handler.capture(self, **kwargs)
        if self._filter_exception_type(result):
            return
        # data (explicit) culprit takes over auto event detection
        culprit = result.pop('culprit', None)
        if custom.get('culprit'):
            culprit = custom.pop('culprit')

        for k, v in compat.iteritems(result):
            if k not in event_data:
                event_data[k] = v

        log = event_data.get('log', {})
        if stack and 'stacktrace' not in log:
            if stack is True:
                frames = stacks.iter_stack_frames(skip=3)
            else:
                frames = stack
            frames = stacks.get_stack_info(
                frames,
                with_locals=self.config.collect_local_variables
                in ('errors', 'all'),
                library_frame_context_lines=self.config.
                source_lines_error_library_frames,
                in_app_frame_context_lines=self.config.
                source_lines_error_app_frames,
                include_paths_re=self.include_paths_re,
                exclude_paths_re=self.exclude_paths_re,
                locals_processor_func=lambda local_var: varmap(
                    lambda k, v: shorten(
                        v,
                        list_length=self.config.local_var_list_max_length,
                        string_length=self.config.local_var_max_length,
                    ), local_var))
            log['stacktrace'] = frames

        if 'stacktrace' in log and not culprit:
            culprit = stacks.get_culprit(log['stacktrace'],
                                         self.config.include_paths,
                                         self.config.exclude_paths)

        if 'level' in log and isinstance(log['level'], compat.integer_types):
            log['level'] = logging.getLevelName(log['level']).lower()

        if log:
            event_data['log'] = log

        if culprit:
            event_data['culprit'] = culprit

        if 'custom' in context:
            context['custom'].update(custom)
        else:
            context['custom'] = custom

        # Run the data through processors
        for processor in self.processors:
            event_data = processor(self, event_data)

        # Make sure all data is coerced
        event_data = transform(event_data)
        if 'exception' in event_data:
            event_data['exception']['handled'] = bool(handled)

        event_data.update({
            'timestamp':
            date.strftime(constants.TIMESTAMP_FORMAT),
        })

        transaction = get_transaction()
        if transaction:
            event_data['transaction'] = {'id': transaction.id}

        return self._build_msg({'errors': [event_data]})

    def _filter_exception_type(self, data):
        exception = data.get('exception')
        if not exception:
            return False

        exc_type = exception.get('type')
        exc_module = exception.get('module')
        if exc_module == 'None':
            exc_module = None

        if exc_type in self.filter_exception_types_dict:
            exc_to_filter_module = self.filter_exception_types_dict[exc_type]
            if not exc_to_filter_module or exc_to_filter_module == exc_module:
                if exc_module:
                    exc_name = '%s.%s' % (exc_module, exc_type)
                else:
                    exc_name = exc_type
                self.logger.info(
                    'Ignored %s exception due to exception type filter',
                    exc_name)
                return True
        return False

    def _get_log_message(self, data):
        # decode message so we can show the actual event
        try:
            data = self.decode(data)
        except Exception:
            message = '<failed decoding data>'
        else:
            message = data.pop('message', '<no message value>')
        return message

    def _get_transport(self, parsed_url):
        if hasattr(self._transport_class,
                   'sync_transport') and is_master_process():
            # when in the master process, always use SYNC mode. This avoids
            # the danger of being forked into an inconsistent threading state
            self.logger.info(
                'Sending message synchronously while in master '
                'process. PID: %s', os.getpid())
            return self._transport_class.sync_transport(parsed_url)
        if parsed_url not in self._transports:
            self._transports[parsed_url] = self._transport_class(
                parsed_url, verify_server_cert=self.config.verify_server_cert)
        return self._transports[parsed_url]

    def _get_stack_info_for_trace(self,
                                  frames,
                                  library_frame_context_lines=None,
                                  in_app_frame_context_lines=None,
                                  with_locals=True,
                                  locals_processor_func=None):
        """Overrideable in derived clients to add frames/info, e.g. templates"""
        return stacks.get_stack_info(
            frames,
            library_frame_context_lines=library_frame_context_lines,
            in_app_frame_context_lines=in_app_frame_context_lines,
            with_locals=with_locals,
            include_paths_re=self.include_paths_re,
            exclude_paths_re=self.exclude_paths_re,
            locals_processor_func=locals_processor_func,
        )
Beispiel #12
0
def transaction_store():
    mock_get_frames = Mock()

    frames = [
        {
            "function": "something_expensive",
            "abs_path": "/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/views.py",
            "lineno": 52,
            "module": "tests.contrib.django.testapp.views",
            "filename": "tests/contrib/django/testapp/views.py",
        },
        {
            "function": "_resolve_lookup",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py",
            "lineno": 789,
            "module": "django.template.base",
            "filename": "django/template/base.py",
        },
        {
            "function": "resolve",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py",
            "lineno": 735,
            "module": "django.template.base",
            "filename": "django/template/base.py",
        },
        {
            "function": "resolve",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py",
            "lineno": 585,
            "module": "django.template.base",
            "filename": "django/template/base.py",
        },
        {
            "lineno": 4,
            "filename": u"/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/templates/list_fish.html",
        },
        {
            "function": "render",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/defaulttags.py",
            "lineno": 4,
            "module": "django.template.defaulttags",
            "filename": "django/template/defaulttags.py",
        },
        {
            "function": "render_node",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/debug.py",
            "lineno": 78,
            "module": "django.template.debug",
            "filename": "django/template/debug.py",
        },
        {
            "function": "render",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py",
            "lineno": 840,
            "module": "django.template.base",
            "filename": "django/template/base.py",
        },
        {
            "function": "instrumented_test_render",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/utils.py",
            "lineno": 85,
            "module": "django.test.utils",
            "filename": "django/test/utils.py",
        },
        {
            "function": "render",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py",
            "lineno": 140,
            "module": "django.template.base",
            "filename": "django/template/base.py",
        },
        {
            "function": "rendered_content",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py",
            "lineno": 82,
            "module": "django.template.response",
            "filename": "django/template/response.py",
        },
        {
            "function": "render",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py",
            "lineno": 105,
            "module": "django.template.response",
            "filename": "django/template/response.py",
        },
        {
            "function": "get_response",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/core/handlers/base.py",
            "lineno": 137,
            "module": "django.core.handlers.base",
            "filename": "django/core/handlers/base.py",
        },
        {
            "function": "__call__",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py",
            "lineno": 109,
            "module": "django.test.client",
            "filename": "django/test/client.py",
        },
        {
            "function": "request",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py",
            "lineno": 426,
            "module": "django.test.client",
            "filename": "django/test/client.py",
        },
        {
            "function": "get",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py",
            "lineno": 280,
            "module": "django.test.client",
            "filename": "django/test/client.py",
        },
        {
            "function": "get",
            "abs_path": "/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py",
            "lineno": 473,
            "module": "django.test.client",
            "filename": "django/test/client.py",
        },
        {
            "function": "test_template_name_as_view",
            "abs_path": "/var/parent-elasticapm/elasticapm/tests/contrib/django/django_tests.py",
            "lineno": 710,
            "module": "tests.contrib.django.django_tests",
            "filename": "tests/contrib/django/django_tests.py",
        },
    ]

    mock_get_frames.return_value = frames
    return TransactionsStore(mock_get_frames, 99999)
Beispiel #13
0
class Client(object):
    """
    The base ElasticAPM client, which handles communication over the
    HTTP API to the APM Server.

    Will read default configuration from the environment variable
    ``ELASTIC_APM_APP_NAME`` and ``ELASTIC_APM_SECRET_TOKEN``
    if available. ::

    >>> from elasticapm import Client

    >>> # Read configuration from environment
    >>> client = Client()

    >>> # Configure the client manually
    >>> client = Client(
    >>>     include_paths=['my.package'],
    >>>     app_name='app_name',
    >>>     secret_token='secret_token',
    >>> )

    >>> # Record an exception
    >>> try:
    >>>     1/0
    >>> except ZeroDivisionError:
    >>>     ident = client.get_ident(client.capture_exception())
    >>>     print ("Exception caught; reference is %%s" %% ident)
    """
    logger = logging.getLogger('elasticapm')
    protocol_version = '1.0'

    environment_config_map = {
        'app_name': 'ELASTIC_APM_APP_NAME',
        'secret_token': 'ELASTIC_APM_SECRET_TOKEN',
        'git_ref': 'ELASTIC_APM_GIT_REF',
        'app_version': 'ELASTIC_APM_APP_VERSION',
    }

    def __init__(self,
                 app_name=None,
                 secret_token=None,
                 transport_class=None,
                 include_paths=None,
                 exclude_paths=None,
                 timeout=None,
                 hostname=None,
                 auto_log_stacks=None,
                 string_max_length=None,
                 list_max_length=None,
                 processors=None,
                 filter_exception_types=None,
                 servers=None,
                 async_mode=None,
                 traces_send_freq_secs=None,
                 transactions_ignore_patterns=None,
                 git_ref=None,
                 app_version=None,
                 **kwargs):
        self.app_name = self.secret_token = self.git_ref = self.app_version = None
        # configure loggers first
        cls = self.__class__
        self.logger = logging.getLogger('%s.%s' %
                                        (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger('elasticapm.errors')
        self.state = ClientState()
        self._configure(app_name=app_name,
                        secret_token=secret_token,
                        git_ref=git_ref,
                        app_version=app_version)
        self.servers = servers or defaults.SERVERS
        self.async_mode = (async_mode is True or
                           (defaults.ASYNC_MODE and async_mode is not False))
        if not transport_class:
            transport_class = (defaults.ASYNC_TRANSPORT_CLASS
                               if self.async_mode else
                               defaults.SYNC_TRANSPORT_CLASS)
        self._transport_class = import_string(transport_class)
        self._transports = {}

        # servers may be set to a NoneType (for Django)
        if self.servers and not (self.app_name and self.secret_token):
            msg = 'Missing configuration for ElasticAPM client. Please see documentation.'
            self.logger.info(msg)

        self.is_send_disabled = (os.environ.get('ELASTIC_APM_DISABLE_SEND',
                                                '').lower() in ('1', 'true'))
        if self.is_send_disabled:
            self.logger.info(
                'Not sending any data to APM Server due to ELASTIC_APM_DISABLE_SEND '
                'environment variable')

        self.include_paths = set(include_paths or defaults.INCLUDE_PATHS)
        self.exclude_paths = set(exclude_paths or defaults.EXCLUDE_PATHS)
        self.timeout = int(timeout or defaults.TIMEOUT)
        self.hostname = six.text_type(hostname or defaults.HOSTNAME)
        self.auto_log_stacks = bool(auto_log_stacks
                                    or defaults.AUTO_LOG_STACKS)

        self.string_max_length = int(string_max_length
                                     or defaults.MAX_LENGTH_STRING)
        self.list_max_length = int(list_max_length or defaults.MAX_LENGTH_LIST)
        self.traces_send_freq_secs = (traces_send_freq_secs
                                      or defaults.TRACES_SEND_FREQ_SECS)

        self.filter_exception_types_dict = {}
        for exc_to_filter in (filter_exception_types or []):
            exc_to_filter_type = exc_to_filter.split(".")[-1]
            exc_to_filter_module = ".".join(exc_to_filter.split(".")[:-1])
            self.filter_exception_types_dict[
                exc_to_filter_type] = exc_to_filter_module

        if processors is None:
            self.processors = defaults.PROCESSORS
        else:
            self.processors = processors
        self.processors = [import_string(p) for p in self.processors]

        self.instrumentation_store = TransactionsStore(
            lambda: self.get_stack_info_for_trace(iter_stack_frames(), False),
            self.traces_send_freq_secs, transactions_ignore_patterns)
        atexit_register(self.close)

    def _configure(self, **kwargs):
        """
        Configures this instance based on kwargs, or environment variables.
        The former take precedence.
        """
        for attr_name, value in kwargs.items():
            if value is None and (attr_name in self.environment_config_map
                                  and self.environment_config_map[attr_name]
                                  in os.environ):
                self.logger.info(
                    "Configuring elasticapm.%s from environment variable '%s'",
                    attr_name, self.environment_config_map[attr_name])
                value = os.environ[self.environment_config_map[attr_name]]
            setattr(self, attr_name, six.text_type(value))

    def get_ident(self, result):
        """
        Returns a searchable string representing a message.

        >>> result = client.process(**kwargs)
        >>> ident = client.get_ident(result)
        """
        return result

    def get_handler(self, name):
        return import_string(name)

    def get_stack_info_for_trace(self, frames, extended=True):
        """Overrideable in derived clients to add frames/info, e.g. templates

        4.0: Use for error frames too.
        """
        return stacks.get_stack_info(frames, extended)

    def build_msg_for_logging(self,
                              event_type,
                              data=None,
                              date=None,
                              extra=None,
                              stack=None,
                              **kwargs):
        """
        Captures, processes and serializes an event into a dict object
        """

        if data is None:
            data = {}
        if extra is None:
            extra = {}
        if not date:
            date = datetime.datetime.utcnow()
        if stack is None:
            stack = self.auto_log_stacks
        if 'context' not in data:
            data['context'] = context = {}
        else:
            context = data['context']

        # if '.' not in event_type:
        # Assume it's a builtin
        event_type = 'elasticapm.events.%s' % event_type

        handler = self.get_handler(event_type)
        result = handler.capture(self, data=data, **kwargs)
        if self._filter_exception_type(result):
            return
        # data (explicit) culprit takes over auto event detection
        culprit = result.pop('culprit', None)
        if data.get('culprit'):
            culprit = data['culprit']

        for k, v in six.iteritems(result):
            if k not in data:
                data[k] = v

        log = data.get('log', {})
        if stack and 'stacktrace' not in log:
            if stack is True:
                frames = iter_stack_frames()
            else:
                frames = stack
            frames = varmap(
                lambda k, v: shorten(v,
                                     string_length=self.string_max_length,
                                     list_length=self.list_max_length),
                stacks.get_stack_info(frames))
            log['stacktrace'] = frames

        if 'stacktrace' in log and not culprit:
            culprit = get_culprit(log['stacktrace'], self.include_paths,
                                  self.exclude_paths)

        if 'level' in log and isinstance(log['level'], six.integer_types):
            log['level'] = logging.getLevelName(log['level']).lower()

        if log:
            data['log'] = log

        if culprit:
            data['culprit'] = culprit

        context['custom'] = extra

        # Run the data through processors
        for processor in self.processors:
            data = processor(self, data)

        # Make sure all data is coerced
        data = transform(data)

        data.update({
            'timestamp': date.strftime(defaults.TIMESTAMP_FORMAT),
        })

        return self.build_msg({'errors': [data]})

    def build_msg(self, data=None, **kwargs):
        data = data or {}
        data['app'] = self.get_app_info()
        data['system'] = self.get_system_info()
        data.update(**kwargs)
        return data

    def capture(self,
                event_type,
                data=None,
                date=None,
                extra=None,
                stack=None,
                **kwargs):
        """
        Captures and processes an event and pipes it off to Client.send.

        To use structured data (interfaces) with capture:

        >>> client.capture('Message', message='foo', data={
        >>>     'http': {
        >>>         'url': '...',
        >>>         'data': {},
        >>>         'query_string': '...',
        >>>         'method': 'POST',
        >>>     },
        >>>     'logger': 'logger.name',
        >>>     'site': 'site.name',
        >>> }, extra={
        >>>     'key': 'value',
        >>> })

        The finalized ``data`` structure contains the following (some optional)
        builtin values:

        >>> {
        >>>     # the culprit and version information
        >>>     'culprit': 'full.module.name', # or /arbitrary/path
        >>>
        >>>     # arbitrary data provided by user
        >>>     'extra': {
        >>>         'key': 'value',
        >>>     }
        >>> }

        :param event_type: the module path to the Event class. Builtins can use
                           shorthand class notation and exclude the full module
                           path.
        :param data: the data base
        :param date: the datetime of this event
        :param extra: a dictionary of additional standard metadata
        :return: a 32-length string identifying this event
        """

        data = self.build_msg_for_logging(event_type, data, date, extra, stack,
                                          **kwargs)

        if data:
            servers = [
                server + defaults.ERROR_API_PATH for server in self.servers
            ]
            self.send(servers=servers, **data)
            return data['errors'][0]['id']

    def _send_remote(self, url, data, headers=None):
        if headers is None:
            headers = {}
        parsed = urlparse.urlparse(url)
        transport = self._get_transport(parsed)
        if transport.async_mode:
            transport.send_async(
                data,
                headers,
                success_callback=self.handle_transport_success,
                fail_callback=self.handle_transport_fail)
        else:
            url = transport.send(data, headers, timeout=self.timeout)
            self.handle_transport_success(url=url)

    def _get_log_message(self, data):
        # decode message so we can show the actual event
        try:
            data = self.decode(data)
        except Exception:
            message = '<failed decoding data>'
        else:
            message = data.pop('message', '<no message value>')
        return message

    def _get_transport(self, parsed_url):
        if self.async_mode and is_master_process():
            # when in the master process, always use SYNC mode. This avoids
            # the danger of being forked into an inconsistent threading state
            self.logger.info(
                'Sending message synchronously while in master '
                'process. PID: %s', os.getpid())
            return import_string(defaults.SYNC_TRANSPORT_CLASS)(parsed_url)
        if parsed_url not in self._transports:
            self._transports[parsed_url] = self._transport_class(parsed_url)
        return self._transports[parsed_url]

    def _filter_exception_type(self, data):
        exception = data.get('exception')
        if not exception:
            return False

        exc_type = exception.get('type')
        exc_module = exception.get('module')
        if exc_module == 'None':
            exc_module = None

        if exc_type in self.filter_exception_types_dict:
            exc_to_filter_module = self.filter_exception_types_dict[exc_type]
            if not exc_to_filter_module or exc_to_filter_module == exc_module:
                if exc_module:
                    exc_name = '%s.%s' % (exc_module, exc_type)
                else:
                    exc_name = exc_type
                self.logger.info(
                    'Ignored %s exception due to exception type filter',
                    exc_name)
                return True
        return False

    def send_remote(self, url, data, headers=None):
        if not self.state.should_try():
            message = self._get_log_message(data)
            self.error_logger.error(message)
            return
        try:
            self._send_remote(url=url, data=data, headers=headers)
        except Exception as e:
            self.handle_transport_fail(exception=e)

    def send(self, secret_token=None, auth_header=None, servers=None, **data):
        """
        Serializes the message and passes the payload onto ``send_encoded``.
        """

        if self.is_send_disabled or self._filter_exception_type(data):
            return

        message = self.encode(data)

        return self.send_encoded(message,
                                 secret_token=secret_token,
                                 auth_header=auth_header,
                                 servers=servers)

    def send_encoded(self,
                     message,
                     secret_token,
                     auth_header=None,
                     servers=None,
                     **kwargs):
        """
        Given an already serialized message, signs the message and passes the
        payload off to ``send_remote`` for each server specified in the servers
        configuration.
        """
        servers = servers or self.servers
        if not servers:
            warnings.warn('elasticapm client has no remote servers configured')
            return

        if not auth_header:
            if not secret_token:
                secret_token = self.secret_token

            auth_header = "Bearer %s" % (secret_token)

        for url in servers:
            headers = {
                'Authorization': auth_header,
                'Content-Type': 'application/json',
                'Content-Encoding': 'deflate',
                'User-Agent': 'elasticapm-python/%s' % elasticapm.VERSION,
            }

            self.send_remote(url=url, data=message, headers=headers)

    def encode(self, data):
        """
        Serializes ``data`` into a raw string.
        """
        return zlib.compress(json.dumps(data).encode('utf8'))

    def decode(self, data):
        """
        Unserializes a string, ``data``.
        """
        return json.loads(zlib.decompress(data).decode('utf8'))

    def capture_message(self, message, **kwargs):
        """
        Creates an event from ``message``.

        >>> client.capture_message('My event just happened!')
        """
        return self.capture('Message', message=message, **kwargs)

    @deprecated(alternative="capture_message()")
    def captureMessage(self, message, **kwargs):
        """
        Deprecated
        :param message:
        :type message:
        :param kwargs:
        :type kwargs:
        :return:
        :rtype:
        """
        self.capture_message(message, **kwargs)

    def capture_exception(self, exc_info=None, **kwargs):
        """
        Creates an event from an exception.

        >>> try:
        >>>     exc_info = sys.exc_info()
        >>>     client.capture_exception(exc_info)
        >>> finally:
        >>>     del exc_info

        If exc_info is not provided, or is set to True, then this method will
        perform the ``exc_info = sys.exc_info()`` and the requisite clean-up
        for you.
        """
        return self.capture('Exception', exc_info=exc_info, **kwargs)

    @deprecated(alternative="capture_exception()")
    def captureException(self, exc_info=None, **kwargs):
        """
        Deprecated
        """
        self.capture_exception(exc_info, **kwargs)

    def capture_query(self, query, params=(), engine=None, **kwargs):
        """
        Creates an event for a SQL query.

        >>> client.capture_query('SELECT * FROM foo')
        """
        return self.capture('Query',
                            query=query,
                            params=params,
                            engine=engine,
                            **kwargs)

    @deprecated(alternative="capture_query()")
    def captureQuery(self, *args, **kwargs):
        """
        Deprecated
        """
        self.capture_query(*args, **kwargs)

    def begin_transaction(self, transaction_type):
        """Register the start of a transaction on the client

        'kind' should follow the convention of '<transaction-kind>.<provider>'
        e.g. 'web.django', 'task.celery'.
        """
        self.instrumentation_store.begin_transaction(transaction_type)

    def end_transaction(self, name, status_code=None):
        self.instrumentation_store.end_transaction(status_code, name)
        if self.instrumentation_store.should_collect():
            self._traces_collect()

    def set_transaction_name(self, name):
        transaction = get_transaction()
        if not transaction:
            return
        transaction.name = name

    def set_transaction_extra_data(self, data, _key=None):
        transaction = get_transaction()
        if not transaction:
            return
        if not _key:
            _key = 'extra'
        transaction.extra[_key] = data

    def close(self):
        self._traces_collect()
        for url, transport in self._transports.items():
            transport.close()

    def handle_transport_success(self, **kwargs):
        """
        Success handler called by the transport
        """
        if kwargs.get('url'):
            self.logger.info('Logged error at ' + kwargs['url'])
        self.state.set_success()

    def handle_transport_fail(self, exception=None, **kwargs):
        """
        Failure handler called by the transport
        """
        if isinstance(exception, TransportException):
            message = self._get_log_message(exception.data)
            self.error_logger.error(exception.args[0])
        else:
            # stdlib exception
            message = str(exception)
        self.error_logger.error('Failed to submit message: %r',
                                message,
                                exc_info=getattr(exception, 'print_trace',
                                                 True))
        self.state.set_fail()

    def _traces_collect(self):
        transactions = self.instrumentation_store.get_all()
        if not transactions:
            return

        data = self.build_msg({
            'transactions': transactions,
        })
        api_path = defaults.TRANSACTIONS_API_PATH

        self.send(servers=[server + api_path for server in self.servers],
                  **data)

    def get_app_info(self):
        language_version = platform.python_version()
        if hasattr(sys, 'pypy_version_info'):
            runtime_version = '.'.join(map(str, sys.pypy_version_info[:3]))
        else:
            runtime_version = language_version
        return {
            'name': self.app_name,
            'version': self.app_version,
            'agent': {
                'name': 'elasticapm-python',
                'version': elasticapm.VERSION,
            },
            'argv': sys.argv,
            'framework': {
                'name': getattr(self, '_framework', None),
                'version': getattr(self, '_framework_version', None),
            },
            'git_ref': self.git_ref,
            'language': {
                'name': 'python',
                'version': platform.python_version(),
            },
            'pid': os.getpid(),
            'process_title': None,
            'runtime': {
                'name': platform.python_implementation(),
                'version': runtime_version,
            }
        }

    def get_system_info(self):
        return {
            'hostname': socket.gethostname(),
            'architecture': platform.machine(),
            'platform': platform.system().lower(),
        }
Beispiel #14
0
    def __init__(self,
                 app_name=None,
                 secret_token=None,
                 transport_class=None,
                 include_paths=None,
                 exclude_paths=None,
                 timeout=None,
                 hostname=None,
                 auto_log_stacks=None,
                 string_max_length=None,
                 list_max_length=None,
                 processors=None,
                 filter_exception_types=None,
                 servers=None,
                 async_mode=None,
                 traces_send_freq_secs=None,
                 transactions_ignore_patterns=None,
                 git_ref=None,
                 app_version=None,
                 **kwargs):
        self.app_name = self.secret_token = self.git_ref = self.app_version = None
        # configure loggers first
        cls = self.__class__
        self.logger = logging.getLogger('%s.%s' %
                                        (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger('elasticapm.errors')
        self.state = ClientState()
        self._configure(app_name=app_name,
                        secret_token=secret_token,
                        git_ref=git_ref,
                        app_version=app_version)
        self.servers = servers or defaults.SERVERS
        self.async_mode = (async_mode is True or
                           (defaults.ASYNC_MODE and async_mode is not False))
        if not transport_class:
            transport_class = (defaults.ASYNC_TRANSPORT_CLASS
                               if self.async_mode else
                               defaults.SYNC_TRANSPORT_CLASS)
        self._transport_class = import_string(transport_class)
        self._transports = {}

        # servers may be set to a NoneType (for Django)
        if self.servers and not (self.app_name and self.secret_token):
            msg = 'Missing configuration for ElasticAPM client. Please see documentation.'
            self.logger.info(msg)

        self.is_send_disabled = (os.environ.get('ELASTIC_APM_DISABLE_SEND',
                                                '').lower() in ('1', 'true'))
        if self.is_send_disabled:
            self.logger.info(
                'Not sending any data to APM Server due to ELASTIC_APM_DISABLE_SEND '
                'environment variable')

        self.include_paths = set(include_paths or defaults.INCLUDE_PATHS)
        self.exclude_paths = set(exclude_paths or defaults.EXCLUDE_PATHS)
        self.timeout = int(timeout or defaults.TIMEOUT)
        self.hostname = six.text_type(hostname or defaults.HOSTNAME)
        self.auto_log_stacks = bool(auto_log_stacks
                                    or defaults.AUTO_LOG_STACKS)

        self.string_max_length = int(string_max_length
                                     or defaults.MAX_LENGTH_STRING)
        self.list_max_length = int(list_max_length or defaults.MAX_LENGTH_LIST)
        self.traces_send_freq_secs = (traces_send_freq_secs
                                      or defaults.TRACES_SEND_FREQ_SECS)

        self.filter_exception_types_dict = {}
        for exc_to_filter in (filter_exception_types or []):
            exc_to_filter_type = exc_to_filter.split(".")[-1]
            exc_to_filter_module = ".".join(exc_to_filter.split(".")[:-1])
            self.filter_exception_types_dict[
                exc_to_filter_type] = exc_to_filter_module

        if processors is None:
            self.processors = defaults.PROCESSORS
        else:
            self.processors = processors
        self.processors = [import_string(p) for p in self.processors]

        self.instrumentation_store = TransactionsStore(
            lambda: self.get_stack_info_for_trace(iter_stack_frames(), False),
            self.traces_send_freq_secs, transactions_ignore_patterns)
        atexit_register(self.close)
Beispiel #15
0
class RequestStoreTest(TestCase):
    def setUp(self):
        self.mock_get_frames = Mock()

        frames = [{
            'function': 'something_expensive',
            'abs_path':
            '/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/views.py',
            'lineno': 52,
            'module': 'tests.contrib.django.testapp.views',
            'filename': 'tests/contrib/django/testapp/views.py'
        }, {
            'function': '_resolve_lookup',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
            'lineno': 789,
            'module': 'django.template.base',
            'filename': 'django/template/base.py'
        }, {
            'function': 'resolve',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
            'lineno': 735,
            'module': 'django.template.base',
            'filename': 'django/template/base.py'
        }, {
            'function': 'resolve',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
            'lineno': 585,
            'module': 'django.template.base',
            'filename': 'django/template/base.py'
        }, {
            'lineno':
            4,
            'filename':
            u'/var/parent-elasticapm/elasticapm/tests/contrib/django/testapp/templates/list_fish.html'
        }, {
            'function': 'render',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/defaulttags.py',
            'lineno': 4,
            'module': 'django.template.defaulttags',
            'filename': 'django/template/defaulttags.py'
        }, {
            'function': 'render_node',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/debug.py',
            'lineno': 78,
            'module': 'django.template.debug',
            'filename': 'django/template/debug.py'
        }, {
            'function': 'render',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
            'lineno': 840,
            'module': 'django.template.base',
            'filename': 'django/template/base.py'
        }, {
            'function': 'instrumented_test_render',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/utils.py',
            'lineno': 85,
            'module': 'django.test.utils',
            'filename': 'django/test/utils.py'
        }, {
            'function': 'render',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/base.py',
            'lineno': 140,
            'module': 'django.template.base',
            'filename': 'django/template/base.py'
        }, {
            'function': 'rendered_content',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py',
            'lineno': 82,
            'module': 'django.template.response',
            'filename': 'django/template/response.py'
        }, {
            'function': 'render',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/template/response.py',
            'lineno': 105,
            'module': 'django.template.response',
            'filename': 'django/template/response.py'
        }, {
            'function': 'get_response',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/core/handlers/base.py',
            'lineno': 137,
            'module': 'django.core.handlers.base',
            'filename': 'django/core/handlers/base.py'
        }, {
            'function': '__call__',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
            'lineno': 109,
            'module': 'django.test.client',
            'filename': 'django/test/client.py'
        }, {
            'function': 'request',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
            'lineno': 426,
            'module': 'django.test.client',
            'filename': 'django/test/client.py'
        }, {
            'function': 'get',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
            'lineno': 280,
            'module': 'django.test.client',
            'filename': 'django/test/client.py'
        }, {
            'function': 'get',
            'abs_path':
            '/home/ron/.virtualenvs/elasticapm/local/lib/python2.7/site-packages/django/test/client.py',
            'lineno': 473,
            'module': 'django.test.client',
            'filename': 'django/test/client.py'
        }, {
            'function': 'test_template_name_as_view',
            'abs_path':
            '/var/parent-elasticapm/elasticapm/tests/contrib/django/django_tests.py',
            'lineno': 710,
            'module': 'tests.contrib.django.django_tests',
            'filename': 'tests/contrib/django/django_tests.py'
        }]

        self.mock_get_frames.return_value = frames
        self.requests_store = TransactionsStore(self.mock_get_frames, 99999)

    def test_lru_get_frames_cache(self):
        self.requests_store.begin_transaction("transaction.test")

        for i in range(10):
            with trace("bleh", "custom"):
                time.sleep(0.01)

        self.assertEqual(self.mock_get_frames.call_count, 10)

    def test_leaf_tracing(self):
        self.requests_store.begin_transaction("transaction.test")

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

                # These two traces should not show up
                with trace("ignored-child1", "custom", leaf=True):
                    time.sleep(0.01)

                with trace("ignored-child2", "custom", leaf=False):
                    time.sleep(0.01)

        self.requests_store.end_transaction(None, "transaction")

        transactions = self.requests_store.get_all()
        traces = transactions[0]['traces']

        self.assertEqual(len(traces), 3)

        signatures = ['transaction', 'root', 'child1-leaf']
        self.assertEqual(set([t['name'] for t in traces]), set(signatures))
Beispiel #16
0
    def __init__(self, config=None, **inline):
        # configure loggers first
        cls = self.__class__
        self.logger = logging.getLogger("%s.%s" %
                                        (cls.__module__, cls.__name__))
        self.error_logger = logging.getLogger("elasticapm.errors")
        self.state = ClientState()

        self.transaction_store = None
        self.processors = []
        self.filter_exception_types_dict = {}
        self._send_timer = None
        self._transports = {}
        self._service_info = None

        self.config = Config(config, inline_dict=inline)
        if self.config.errors:
            for msg in self.config.errors.values():
                self.error_logger.error(msg)
            self.config.disable_send = True

        self._transport_class = import_string(self.config.transport_class)

        for exc_to_filter in self.config.filter_exception_types or []:
            exc_to_filter_type = exc_to_filter.split(".")[-1]
            exc_to_filter_module = ".".join(exc_to_filter.split(".")[:-1])
            self.filter_exception_types_dict[
                exc_to_filter_type] = exc_to_filter_module

        self.processors = [import_string(p) for p in self.config.processors
                           ] if self.config.processors else []

        if platform.python_implementation() == "PyPy":
            # PyPy introduces a `_functools.partial.__call__` frame due to our use
            # of `partial` in AbstractInstrumentedModule
            skip_modules = ("elasticapm.", "_functools")
        else:
            skip_modules = ("elasticapm.", )

        def frames_collector_func():
            return self._get_stack_info_for_trace(
                stacks.iter_stack_frames(skip_top_modules=skip_modules),
                library_frame_context_lines=self.config.
                source_lines_span_library_frames,
                in_app_frame_context_lines=self.config.
                source_lines_span_app_frames,
                with_locals=self.config.collect_local_variables
                in ("all", "transactions"),
                locals_processor_func=lambda local_var: varmap(
                    lambda k, v: shorten(
                        v,
                        list_length=self.config.local_var_list_max_length,
                        string_length=self.config.local_var_max_length,
                    ),
                    local_var,
                ),
            )

        self.transaction_store = TransactionsStore(
            frames_collector_func=frames_collector_func,
            collect_frequency=self.config.flush_interval,
            sample_rate=self.config.transaction_sample_rate,
            max_spans=self.config.transaction_max_spans,
            span_frames_min_duration=self.config.span_frames_min_duration_ms,
            max_queue_size=self.config.max_queue_size,
            ignore_patterns=self.config.transactions_ignore_patterns,
        )
        self.include_paths_re = stacks.get_path_regex(
            self.config.include_paths) if self.config.include_paths else None
        self.exclude_paths_re = stacks.get_path_regex(
            self.config.exclude_paths) if self.config.exclude_paths else None
        compat.atexit_register(self.close)