def test_generate_random_64bit_string(rand):
    rand.return_value = b'17133d482ba4f605'
    random_string = request_helper.generate_random_64bit_string()
    assert random_string == '17133d482ba4f605'
    # This acts as a contract test of sorts. This should return a str
    # in both py2 and py3. IOW, no unicode objects.
    assert isinstance(random_string, str)
Example #2
0
def create_headers_for_new_span():
    """
    Generate the headers for a new zipkin span.

    .. note::

        If the method is not called from within a pyramid service call OR
        pyramid_zipkin is not included as a pyramid tween, empty dict will be
        returned back.

    :returns: dict containing (X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId,
                X-B3-Flags and X-B3-Sampled) keys OR an empty dict.
    """
    zipkin_attrs = get_zipkin_attrs()

    if not zipkin_attrs:
        return {}

    return {
        'X-B3-TraceId': zipkin_attrs.trace_id,
        'X-B3-SpanId': generate_random_64bit_string(),
        'X-B3-ParentSpanId': zipkin_attrs.span_id,
        'X-B3-Flags': '0',
        'X-B3-Sampled': '1' if zipkin_attrs.is_sampled else '0',
    }
Example #3
0
    def __enter__(self):
        """Enter the client context. All spans/annotations logged inside this
        context will be attributed to this client span.
        """
        zipkin_attrs = get_zipkin_attrs()
        self.is_sampled = zipkin_attrs is not None and zipkin_attrs.is_sampled
        if not self.is_sampled:
            return self

        self.start_timestamp = time.time()
        self.span_id = generate_random_64bit_string()
        # Put span ID on logging handler. Assume there's only a single handler
        # on the logger, since all logging should be set up in this package.
        self.handler = zipkin_logger.handlers[0]
        # Store the old parent_span_id, probably None, in case we have
        # nested ClientSpanContexts
        self.old_parent_span_id = self.handler.parent_span_id
        self.handler.parent_span_id = self.span_id
        # Push new zipkin attributes onto the threadlocal stack, so that
        # create_headers_for_new_span() performs as expected in this context.
        # The only difference is that span_id is this new client span's ID
        # and parent_span_id is the old span's ID.
        new_zipkin_attrs = ZipkinAttrs(
            trace_id=zipkin_attrs.trace_id,
            span_id=self.span_id,
            parent_span_id=zipkin_attrs.span_id,
            flags=zipkin_attrs.flags,
            is_sampled=zipkin_attrs.is_sampled,
        )
        push_zipkin_attrs(new_zipkin_attrs)
        return self
Example #4
0
    def __enter__(self):
        """Enter the new span context. All spans/annotations logged inside this
        context will be attributed to this span.

        In the unsampled case, this context still generates new span IDs and
        pushes them onto the threadlocal stack, so downstream services calls
        made will pass the correct headers. However, the logging handler is
        never attached in the unsampled case, so it is left alone.
        """
        zipkin_attrs = get_zipkin_attrs()
        self.is_sampled = zipkin_attrs is not None and zipkin_attrs.is_sampled
        self.span_id = generate_random_64bit_string()
        self.start_timestamp = time.time()
        # Push new zipkin attributes onto the threadlocal stack, so that
        # create_headers_for_new_span() performs as expected in this context.
        # The only difference is that span_id is this new span's ID
        # and parent_span_id is the old span's ID. Checking for a None
        # zipkin_attrs value is protecting against calling this outside of
        # a zipkin logging context entirely (e.g. in a batch). If new attrs
        # are stored, set a flag to pop them off at context exit.
        self.do_pop_attrs = False
        if zipkin_attrs is not None:
            new_zipkin_attrs = ZipkinAttrs(
                trace_id=zipkin_attrs.trace_id,
                span_id=self.span_id,
                parent_span_id=zipkin_attrs.span_id,
                flags=zipkin_attrs.flags,
                is_sampled=zipkin_attrs.is_sampled,
            )
            push_zipkin_attrs(new_zipkin_attrs)
            self.do_pop_attrs = True
        # In the sampled case, patch the ZipkinLoggerHandler.
        if self.is_sampled:
            # Be defensive about logging setup. Since ZipkinAttrs are local
            # the a thread, multithreaded frameworks can get in strange states.
            # The logging is not going to be correct in these cases, so we set
            # a flag that turns off logging on __exit__.
            if len(zipkin_logger.handlers) > 0:
                # Put span ID on logging handler. Assume there's only a single
                # handler, since all logging should be set up in this package.
                self.handler = zipkin_logger.handlers[0]
                # Store the old parent_span_id, probably None, in case we have
                # nested SpanContexts
                self.old_parent_span_id = self.handler.parent_span_id
                self.handler.parent_span_id = self.span_id
                self.logging_initialized = True

        return self
def test_generate_random_64bit_string(rand):
    rand.return_value = b'17133d482ba4f605'
    assert request_helper.generate_random_64bit_string() == '17133d482ba4f605'
    def log_spans(self):
        """Main function to log all the annotations stored during the entire
        request. This is done if the request is sampled and the response was
        a success. It also logs the service `ss` and `sr` annotations.
        """
        if self.zipkin_attrs.is_sampled and self.is_response_success():
            # Collect additional annotations from the logging handler
            annotations_by_span_id = defaultdict(dict)
            binary_annotations_by_span_id = defaultdict(dict)
            for msg in self.handler.extra_annotations:
                span_id = msg['parent_span_id'] or self.zipkin_attrs.span_id
                # This should check if these are non-None
                annotations_by_span_id[span_id].update(msg['annotations'])
                binary_annotations_by_span_id[span_id].update(
                    msg['binary_annotations']
                )

            # Collect, annotate, and log client spans from the logging handler
            for span in self.handler.client_spans:
                # The parent_span_id is either the parent ID set in the
                # logging handler or the current Zipkin context's span ID.
                parent_span_id = (
                    span['parent_span_id'] or
                    self.zipkin_attrs.span_id
                )
                # A new client span's span ID can be overridden
                span_id = span['span_id'] or generate_random_64bit_string()
                endpoint = copy_endpoint_with_new_service_name(
                    self.thrift_endpoint, span['service_name']
                )
                # Collect annotations both logged with the new spans and
                # logged in separate log messages.
                annotations = span['annotations']
                annotations.update(annotations_by_span_id[span_id])
                binary_annotations = span['binary_annotations']
                binary_annotations.update(binary_annotations_by_span_id[span_id])
                # Create serializable thrift objects of annotations
                thrift_annotations = annotation_list_builder(
                    annotations, endpoint
                )
                thrift_binary_annotations = binary_annotation_list_builder(
                    binary_annotations, endpoint
                )

                log_span(
                    span_id=span_id,
                    parent_span_id=parent_span_id,
                    trace_id=self.zipkin_attrs.trace_id,
                    span_name=span['span_name'],
                    annotations=thrift_annotations,
                    binary_annotations=thrift_binary_annotations,
                    registry_settings=self.registry_settings,
                )

            # Collect extra annotations for server span, then log it.
            extra_annotations = annotations_by_span_id[self.zipkin_attrs.span_id]
            extra_binary_annotations = binary_annotations_by_span_id[
                self.zipkin_attrs.span_id
            ]
            annotations = dict(
                sr=self.start_timestamp,
                ss=time.time(),
                **extra_annotations
            )
            thrift_annotations = annotation_list_builder(
                annotations,
                self.thrift_endpoint,
            )

            # Binary annotations can be set through debug messages or the
            # set_extra_binary_annotations registry setting.
            self.binary_annotations_dict.update(extra_binary_annotations)
            thrift_binary_annotations = binary_annotation_list_builder(
                self.binary_annotations_dict,
                self.thrift_endpoint,
            )

            span_name = "{0} {1}".format(
                self.request_method, self.request_path)
            log_span(
                span_id=self.zipkin_attrs.span_id,
                parent_span_id=self.zipkin_attrs.parent_span_id,
                trace_id=self.zipkin_attrs.trace_id,
                span_name=span_name,
                annotations=thrift_annotations,
                binary_annotations=thrift_binary_annotations,
                registry_settings=self.registry_settings,
            )
def test_generate_random_64bit_string(rand):
    rand.return_value = '42'
    assert request_helper.generate_random_64bit_string() == '42'
Example #8
0
def test_generate_random_64bit_string(rand):
    rand.return_value = '42'
    assert request_helper.generate_random_64bit_string() == '42'