py_zipkin-0.9.0/0000755000174700017470000000000013140416534012777 5ustar debiandebianpy_zipkin-0.9.0/setup.cfg0000644000174700017470000000016513140416534014622 0ustar debiandebian[wheel] universal = True [pep8] ignore = E265,E309,E501 [egg_info] tag_date = 0 tag_build = tag_svn_revision = 0 py_zipkin-0.9.0/setup.py0000644000174700017470000000207513137710705014520 0ustar debiandebian#!/usr/bin/python # -*- coding: utf-8 -*- from setuptools import find_packages from setuptools import setup __version__ = '0.9.0' setup( name='py_zipkin', version=__version__, provides=["py_zipkin"], author='Yelp, Inc.', author_email='opensource+py-zipkin@yelp.com', license='Copyright Yelp 2017', url="https://github.com/Yelp/py_zipkin", description='Library for using Zipkin in Python.', packages=find_packages(exclude=('tests*', 'testing*', 'tools*')), package_data={'': ['*.thrift']}, install_requires=[ 'six', 'thriftpy', ], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", ], ) py_zipkin-0.9.0/PKG-INFO0000644000174700017470000000137513140416534014102 0ustar debiandebianMetadata-Version: 1.1 Name: py_zipkin Version: 0.9.0 Summary: Library for using Zipkin in Python. Home-page: https://github.com/Yelp/py_zipkin Author: Yelp, Inc. Author-email: opensource+py-zipkin@yelp.com License: Copyright Yelp 2017 Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Provides: py_zipkin py_zipkin-0.9.0/py_zipkin/0000755000174700017470000000000013140416534015013 5ustar debiandebianpy_zipkin-0.9.0/py_zipkin/zipkin.py0000644000174700017470000005237113137710705016704 0ustar debiandebian# -*- coding: utf-8 -*- import functools import random import time from collections import namedtuple from py_zipkin.exception import ZipkinError from py_zipkin.logging_helper import zipkin_logger from py_zipkin.logging_helper import ZipkinLoggerHandler from py_zipkin.logging_helper import ZipkinLoggingContext from py_zipkin.thread_local import get_zipkin_attrs from py_zipkin.thread_local import pop_zipkin_attrs from py_zipkin.thread_local import push_zipkin_attrs from py_zipkin.thrift import SERVER_ADDR_VAL from py_zipkin.thrift import create_binary_annotation from py_zipkin.thrift import create_endpoint from py_zipkin.thrift import zipkin_core from py_zipkin.util import generate_random_64bit_string from py_zipkin.util import generate_random_128bit_string """ Holds the basic attributes needed to log a zipkin trace :param trace_id: Unique trace id :param span_id: Span Id of the current request span :param parent_span_id: Parent span Id of the current request span :param flags: stores flags header. Currently unused :param is_sampled: pre-computed boolean whether the trace should be logged """ ZipkinAttrs = namedtuple( 'ZipkinAttrs', ['trace_id', 'span_id', 'parent_span_id', 'flags', 'is_sampled'], ) STANDARD_ANNOTATIONS = { 'client': {'cs', 'cr'}, 'server': {'ss', 'sr'}, } STANDARD_ANNOTATIONS_KEYS = frozenset(STANDARD_ANNOTATIONS.keys()) class zipkin_span(object): """Context manager/decorator for all of your zipkin tracing needs. Usage #1: Start a trace with a given sampling rate This begins the zipkin trace and also records the root span. The required params are service_name, transport_handler, and sample_rate. # Start a trace with do_stuff() as the root span def some_batch_job(a, b): with zipkin_span( service_name='my_service', span_name='my_span_name', transport_handler=some_handler, port=22, sample_rate=0.05, ): do_stuff() Usage #2: Trace a service call. The typical use case is instrumenting a framework like Pyramid or Django. Only ss and sr times are recorded for the root span. Required params are service_name, zipkin_attrs, transport_handler, and port. # Used in a pyramid tween def tween(request): zipkin_attrs = some_zipkin_attr_creator(request) with zipkin_span( service_name='my_service,' span_name='my_span_name', zipkin_attrs=zipkin_attrs, transport_handler=some_handler, port=22, ) as zipkin_context: response = handler(request) zipkin_context.update_binary_annotations( some_binary_annotations) return response Usage #3: Log a span within the context of a zipkin trace If you're already in a zipkin trace, you can use this to log a span inside. The only required param is service_name. If you're not in a zipkin trace, this won't do anything. # As a decorator @zipkin_span(service_name='my_service', span_name='my_function') def my_function(): do_stuff() # As a context manager def my_function(): with zipkin_span(service_name='my_service', span_name='do_stuff'): do_stuff() """ def __init__( self, service_name, span_name='span', zipkin_attrs=None, transport_handler=None, max_span_batch_size=None, annotations=None, binary_annotations=None, port=0, sample_rate=None, include=('client', 'server'), add_logging_annotation=False, report_root_timestamp=False, use_128bit_trace_id=False, host=None ): """Logs a zipkin span. If this is the root span, then a zipkin trace is started as well. :param service_name: The name of the called service :type service_name: string :param span_name: Optional name of span, defaults to 'span' :type span_name: string :param zipkin_attrs: Optional set of zipkin attributes to be used :type zipkin_attrs: ZipkinAttrs :param transport_handler: Callback function that takes a message parameter and handles logging it :type transport_handler: function :param max_span_batch_size: Spans in a trace are sent in batches, max_span_batch_size defines max size of one batch :type max_span_batch_size: int :param annotations: Optional dict of str -> timestamp annotations :type annotations: dict of str -> int :param binary_annotations: Optional dict of str -> str span attrs :type binary_annotations: dict of str -> str :param port: The port number of the service. Defaults to 0. :type port: int :param sample_rate: Rate at which to sample; 0.0 - 100.0. If passed-in zipkin_attrs have is_sampled=False and the sample_rate param is > 0, a new span will be generated at this rate. This means that if you propagate sampling decisions to downstream services, but still have sample_rate > 0 in those services, the actual rate of generated spans for those services will be > sampling_rate. :type sample_rate: float :param include: which annotations to include can be one of {'client', 'server'} corresponding to ('cs', 'cr') and ('ss', 'sr') respectively :type include: iterable :param add_logging_annotation: Whether to add a 'logging_end' annotation when py_zipkin finishes logging spans :type add_logging_annotation: boolean :param report_root_timestamp: Whether the span should report timestamp and duration. Only applies to "root" spans in this local context, so spans created inside other span contexts will always log timestamp/duration. Note that this is only an override for spans that have zipkin_attrs passed in. Spans that make their own sampling decisions (i.e. are the root spans of entire traces) will always report timestamp/duration. :type report_root_timestamp: boolean :param use_128bit_trace_id: If true, generate 128-bit trace_ids :type use_128bit_trace_id: boolean :param host: Contains the ipv4 value of the host. The ipv4 value isn't automatically determined in a docker environment :type host: string """ self.service_name = service_name self.span_name = span_name self.zipkin_attrs = zipkin_attrs self.transport_handler = transport_handler self.max_span_batch_size = max_span_batch_size self.annotations = annotations or {} self.binary_annotations = binary_annotations or {} self.port = port self.logging_context = None self.sample_rate = sample_rate self.include = include self.add_logging_annotation = add_logging_annotation self.report_root_timestamp_override = report_root_timestamp self.use_128bit_trace_id = use_128bit_trace_id self.host = host self.logging_configured = False # Spans that log a 'cs' timestamp can additionally record # 'sa' binary annotations that show where the request is going. # This holds a list of 'sa' binary annotations. self.sa_binary_annotations = [] # Validation checks if self.zipkin_attrs or self.sample_rate is not None: if self.transport_handler is None: raise ZipkinError( 'Root spans require a transport handler to be given') if self.sample_rate is not None and not (0.0 <= self.sample_rate <= 100.0): raise ZipkinError('Sample rate must be between 0.0 and 100.0') if not set(include).issubset(STANDARD_ANNOTATIONS_KEYS): raise ZipkinError( 'Only %s are supported as annotations' % STANDARD_ANNOTATIONS_KEYS ) else: # get a list of all of the mapped annotations self.annotation_filter = set() for include_name in include: self.annotation_filter.update(STANDARD_ANNOTATIONS[include_name]) def __call__(self, f): @functools.wraps(f) def decorated(*args, **kwargs): with zipkin_span( service_name=self.service_name, span_name=self.span_name, zipkin_attrs=self.zipkin_attrs, transport_handler=self.transport_handler, annotations=self.annotations, binary_annotations=self.binary_annotations, port=self.port, sample_rate=self.sample_rate, include=self.include, host=self.host ): return f(*args, **kwargs) return decorated def __enter__(self): return self.start() def start(self): """Enter the new span context. All annotations logged inside this context will be attributed to this span. All new spans generated inside this context will have this span as their parent. 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 the spans are never logged. """ self.do_pop_attrs = False # If zipkin_attrs are passed in or this span is doing its own sampling, # it will need to actually log spans at __exit__. self.perform_logging = self.zipkin_attrs or self.sample_rate is not None report_root_timestamp = False if self.sample_rate is not None: if self.zipkin_attrs and not self.zipkin_attrs.is_sampled: report_root_timestamp = True self.zipkin_attrs = create_attrs_for_span( sample_rate=self.sample_rate, trace_id=self.zipkin_attrs.trace_id, use_128bit_trace_id=self.use_128bit_trace_id, ) elif not self.zipkin_attrs: report_root_timestamp = True self.zipkin_attrs = create_attrs_for_span( sample_rate=self.sample_rate, use_128bit_trace_id=self.use_128bit_trace_id, ) if not self.zipkin_attrs: # This span is inside the context of an existing trace existing_zipkin_attrs = get_zipkin_attrs() if existing_zipkin_attrs: self.zipkin_attrs = ZipkinAttrs( trace_id=existing_zipkin_attrs.trace_id, span_id=generate_random_64bit_string(), parent_span_id=existing_zipkin_attrs.span_id, flags=existing_zipkin_attrs.flags, is_sampled=existing_zipkin_attrs.is_sampled, ) # If zipkin_attrs are not set up by now, that means this span is not # configured to perform logging itself, and it's not in an existing # Zipkin trace. That means there's nothing else to do and it can exit # early. if not self.zipkin_attrs: return self push_zipkin_attrs(self.zipkin_attrs) self.do_pop_attrs = True self.start_timestamp = time.time() if self.perform_logging: # Don't set up any logging if we're not sampling if not self.zipkin_attrs.is_sampled: return self endpoint = create_endpoint(self.port, self.service_name, self.host) client_context = set(self.include) == {'client'} self.log_handler = ZipkinLoggerHandler(self.zipkin_attrs) self.logging_context = ZipkinLoggingContext( self.zipkin_attrs, endpoint, self.log_handler, self.span_name, self.transport_handler, report_root_timestamp or self.report_root_timestamp_override, binary_annotations=self.binary_annotations, add_logging_annotation=self.add_logging_annotation, client_context=client_context, max_span_batch_size=self.max_span_batch_size, ) self.logging_context.start() self.logging_configured = True return self else: # In the sampled case, patch the ZipkinLoggerHandler. if self.zipkin_attrs.is_sampled: # Be defensive about logging setup. Since ZipkinAttrs are local to # the 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__. try: # Assume there's only a single handler, since all logging # should be set up in this package. log_handler = zipkin_logger.handlers[0] except IndexError: return self # Make sure it's not a NullHandler or something if not isinstance(log_handler, ZipkinLoggerHandler): return self # Put span ID on logging handler. self.log_handler = zipkin_logger.handlers[0] # Store the old parent_span_id, probably None, in case we have # nested zipkin_spans self.old_parent_span_id = self.log_handler.parent_span_id self.log_handler.parent_span_id = self.zipkin_attrs.span_id self.logging_configured = True return self def __exit__(self, _exc_type, _exc_value, _exc_traceback): self.stop(_exc_type, _exc_value, _exc_traceback) def stop(self, _exc_type=None, _exc_value=None, _exc_traceback=None): """Exit the span context. Zipkin attrs are pushed onto the threadlocal stack regardless of sampling, so they always need to be popped off. The actual logging of spans depends on sampling and that the logging was correctly set up. """ if self.do_pop_attrs: pop_zipkin_attrs() if not self.logging_configured: return # Add the error annotation if an exception occurred if any((_exc_type, _exc_value, _exc_traceback)): error_msg = '{0}: {1}'.format(_exc_type.__name__, _exc_value) self.update_binary_annotations({ zipkin_core.ERROR: error_msg, }) # Logging context is only initialized for "root" spans of the local # process (i.e. this zipkin_span not inside of any other local # zipkin_spans) if self.logging_context: self.logging_context.stop() self.logging_context = None return # If we've gotten here, that means that this span is a child span of # this context's root span (i.e. it's a zipkin_span inside another # zipkin_span). end_timestamp = time.time() self.log_handler.parent_span_id = self.old_parent_span_id # We are simulating a full two-part span locally, so set cs=sr and ss=cr full_annotations = { 'cs': self.start_timestamp, 'sr': self.start_timestamp, 'ss': end_timestamp, 'cr': end_timestamp, } # But we filter down if we only want to emit some of the annotations filtered_annotations = { k: v for k, v in full_annotations.items() if k in self.annotation_filter } self.annotations.update(filtered_annotations) self.log_handler.store_local_span( span_name=self.span_name, service_name=self.service_name, annotations=self.annotations, binary_annotations=self.binary_annotations, sa_binary_annotations=self.sa_binary_annotations, span_id=self.zipkin_attrs.span_id, ) def update_binary_annotations(self, extra_annotations): """Updates the binary annotations for the current span. If this trace is not being sampled then this is a no-op. """ if not self.zipkin_attrs: return if not self.zipkin_attrs.is_sampled: return if not self.logging_context: # This is not the root span, so binary annotations will be added # to the log handler when this span context exits. self.binary_annotations.update(extra_annotations) else: # Otherwise, we're in the context of the root span, so just update # the binary annotations for the logging context directly. self.logging_context.binary_annotations_dict.update(extra_annotations) def add_sa_binary_annotation( self, port=0, service_name='unknown', host='127.0.0.1', ): """Adds a 'sa' binary annotation to the current span. 'sa' binary annotations are useful for situations where you need to log where a request is going but the destination doesn't support zipkin. Note that the span must have 'cs'/'cr' annotations. :param port: The port number of the destination :type port: int :param service_name: The name of the destination service :type service_name: str :param host: Host address of the destination :type host: str """ if not self.zipkin_attrs or not self.zipkin_attrs.is_sampled: return if 'client' not in self.include: # TODO: trying to set a sa binary annotation for a non-client span # should result in a logged error return sa_endpoint = create_endpoint( port=port, service_name=service_name, host=host, ) sa_binary_annotation = create_binary_annotation( key=zipkin_core.SERVER_ADDR, value=SERVER_ADDR_VAL, annotation_type=zipkin_core.AnnotationType.BOOL, host=sa_endpoint, ) if not self.logging_context: self.sa_binary_annotations.append(sa_binary_annotation) else: self.logging_context.sa_binary_annotations.append(sa_binary_annotation) def _validate_args(kwargs): if 'include' in kwargs: raise ValueError( '"include" is not valid in this context. ' 'You probably want to use zipkin_span()' ) class zipkin_client_span(zipkin_span): """Logs a client-side zipkin span. Subclass of :class:`zipkin_span` using only annotations relevant to clients """ def __init__(self, *args, **kwargs): """Logs a zipkin span with client annotations. See :class:`zipkin_span` for arguments """ _validate_args(kwargs) kwargs['include'] = ('client',) super(zipkin_client_span, self).__init__(*args, **kwargs) class zipkin_server_span(zipkin_span): """Logs a server-side zipkin span. Subclass of :class:`zipkin_span` using only annotations relevant to servers """ def __init__(self, *args, **kwargs): """Logs a zipkin span with server annotations. See :class:`zipkin_span` for arguments """ _validate_args(kwargs) kwargs['include'] = ('server',) super(zipkin_server_span, self).__init__(*args, **kwargs) def create_attrs_for_span( sample_rate=100.0, trace_id=None, span_id=None, use_128bit_trace_id=False, ): """Creates a set of zipkin attributes for a span. :param sample_rate: Float between 0.0 and 100.0 to determine sampling rate :type sample_rate: float :param trace_id: Optional 16-character hex string representing a trace_id. If this is None, a random trace_id will be generated. :type trace_id: str :param span_id: Optional 16-character hex string representing a span_id. If this is None, a random span_id will be generated. :type span_id: str """ # Calculate if this trace is sampled based on the sample rate if trace_id is None: if use_128bit_trace_id: trace_id = generate_random_128bit_string() else: trace_id = generate_random_64bit_string() if span_id is None: span_id = generate_random_64bit_string() if sample_rate == 0.0: is_sampled = False else: is_sampled = (random.random() * 100) < sample_rate return ZipkinAttrs( trace_id=trace_id, span_id=span_id, parent_span_id=None, flags='0', is_sampled=is_sampled, ) def create_http_headers_for_new_span(): """ Generate the headers for a new zipkin span. .. note:: If the method is not called from within a zipkin_trace conext, 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', } py_zipkin-0.9.0/py_zipkin/exception.py0000644000174700017470000000016212770300472017363 0ustar debiandebian# -*- coding: utf-8 -*- class ZipkinError(Exception): """Custom error to be raised on Zipkin exceptions.""" py_zipkin-0.9.0/py_zipkin/__init__.py0000644000174700017470000000000012770300472017113 0ustar debiandebianpy_zipkin-0.9.0/py_zipkin/thread_local.py0000644000174700017470000000227412770300472020014 0ustar debiandebian# -*- coding: utf-8 -*- import threading _thread_local = threading.local() def get_thread_local_zipkin_attrs(): """A wrapper to return _thread_local.requests :returns: list that may contain zipkin attribute tuples :rtype: list """ if not hasattr(_thread_local, 'zipkin_attrs'): _thread_local.zipkin_attrs = [] return _thread_local.zipkin_attrs def get_zipkin_attrs(): """Get the topmost level zipkin attributes stored. :returns: tuple containing zipkin attrs :rtype: :class:`zipkin.ZipkinAttrs` """ zipkin_attrs = get_thread_local_zipkin_attrs() if zipkin_attrs: return zipkin_attrs[-1] def pop_zipkin_attrs(): """Pop the topmost level zipkin attributes, if present. :returns: tuple containing zipkin attrs :rtype: :class:`zipkin.ZipkinAttrs` """ zipkin_attrs = get_thread_local_zipkin_attrs() if zipkin_attrs: return zipkin_attrs.pop() def push_zipkin_attrs(zipkin_attr): """Stores the zipkin attributes to thread local. :param zipkin_attr: tuple containing zipkin related attrs :type zipkin_attr: :class:`zipkin.ZipkinAttrs` """ get_thread_local_zipkin_attrs().append(zipkin_attr) py_zipkin-0.9.0/py_zipkin/thrift/0000755000174700017470000000000013140416534016313 5ustar debiandebianpy_zipkin-0.9.0/py_zipkin/thrift/__init__.py0000644000174700017470000001372113137710705020433 0ustar debiandebian# -*- coding: utf-8 -*- import os import socket import struct import thriftpy from thriftpy.protocol.binary import TBinaryProtocol from thriftpy.protocol.binary import write_list_begin from thriftpy.thrift import TType from thriftpy.transport import TMemoryBuffer from py_zipkin.util import unsigned_hex_to_signed_int thrift_filepath = os.path.join(os.path.dirname(__file__), 'zipkinCore.thrift') zipkin_core = thriftpy.load(thrift_filepath, module_name="zipkinCore_thrift") SERVER_ADDR_VAL = '\x01' dummy_endpoint = zipkin_core.Endpoint() def create_annotation(timestamp, value, host): """ Create a zipkin annotation object :param timestamp: timestamp of when the annotation occured in microseconds :param value: name of the annotation, such as 'sr' :param host: zipkin endpoint object :returns: zipkin annotation object """ return zipkin_core.Annotation(timestamp=timestamp, value=value, host=host) def create_binary_annotation(key, value, annotation_type, host): """ Create a zipkin binary annotation object :param key: name of the annotation, such as 'http.uri' :param value: value of the annotation, such as a URI :param annotation_type: type of annotation, such as AnnotationType.I32 :param host: zipkin endpoint object :returns: zipkin binary annotation object """ return zipkin_core.BinaryAnnotation( key=key, value=value, annotation_type=annotation_type, host=host, ) def create_endpoint(port=0, service_name='unknown', host=None): """Create a zipkin Endpoint object. An Endpoint object holds information about the network context of a span. :param port: int value of the port. Defaults to 0 :param service_name: service name as a str. Defaults to 'unknown' :param host: string containing ipv4 value of the host, if not provided, host is determined automatically :returns: zipkin Endpoint object """ if host is None: try: host = socket.gethostbyname(socket.gethostname()) except socket.gaierror: host = '127.0.0.1' # Convert ip address to network byte order ipv4 = struct.unpack('!i', socket.inet_aton(host))[0] # Zipkin passes unsigned values in signed types because Thrift has no # unsigned types, so we have to convert the value. port = struct.unpack('h', struct.pack('H', port))[0] return zipkin_core.Endpoint( ipv4=ipv4, port=port, service_name=service_name, ) def copy_endpoint_with_new_service_name(endpoint, service_name): """Copies a copy of a given endpoint with a new service name. This should be very fast, on the order of several microseconds. :param endpoint: existing zipkin_core.Endpoint object :param service_name: str of new service name :returns: zipkin Endpoint object """ return zipkin_core.Endpoint( ipv4=endpoint.ipv4, port=endpoint.port, service_name=service_name, ) def annotation_list_builder(annotations, host): """ Reformat annotations dict to return list of corresponding zipkin_core objects. :param annotations: dict containing key as annotation name, value being timestamp in seconds(float). :type host: :class:`zipkin_core.Endpoint` :returns: a list of annotation zipkin_core objects :rtype: list """ return [ create_annotation(int(timestamp * 1000000), key, host) for key, timestamp in annotations.items() ] def binary_annotation_list_builder(binary_annotations, host): """ Reformat binary annotations dict to return list of zipkin_core objects. The value of the binary annotations MUST be in string format. :param binary_annotations: dict with key, value being the name and value of the binary annotation being logged. :type host: :class:`zipkin_core.Endpoint` :returns: a list of binary annotation zipkin_core objects :rtype: list """ # TODO: Remove the type hard-coding of STRING to take it as a param option. ann_type = zipkin_core.AnnotationType.STRING return [ create_binary_annotation(key, str(value), ann_type, host) for key, value in binary_annotations.items() ] def create_span( span_id, parent_span_id, trace_id, span_name, annotations, binary_annotations, timestamp_s, duration_s, ): """Takes a bunch of span attributes and returns a thriftpy representation of the span. Timestamps passed in are in seconds, they're converted to microseconds before thrift encoding. """ # Check if trace_id is 128-bit. If so, record trace_id_high separately. trace_id_length = len(trace_id) trace_id_high = None if trace_id_length > 16: assert trace_id_length == 32 trace_id, trace_id_high = trace_id[16:], trace_id[:16] if trace_id_high: trace_id_high = unsigned_hex_to_signed_int(trace_id_high) span_dict = { 'trace_id': unsigned_hex_to_signed_int(trace_id), 'name': span_name, 'id': unsigned_hex_to_signed_int(span_id), 'annotations': annotations, 'binary_annotations': binary_annotations, 'timestamp': int(timestamp_s * 1000000) if timestamp_s else None, 'duration': int(duration_s * 1000000) if duration_s else None, 'trace_id_high': trace_id_high, } if parent_span_id: span_dict['parent_id'] = unsigned_hex_to_signed_int(parent_span_id) return zipkin_core.Span(**span_dict) def thrift_objs_in_bytes(thrift_obj_list): # pragma: no cover """ Returns TBinaryProtocol encoded Thrift objects. :param thrift_obj_list: thrift objects list to encode :returns: thrift objects in TBinaryProtocol format bytes. """ transport = TMemoryBuffer() protocol = TBinaryProtocol(transport) write_list_begin(transport, TType.STRUCT, len(thrift_obj_list)) for thrift_obj in thrift_obj_list: thrift_obj.write(protocol) return bytes(transport.getvalue()) py_zipkin-0.9.0/py_zipkin/thrift/zipkinCore.thrift0000644000174700017470000004213313057371042021656 0ustar debiandebian# Copyright 2012 Twitter Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. namespace java com.twitter.zipkin.thriftjava #@namespace scala com.twitter.zipkin.thriftscala namespace rb Zipkin #************** Annotation.value ************** /** * The client sent ("cs") a request to a server. There is only one send per * span. For example, if there's a transport error, each attempt can be logged * as a WIRE_SEND annotation. * * If chunking is involved, each chunk could be logged as a separate * CLIENT_SEND_FRAGMENT in the same span. * * Annotation.host is not the server. It is the host which logged the send * event, almost always the client. When logging CLIENT_SEND, instrumentation * should also log the SERVER_ADDR. */ const string CLIENT_SEND = "cs" /** * The client received ("cr") a response from a server. There is only one * receive per span. For example, if duplicate responses were received, each * can be logged as a WIRE_RECV annotation. * * If chunking is involved, each chunk could be logged as a separate * CLIENT_RECV_FRAGMENT in the same span. * * Annotation.host is not the server. It is the host which logged the receive * event, almost always the client. The actual endpoint of the server is * recorded separately as SERVER_ADDR when CLIENT_SEND is logged. */ const string CLIENT_RECV = "cr" /** * The server sent ("ss") a response to a client. There is only one response * per span. If there's a transport error, each attempt can be logged as a * WIRE_SEND annotation. * * Typically, a trace ends with a server send, so the last timestamp of a trace * is often the timestamp of the root span's server send. * * If chunking is involved, each chunk could be logged as a separate * SERVER_SEND_FRAGMENT in the same span. * * Annotation.host is not the client. It is the host which logged the send * event, almost always the server. The actual endpoint of the client is * recorded separately as CLIENT_ADDR when SERVER_RECV is logged. */ const string SERVER_SEND = "ss" /** * The server received ("sr") a request from a client. There is only one * request per span. For example, if duplicate responses were received, each * can be logged as a WIRE_RECV annotation. * * Typically, a trace starts with a server receive, so the first timestamp of a * trace is often the timestamp of the root span's server receive. * * If chunking is involved, each chunk could be logged as a separate * SERVER_RECV_FRAGMENT in the same span. * * Annotation.host is not the client. It is the host which logged the receive * event, almost always the server. When logging SERVER_RECV, instrumentation * should also log the CLIENT_ADDR. */ const string SERVER_RECV = "sr" /** * Optionally logs an attempt to send a message on the wire. Multiple wire send * events could indicate network retries. A lag between client or server send * and wire send might indicate queuing or processing delay. */ const string WIRE_SEND = "ws" /** * Optionally logs an attempt to receive a message from the wire. Multiple wire * receive events could indicate network retries. A lag between wire receive * and client or server receive might indicate queuing or processing delay. */ const string WIRE_RECV = "wr" /** * Optionally logs progress of a (CLIENT_SEND, WIRE_SEND). For example, this * could be one chunk in a chunked request. */ const string CLIENT_SEND_FRAGMENT = "csf" /** * Optionally logs progress of a (CLIENT_RECV, WIRE_RECV). For example, this * could be one chunk in a chunked response. */ const string CLIENT_RECV_FRAGMENT = "crf" /** * Optionally logs progress of a (SERVER_SEND, WIRE_SEND). For example, this * could be one chunk in a chunked response. */ const string SERVER_SEND_FRAGMENT = "ssf" /** * Optionally logs progress of a (SERVER_RECV, WIRE_RECV). For example, this * could be one chunk in a chunked request. */ const string SERVER_RECV_FRAGMENT = "srf" #***** BinaryAnnotation.key ****** /** * The domain portion of the URL or host header. Ex. "mybucket.s3.amazonaws.com" * * Used to filter by host as opposed to ip address. */ const string HTTP_HOST = "http.host" /** * The HTTP method, or verb, such as "GET" or "POST". * * Used to filter against an http route. */ const string HTTP_METHOD = "http.method" /** * The absolute http path, without any query parameters. Ex. "/objects/abcd-ff" * * Used to filter against an http route, portably with zipkin v1. * * In zipkin v1, only equals filters are supported. Dropping query parameters makes the number * of distinct URIs less. For example, one can query for the same resource, regardless of signing * parameters encoded in the query line. This does not reduce cardinality to a HTTP single route. * For example, it is common to express a route as an http URI template like * "/resource/{resource_id}". In systems where only equals queries are available, searching for * http/path=/resource won't match if the actual request was /resource/abcd-ff. * * Historical note: This was commonly expressed as "http.uri" in zipkin, eventhough it was most * often just a path. */ const string HTTP_PATH = "http.path" /** * The entire URL, including the scheme, host and query parameters if available. Ex. * "https://mybucket.s3.amazonaws.com/objects/abcd-ff?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Algorithm=AWS4-HMAC-SHA256..." * * Combined with HTTP_METHOD, you can understand the fully-qualified request line. * * This is optional as it may include private data or be of considerable length. */ const string HTTP_URL = "http.url" /** * The HTTP status code, when not in 2xx range. Ex. "503" * * Used to filter for error status. */ const string HTTP_STATUS_CODE = "http.status_code" /** * The size of the non-empty HTTP request body, in bytes. Ex. "16384" * * Large uploads can exceed limits or contribute directly to latency. */ const string HTTP_REQUEST_SIZE = "http.request.size" /** * The size of the non-empty HTTP response body, in bytes. Ex. "16384" * * Large downloads can exceed limits or contribute directly to latency. */ const string HTTP_RESPONSE_SIZE = "http.response.size" /** * The value of "lc" is the component or namespace of a local span. * * BinaryAnnotation.host adds service context needed to support queries. * * Local Component("lc") supports three key features: flagging, query by * service and filtering Span.name by namespace. * * While structurally the same, local spans are fundamentally different than * RPC spans in how they should be interpreted. For example, zipkin v1 tools * center on RPC latency and service graphs. Root local-spans are neither * indicative of critical path RPC latency, nor have impact on the shape of a * service graph. By flagging with "lc", tools can special-case local spans. * * Zipkin v1 Spans are unqueryable unless they can be indexed by service name. * The only path to a service name is by (Binary)?Annotation.host.serviceName. * By logging "lc", a local span can be queried even if no other annotations * are logged. * * The value of "lc" is the namespace of Span.name. For example, it might be * "finatra2", for a span named "bootstrap". "lc" allows you to resolves * conflicts for the same Span.name, for example "finatra/bootstrap" vs * "finch/bootstrap". Using local component, you'd search for spans named * "bootstrap" where "lc=finch" */ const string LOCAL_COMPONENT = "lc" #***** Annotation.value or BinaryAnnotation.key ****** /** * When an annotation value, this indicates when an error occurred. When a * binary annotation key, the value is a human readable message associated * with an error. * * Due to transient errors, an ERROR annotation should not be interpreted * as a span failure, even the annotation might explain additional latency. * Instrumentation should add the ERROR binary annotation when the operation * failed and couldn't be recovered. * * Here's an example: A span has an ERROR annotation, added when a WIRE_SEND * failed. Another WIRE_SEND succeeded, so there's no ERROR binary annotation * on the span because the overall operation succeeded. * * Note that RPC spans often include both client and server hosts: It is * possible that only one side perceived the error. */ const string ERROR = "error" #***** BinaryAnnotation.key where value = [1] and annotation_type = BOOL ****** /** * Indicates a client address ("ca") in a span. Most likely, there's only one. * Multiple addresses are possible when a client changes its ip or port within * a span. */ const string CLIENT_ADDR = "ca" /** * Indicates a server address ("sa") in a span. Most likely, there's only one. * Multiple addresses are possible when a client is redirected, or fails to a * different server ip or port. */ const string SERVER_ADDR = "sa" /** * Indicates the network context of a service recording an annotation with two * exceptions. * * When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR, * the endpoint indicates the source or destination of an RPC. This exception * allows zipkin to display network context of uninstrumented services, or * clients such as web browsers. */ struct Endpoint { /** * IPv4 host address packed into 4 bytes. * * Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4 */ 1: i32 ipv4 /** * IPv4 port or 0, if unknown. * * Note: this is to be treated as an unsigned integer, so watch for negatives. */ 2: i16 port /** * Classifier of a source or destination in lowercase, such as "zipkin-web". * * This is the primary parameter for trace lookup, so should be intuitive as * possible, for example, matching names in service discovery. * * Conventionally, when the service name isn't known, service_name = "unknown". * However, it is also permissible to set service_name = "" (empty string). * The difference in the latter usage is that the span will not be queryable * by service name unless more information is added to the span with non-empty * service name, e.g. an additional annotation from the server. * * Particularly clients may not have a reliable service name at ingest. One * approach is to set service_name to "" at ingest, and later assign a * better label based on binary annotations, such as user agent. */ 3: string service_name /** * IPv6 host address packed into 16 bytes. Ex Inet6Address.getBytes() */ 4: optional binary ipv6 } /** * Associates an event that explains latency with a timestamp. * * Unlike log statements, annotations are often codes: for example "sr". */ struct Annotation { /** * Microseconds from epoch. * * This value should use the most precise value possible. For example, * gettimeofday or multiplying currentTimeMillis by 1000. */ 1: i64 timestamp /** * Usually a short tag indicating an event, like "sr" or "finagle.retry". */ 2: string value /** * The host that recorded the value, primarily for query by service name. */ 3: optional Endpoint host // don't reuse 4: optional i32 OBSOLETE_duration // how long did the operation take? microseconds } /** * A subset of thrift base types, except BYTES. */ enum AnnotationType { /** * Set to 0x01 when key is CLIENT_ADDR or SERVER_ADDR */ BOOL, /** * No encoding, or type is unknown. */ BYTES, I16, I32, I64, DOUBLE, /** * the only type zipkin v1 supports search against. */ STRING } /** * Binary annotations are tags applied to a Span to give it context. For * example, a binary annotation of HTTP_PATH ("http.path") could the path * to a resource in a RPC call. * * Binary annotations of type STRING are always queryable, though more a * historical implementation detail than a structural concern. * * Binary annotations can repeat, and vary on the host. Similar to Annotation, * the host indicates who logged the event. This allows you to tell the * difference between the client and server side of the same key. For example, * the key "http.path" might be different on the client and server side due to * rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field, * you can see the different points of view, which often help in debugging. */ struct BinaryAnnotation { /** * Name used to lookup spans, such as "http.path" or "finagle.version". */ 1: string key, /** * Serialized thrift bytes, in TBinaryProtocol format. * * For legacy reasons, byte order is big-endian. See THRIFT-3217. */ 2: binary value, /** * The thrift type of value, most often STRING. * * annotation_type shouldn't vary for the same key. */ 3: AnnotationType annotation_type, /** * The host that recorded value, allowing query by service name or address. * * There are two exceptions: when key is "ca" or "sa", this is the source or * destination of an RPC. This exception allows zipkin to display network * context of uninstrumented services, such as browsers or databases. */ 4: optional Endpoint host } /** * A trace is a series of spans (often RPC calls) which form a latency tree. * * Spans are usually created by instrumentation in RPC clients or servers, but * can also represent in-process activity. Annotations in spans are similar to * log statements, and are sometimes created directly by application developers * to indicate events of interest, such as a cache miss. * * The root span is where parent_id = Nil; it usually has the longest duration * in the trace. * * Span identifiers are packed into i64s, but should be treated opaquely. * String encoding is fixed-width lower-hex, to avoid signed interpretation. */ struct Span { /** * Unique 8-byte identifier for a trace, set on all spans within it. */ 1: i64 trace_id /** * Span name in lowercase, rpc method for example. Conventionally, when the * span name isn't known, name = "unknown". */ 3: string name, /** * Unique 8-byte identifier of this span within a trace. A span is uniquely * identified in storage by (trace_id, id). */ 4: i64 id, /** * The parent's Span.id; absent if this the root span in a trace. */ 5: optional i64 parent_id, /** * Associates events that explain latency with a timestamp. Unlike log * statements, annotations are often codes: for example SERVER_RECV("sr"). * Annotations are sorted ascending by timestamp. */ 6: list annotations, /** * Tags a span with context, usually to support query or aggregation. For * example, a binary annotation key could be "http.path". */ 8: list binary_annotations /** * True is a request to store this span even if it overrides sampling policy. */ 9: optional bool debug = 0 /** * Epoch microseconds of the start of this span, absent if this an incomplete * span. * * This value should be set directly by instrumentation, using the most * precise value possible. For example, gettimeofday or syncing nanoTime * against a tick of currentTimeMillis. * * For compatibilty with instrumentation that precede this field, collectors * or span stores can derive this via Annotation.timestamp. * For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp. * * Timestamp is nullable for input only. Spans without a timestamp cannot be * presented in a timeline: Span stores should not output spans missing a * timestamp. * * There are two known edge-cases where this could be absent: both cases * exist when a collector receives a span in parts and a binary annotation * precedes a timestamp. This is possible when.. * - The span is in-flight (ex not yet received a timestamp) * - The span's start event was lost */ 10: optional i64 timestamp, /** * Measurement in microseconds of the critical path, if known. Durations of * less than one microsecond must be rounded up to 1 microsecond. * * This value should be set directly, as opposed to implicitly via annotation * timestamps. Doing so encourages precision decoupled from problems of * clocks, such as skew or NTP updates causing time to move backwards. * * For compatibility with instrumentation that precede this field, collectors * or span stores can derive this by subtracting Annotation.timestamp. * For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp. * * If this field is persisted as unset, zipkin will continue to work, except * duration query support will be implementation-specific. Similarly, setting * this field non-atomically is implementation-specific. * * This field is i64 vs i32 to support spans longer than 35 minutes. */ 11: optional i64 duration /** * Optional unique 8-byte additional identifier for a trace. If non zero, this * means the trace uses 128 bit traceIds instead of 64 bit. */ 12: optional i64 trace_id_high } py_zipkin-0.9.0/py_zipkin/logging_helper.py0000644000174700017470000003255313137710705020365 0ustar debiandebian# -*- coding: utf-8 -*- import logging import time from collections import defaultdict from py_zipkin.exception import ZipkinError from py_zipkin.thrift import annotation_list_builder from py_zipkin.thrift import binary_annotation_list_builder from py_zipkin.thrift import copy_endpoint_with_new_service_name from py_zipkin.thrift import create_span from py_zipkin.thrift import thrift_objs_in_bytes from py_zipkin.util import generate_random_64bit_string try: # Python 2.7+ from logging import NullHandler except ImportError: # pragma: no cover class NullHandler(logging.Handler): def emit(self, record): pass null_handler = NullHandler() zipkin_logger = logging.getLogger('py_zipkin.logger') zipkin_logger.addHandler(null_handler) zipkin_logger.setLevel(logging.DEBUG) LOGGING_END_KEY = 'py_zipkin.logging_end' class ZipkinLoggingContext(object): """A logging context specific to a Zipkin trace. If the trace is sampled, the logging context sends serialized Zipkin spans to a transport_handler. The logging context sends root "server" or "client" span, as well as all local child spans collected within this context. This class should only be used by the main `zipkin_span` entrypoint. """ def __init__( self, zipkin_attrs, thrift_endpoint, log_handler, span_name, transport_handler, report_root_timestamp, binary_annotations=None, add_logging_annotation=False, client_context=False, max_span_batch_size=None, ): self.zipkin_attrs = zipkin_attrs self.thrift_endpoint = thrift_endpoint self.log_handler = log_handler self.span_name = span_name self.transport_handler = transport_handler self.response_status_code = 0 self.report_root_timestamp = report_root_timestamp self.binary_annotations_dict = binary_annotations or {} self.sa_binary_annotations = [] self.add_logging_annotation = add_logging_annotation self.client_context = client_context self.max_span_batch_size = max_span_batch_size def start(self): """Actions to be taken before request is handled. 1) Attach `zipkin_logger` to :class:`ZipkinLoggerHandler` object. 2) Record the start timestamp. """ zipkin_logger.removeHandler(null_handler) zipkin_logger.addHandler(self.log_handler) self.start_timestamp = time.time() return self def stop(self): """Actions to be taken post request handling. 1) Log the service annotations to scribe. 2) Detach `zipkin_logger` handler. """ self.log_spans() zipkin_logger.removeHandler(self.log_handler) zipkin_logger.addHandler(null_handler) 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`) or the client ('cs' and 'cr') annotations. """ if not self.zipkin_attrs.is_sampled: return span_sender = ZipkinBatchSender(self.transport_handler, self.max_span_batch_size) with span_sender: end_timestamp = time.time() # Collect additional annotations from the logging handler annotations_by_span_id = defaultdict(dict) binary_annotations_by_span_id = defaultdict(dict) for msg in self.log_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.log_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]) timestamp, duration = get_local_span_timestamp_and_duration( annotations ) # Create serializable thrift objects of annotations thrift_annotations = annotation_list_builder( annotations, endpoint ) thrift_binary_annotations = binary_annotation_list_builder( binary_annotations, endpoint ) if span.get('sa_binary_annotations'): thrift_binary_annotations += span['sa_binary_annotations'] span_sender.add_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, timestamp_s=timestamp, duration_s=duration, ) extra_annotations = annotations_by_span_id[ self.zipkin_attrs.span_id] extra_binary_annotations = binary_annotations_by_span_id[ self.zipkin_attrs.span_id ] k1, k2 = ('sr', 'ss') if self.client_context: k1, k2 = ('cs', 'cr') annotations = {k1: self.start_timestamp, k2: end_timestamp} annotations.update(extra_annotations) if self.add_logging_annotation: annotations[LOGGING_END_KEY] = time.time() 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, ) if self.sa_binary_annotations: thrift_binary_annotations += self.sa_binary_annotations if self.report_root_timestamp: timestamp = self.start_timestamp duration = end_timestamp - self.start_timestamp else: timestamp = duration = None span_sender.add_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=self.span_name, annotations=thrift_annotations, binary_annotations=thrift_binary_annotations, timestamp_s=timestamp, duration_s=duration, ) def get_local_span_timestamp_and_duration(annotations): if 'cs' in annotations and 'cr' in annotations: return annotations['cs'], annotations['cr'] - annotations['cs'] elif 'sr' in annotations and 'ss' in annotations: return annotations['sr'], annotations['ss'] - annotations['sr'] return None, None class ZipkinLoggerHandler(logging.StreamHandler, object): """Logger Handler to log span annotations or additional client spans to scribe. To connect to the handler, logger name must be 'py_zipkin.logger'. :param zipkin_attrs: ZipkinAttrs namedtuple object """ def __init__(self, zipkin_attrs): super(ZipkinLoggerHandler, self).__init__() # If parent_span_id is set, the application is in a logging context # where each additional client span logged has this span as its parent. # This is to allow logging of hierarchies of spans instead of just # single client spans. See the SpanContext class. self.parent_span_id = None self.zipkin_attrs = zipkin_attrs self.client_spans = [] self.extra_annotations = [] def store_local_span( self, span_name, service_name, annotations, binary_annotations, sa_binary_annotations=None, span_id=None, ): """Convenience method for storing a local child span (a zipkin_span inside other zipkin_spans) to be logged when the outermost zipkin_span exits. """ self.client_spans.append({ 'span_name': span_name, 'service_name': service_name, 'parent_span_id': self.parent_span_id, 'span_id': span_id, 'annotations': annotations, 'binary_annotations': binary_annotations, 'sa_binary_annotations': sa_binary_annotations, }) def emit(self, record): """Handle each record message. This function is called whenever zipkin_logger.debug() is called. :param record: object containing the `msg` object. Structure of record.msg should be the following: :: { "annotations": { "cs": ts1, "cr": ts2, }, "binary_annotations": { "http.uri": "/foo/bar", }, "name": "foo_span", "service_name": "myService", } Keys: - annotations: str -> timestamp annotations - binary_annotations: str -> str binary annotations (One of either annotations or binary_annotations is required) - name: str of new span name; only used if service-name is also specified. - service_name: str of new client span's service name. If service_name is specified, this log msg is considered to represent a new client span. If service_name is omitted, this is considered additional annotation for the currently active "parent span" (either the server span or the parent client span inside a SpanContext). """ if not self.zipkin_attrs.is_sampled: return span_name = record.msg.get('name', 'span') annotations = record.msg.get('annotations', {}) binary_annotations = record.msg.get('binary_annotations', {}) if not annotations and not binary_annotations: raise ZipkinError( "At least one of annotation/binary annotation has" " to be provided for {0} span".format(span_name) ) service_name = record.msg.get('service_name', None) # Presence of service_name means this is to be a new local span. if service_name is not None: self.store_local_span( span_name=span_name, service_name=service_name, annotations=annotations, binary_annotations=binary_annotations, ) else: self.extra_annotations.append({ 'annotations': annotations, 'binary_annotations': binary_annotations, 'parent_span_id': self.parent_span_id, }) class ZipkinBatchSender(object): MAX_PORTION_SIZE = 100 def __init__(self, transport_handler, max_portion_size=None): self.transport_handler = transport_handler self.max_portion_size = max_portion_size or self.MAX_PORTION_SIZE def __enter__(self): self.queue = [] return self def __exit__(self, _exc_type, _exc_value, _exc_traceback): if any((_exc_type, _exc_value, _exc_traceback)): error = '{0}: {1}'.format(_exc_type.__name__, _exc_value) raise ZipkinError(error) else: self.flush() def add_span( self, span_id, parent_span_id, trace_id, span_name, annotations, binary_annotations, timestamp_s, duration_s, ): thrift_span = create_span( span_id, parent_span_id, trace_id, span_name, annotations, binary_annotations, timestamp_s, duration_s, ) self.queue.append(thrift_span) if len(self.queue) >= self.max_portion_size: self.flush() def flush(self): if self.transport_handler and len(self.queue) > 0: message = thrift_objs_in_bytes(self.queue) self.transport_handler(message) self.queue = [] py_zipkin-0.9.0/py_zipkin/util.py0000644000174700017470000000322613113130755016343 0ustar debiandebian# -*- coding: utf-8 -*- import codecs import os import struct def generate_random_64bit_string(): """Returns a 64 bit UTF-8 encoded string. In the interests of simplicity, this is always cast to a `str` instead of (in py2 land) a unicode string. Certain clients (I'm looking at you, Twisted) don't enjoy unicode headers. :returns: random 16-character string """ return str(codecs.encode(os.urandom(8), 'hex_codec').decode('utf-8')) def generate_random_128bit_string(): """Returns a 128 bit UTF-8 encoded string. Follows the same conventions as generate_random_64bit_string(). :returns: random 32-character string """ return str(codecs.encode(os.urandom(16), 'hex_codec').decode('utf-8')) def unsigned_hex_to_signed_int(hex_string): """Converts a 64-bit hex string to a signed int value. This is due to the fact that Apache Thrift only has signed values. Examples: '17133d482ba4f605' => 1662740067609015813 'b6dbb1c2b362bf51' => -5270423489115668655 :param hex_string: the string representation of a zipkin ID :returns: signed int representation """ return struct.unpack('q', struct.pack('Q', int(hex_string, 16)))[0] def signed_int_to_unsigned_hex(signed_int): """Converts a signed int value to a 64-bit hex string. Examples: 1662740067609015813 => '17133d482ba4f605' -5270423489115668655 => 'b6dbb1c2b362bf51' :param signed_int: an int to convert :returns: unsigned hex string """ hex_string = hex(struct.unpack('Q', struct.pack('q', signed_int))[0])[2:] if hex_string.endswith('L'): return hex_string[:-1] return hex_string py_zipkin-0.9.0/MANIFEST.in0000644000174700017470000000000012770300472014524 0ustar debiandebianpy_zipkin-0.9.0/py_zipkin.egg-info/0000755000174700017470000000000013140416534016505 5ustar debiandebianpy_zipkin-0.9.0/py_zipkin.egg-info/SOURCES.txt0000644000174700017470000000061313140416534020371 0ustar debiandebianMANIFEST.in setup.cfg setup.py py_zipkin/__init__.py py_zipkin/exception.py py_zipkin/logging_helper.py py_zipkin/thread_local.py py_zipkin/util.py py_zipkin/zipkin.py py_zipkin.egg-info/PKG-INFO py_zipkin.egg-info/SOURCES.txt py_zipkin.egg-info/dependency_links.txt py_zipkin.egg-info/requires.txt py_zipkin.egg-info/top_level.txt py_zipkin/thrift/__init__.py py_zipkin/thrift/zipkinCore.thriftpy_zipkin-0.9.0/py_zipkin.egg-info/PKG-INFO0000644000174700017470000000137513140416534017610 0ustar debiandebianMetadata-Version: 1.1 Name: py-zipkin Version: 0.9.0 Summary: Library for using Zipkin in Python. Home-page: https://github.com/Yelp/py_zipkin Author: Yelp, Inc. Author-email: opensource+py-zipkin@yelp.com License: Copyright Yelp 2017 Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Provides: py_zipkin py_zipkin-0.9.0/py_zipkin.egg-info/top_level.txt0000644000174700017470000000001213140416534021230 0ustar debiandebianpy_zipkin py_zipkin-0.9.0/py_zipkin.egg-info/dependency_links.txt0000644000174700017470000000000113140416534022553 0ustar debiandebian py_zipkin-0.9.0/py_zipkin.egg-info/requires.txt0000644000174700017470000000001513140416534021101 0ustar debiandebiansix thriftpy