def __init__( self, service_name, span_name='span', zipkin_attrs=None, transport_handler=None, annotations=None, binary_annotations=None, port=0, sample_rate=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 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 """ self.service_name = service_name self.span_name = span_name self.zipkin_attrs = zipkin_attrs self.transport_handler = transport_handler self.annotations = annotations or {} self.binary_annotations = binary_annotations or {} self.port = port self.logging_context = None self.sample_rate = sample_rate # 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')
def __init__( self, service_name, span_name='span', zipkin_attrs=None, transport_handler=None, annotations=None, binary_annotations=None, port=0, sample_rate=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 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: Custom sampling rate (between 100.0 and 0.0) if this is the root of the trace :type sample_rate: float """ self.service_name = service_name self.span_name = span_name self.zipkin_attrs = zipkin_attrs self.transport_handler = transport_handler self.annotations = annotations or {} self.binary_annotations = binary_annotations or {} self.port = port self.logging_context = None self.sample_rate = sample_rate # 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')
def detect_span_version_and_encoding(message): """Returns the span type and encoding for the message provided. The logic in this function is a Python port of https://github.com/openzipkin/zipkin/blob/master/zipkin/src/main/java/zipkin/internal/DetectingSpanDecoder.java :param message: span to perform operations on. :type message: byte array :returns: span encoding. :rtype: Encoding """ # In case message is sent in as non-bytearray format, # safeguard convert to bytearray before handling if isinstance(message, six.string_types): # Even six.b is not enough to handle the py2/3 difference since # it uses latin-1 as default encoding and not utf-8. if six.PY2: message = six.b(message) # pragma: no cover else: message = message.encode("utf-8") # pragma: no cover if len(message) < 2: raise ZipkinError("Invalid span format. Message too short.") # Check for binary format if six.byte2int(message) <= 16: if six.byte2int(message) == 10 and six.byte2int(message[1:2]) != 0: return Encoding.V2_PROTO3 return Encoding.V1_THRIFT str_msg = message.decode("utf-8") # JSON case for list of spans if str_msg[0] == "[": span_list = json.loads(str_msg) if len(span_list) > 0: # Assumption: All spans in a list are the same version # Logic: Search for identifying fields in all spans, if any span can # be strictly identified to a version, return that version. # Otherwise, if no spans could be strictly identified, default to V2. for span in span_list: if any(word in span for word in _V2_ATTRIBUTES): return Encoding.V2_JSON elif "binaryAnnotations" in span or ( "annotations" in span and "endpoint" in span["annotations"] ): return Encoding.V1_JSON return Encoding.V2_JSON raise ZipkinError("Unknown or unsupported span encoding")
def tween(request): settings = request.registry.settings # Creates zipkin_attrs and attaches a zipkin_trace_id attr to the request if 'zipkin.create_zipkin_attr' in settings: zipkin_attrs = settings['zipkin.create_zipkin_attr'](request) else: zipkin_attrs = create_zipkin_attr(request) if 'zipkin.transport_handler' in settings: transport_handler = settings['zipkin.transport_handler'] stream_name = settings.get('zipkin.stream_name', 'zipkin') transport_handler = functools.partial(transport_handler, stream_name) else: raise ZipkinError( "`zipkin.transport_handler` is a required config property, which" " is missing. It is a callback method which takes a log stream" " and a message as params and logs the message via scribe/kafka." ) service_name = request.registry.settings.get('service_name', 'unknown') span_name = '{0} {1}'.format(request.method, request.path) with zipkin_span( service_name=service_name, span_name=span_name, zipkin_attrs=zipkin_attrs, transport_handler=transport_handler, port=request.server_port, ) as zipkin_context: response = handler(request) zipkin_context.update_binary_annotations_for_root_span( get_binary_annotations(request, response), ) return response
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, sa_endpoint=None, ) else: self.extra_annotations.append({ 'annotations': annotations, 'binary_annotations': binary_annotations, 'parent_span_id': self.parent_span_id, })
def encode_span(self, span): """Encodes a single span to protobuf.""" if not protobuf.installed(): raise ZipkinError( "protobuf encoding requires installing the protobuf's extra " "requirements. Use py-zipkin[protobuf] in your requirements.txt." ) pb_span = protobuf.create_protobuf_span(span) return protobuf.encode_pb_list([pb_span])
def update_binary_annotations_for_root_span(self, extra_annotations): """Updates the binary annotations for the root span of the trace. If this trace is not being sampled then this is a no-op. """ if not self.zipkin_attrs.is_sampled: return if not self.logging_context: raise ZipkinError('No logging context available') self.logging_context.binary_annotations_dict.update(extra_annotations)
def __exit__(self, _exc_type, _exc_value, _exc_traceback): if any((_exc_type, _exc_value, _exc_traceback)): filename = os.path.split(_exc_traceback.tb_frame.f_code.co_filename)[1] error = '({0}:{1}) {2}: {3}'.format( filename, _exc_traceback.tb_lineno, _exc_type.__name__, _exc_value, ) raise ZipkinError(error) else: self.flush()
def get_decoder(encoding): """Creates encoder object for the given encoding. :param encoding: desired output encoding protocol :type encoding: Encoding :return: corresponding IEncoder object :rtype: IEncoder """ if encoding == Encoding.V1_THRIFT: return _V1ThriftDecoder() if encoding == Encoding.V1_JSON: raise NotImplementedError("{} decoding not yet implemented".format(encoding)) if encoding == Encoding.V2_JSON: raise NotImplementedError("{} decoding not yet implemented".format(encoding)) raise ZipkinError("Unknown encoding: {}".format(encoding))
def get_encoder(encoding): """Creates encoder object for the given encoding. :param encoding: desired output encoding protocol. :type encoding: Encoding :return: corresponding IEncoder object :rtype: IEncoder """ if encoding == Encoding.V1_THRIFT: return _V1ThriftEncoder() if encoding == Encoding.V1_JSON: return _V1JSONEncoder() if encoding == Encoding.V2_JSON: return _V2JSONEncoder() raise ZipkinError('Unknown encoding: {}'.format(encoding))
def _get_settings_from_request(request): """Extracts Zipkin attributes and configuration from request attributes. See the `zipkin_span` context in py-zipkin for more detaied information on all the settings. Here are the supported Pyramid registry settings: zipkin.create_zipkin_attr: allows the service to override the creation of Zipkin attributes. For example, if you want to deterministically calculate trace ID from some service-specific attributes. zipkin.transport_handler: how py-zipkin will log the spans it generates. zipkin.stream_name: an additional parameter to be used as the first arg to the transport_handler function. A good example is a Kafka topic. zipkin.add_logging_annotation: if true, the outermost span in this service will have an annotation set when py-zipkin begins its logging. zipkin.report_root_timestamp: if true, the outermost span in this service will set its timestamp and duration attributes. Use this only if this service is not going to have a corresponding client span. See https://github.com/Yelp/pyramid_zipkin/issues/68 zipkin.firehose_handler: [EXPERIMENTAL] this enables "firehose tracing", which will log 100% of the spans to this handler, regardless of sampling decision. This is experimental and may change or be removed at any time without warning. zipkin.use_pattern_as_span_name: if true, we'll use the pyramid route pattern as span name. If false (default) we'll keep using the raw url path. """ settings = request.registry.settings # Creates zipkin_attrs and attaches a zipkin_trace_id attr to the request if 'zipkin.create_zipkin_attr' in settings: zipkin_attrs = settings['zipkin.create_zipkin_attr'](request) else: zipkin_attrs = create_zipkin_attr(request) if 'zipkin.transport_handler' in settings: transport_handler = settings['zipkin.transport_handler'] if not isinstance(transport_handler, BaseTransportHandler): warnings.warn( 'Using a function as transport_handler is deprecated. ' 'Please extend py_zipkin.transport.BaseTransportHandler', DeprecationWarning, ) stream_name = settings.get('zipkin.stream_name', 'zipkin') transport_handler = functools.partial(transport_handler, stream_name) else: raise ZipkinError( "`zipkin.transport_handler` is a required config property, which" " is missing. Have a look at py_zipkin's docs for how to implement" " it: https://github.com/Yelp/py_zipkin#transport") context_stack = _getattr_path(request, settings.get('zipkin.request_context')) service_name = settings.get('service_name', 'unknown') span_name = '{0} {1}'.format(request.method, request.path) add_logging_annotation = settings.get( 'zipkin.add_logging_annotation', False, ) # If the incoming request doesn't have Zipkin headers, this request is # assumed to be the root span of a trace. There's also a configuration # override to allow services to write their own logic for reporting # timestamp/duration. if 'zipkin.report_root_timestamp' in settings: report_root_timestamp = settings['zipkin.report_root_timestamp'] else: report_root_timestamp = 'X-B3-TraceId' not in request.headers zipkin_host = settings.get('zipkin.host') zipkin_port = settings.get('zipkin.port', request.server_port) firehose_handler = settings.get('zipkin.firehose_handler') post_handler_hook = settings.get('zipkin.post_handler_hook') max_span_batch_size = settings.get('zipkin.max_span_batch_size') use_pattern_as_span_name = bool( settings.get('zipkin.use_pattern_as_span_name', False), ) encoding = settings.get('zipkin.encoding', Encoding.V1_THRIFT) return _ZipkinSettings( zipkin_attrs, transport_handler, service_name, span_name, add_logging_annotation, report_root_timestamp, zipkin_host, zipkin_port, context_stack, firehose_handler, post_handler_hook, max_span_batch_size, use_pattern_as_span_name, encoding=encoding, )
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=None, add_logging_annotation=False, report_root_timestamp=False, use_128bit_trace_id=False, host=None, context_stack=None, span_storage=None, firehose_handler=None, kind=None, timestamp=None, duration=None, encoding=Encoding.V2_JSON, _tracer=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: BaseTransportHandler :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. DEPRECATED: use kind instead. `include` will be removed in 1.0. :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 or ipv6 value of the host. The ip value isn't automatically determined in a docker environment. :type host: string :param context_stack: explicit context stack for storing zipkin attributes :type context_stack: object :param span_storage: explicit Span storage for storing zipkin spans before they're emitted. :type span_storage: py_zipkin.storage.SpanStorage :param firehose_handler: [EXPERIMENTAL] Similar to transport_handler, except that it will receive 100% of the spans regardless of trace sampling rate. :type firehose_handler: BaseTransportHandler :param kind: Span type (client, server, local, etc...). :type kind: Kind :param timestamp: Timestamp in seconds, defaults to `time.time()`. Set this if you want to use a custom timestamp. :type timestamp: float :param duration: Duration in seconds, defaults to the time spent in the context. Set this if you want to use a custom duration. :type duration: float :param encoding: Output encoding format, defaults to V1 thrift spans. :type encoding: Encoding :param _tracer: Current tracer object. This argument is passed in automatically when you create a zipkin_span from a Tracer. :type _tracer: Tracer """ self.service_name = service_name self.span_name = span_name self.zipkin_attrs_override = 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.sample_rate = sample_rate 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._context_stack = context_stack self._span_storage = span_storage self.firehose_handler = firehose_handler self.kind = self._generate_kind(kind, include) self.timestamp = timestamp self.duration = duration self.encoding = encoding self._tracer = _tracer self._is_local_root_span = False self.logging_context = None self.do_pop_attrs = False # Spans that log a 'cs' timestamp can additionally record a # 'sa' binary annotation that shows where the request is going. self.remote_endpoint = None self.zipkin_attrs = None # It used to be possible to override timestamp and duration by passing # in the cs/cr or sr/ss annotations. We want to keep backward compatibility # for now, so this logic overrides self.timestamp and self.duration in the # same way. # This doesn't fit well with v2 spans since those annotations are gone, so # we also log a deprecation warning. if "sr" in self.annotations and "ss" in self.annotations: self.duration = self.annotations["ss"] - self.annotations["sr"] self.timestamp = self.annotations["sr"] log.warning( "Manually setting 'sr'/'ss' annotations is deprecated. Please " "use the timestamp and duration parameters.") if "cr" in self.annotations and "cs" in self.annotations: self.duration = self.annotations["cr"] - self.annotations["cs"] self.timestamp = self.annotations["cs"] log.warning( "Manually setting 'cr'/'cs' annotations is deprecated. Please " "use the timestamp and duration parameters.") # Root spans have transport_handler and at least one of # zipkin_attrs_override or sample_rate. if self.zipkin_attrs_override or self.sample_rate is not None: # transport_handler is mandatory for root spans if self.transport_handler is None: raise ZipkinError( "Root spans require a transport handler to be given") self._is_local_root_span = True # If firehose_handler than this is a local root span. if self.firehose_handler: self._is_local_root_span = True 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 self._span_storage is not None and not isinstance( self._span_storage, storage.SpanStorage): raise ZipkinError( "span_storage should be an instance of py_zipkin.storage.SpanStorage" ) if self._span_storage is not None: log.warning( "span_storage is deprecated. Set local_storage instead.") self.get_tracer()._span_storage = self._span_storage if self._context_stack is not None: log.warning( "context_stack is deprecated. Set local_storage instead.") self.get_tracer()._context_stack = self._context_stack
def __init__( self, service_name, span_name='span', zipkin_attrs=None, transport_handler=None, annotations=None, binary_annotations=None, port=0, sample_rate=None, include=('client', 'server'), add_logging_annotation=False, ): """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 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 'start_logging' annotation when py_zipkin starts logging spans :type add_logging_annotation: boolean """ self.service_name = service_name self.span_name = span_name self.zipkin_attrs = zipkin_attrs self.transport_handler = transport_handler 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 # 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 __init__( self, trace_id, name, parent_id, span_id, timestamp, duration, annotations, tags, kind, local_endpoint=None, service_name=None, sa_endpoint=None, report_timestamp=True, ): """Creates a new SpanBuilder. :param trace_id: Trace id. :type trace_id: str :param name: Name of the span. :type name: str :param parent_id: Parent span id. :type parent_id: str :param span_id: Span id. :type span_id: str :param timestamp: start timestamp in seconds. :type timestamp: float :param duration: span duration in seconds. :type duration: float :param annotations: Optional dict of str -> timestamp annotations. :type annotations: dict :param tags: Optional dict of str -> str span tags. :type tags: dict :param kind: Span type (client, server, local, etc...) :type kind: Kind :param local_endpoint: The host that recorded this span. :type local_endpoint: Endpoint :param service_name: The name of the called service :type service_name: str :param sa_endpoint: Remote server in client spans. :type sa_endpoint: Endpoint :param report_timestamp: Whether the span should report timestamp and duration. :type report_timestamp: bool """ self.trace_id = trace_id self.name = name self.parent_id = parent_id self.span_id = span_id self.kind = kind self.timestamp = timestamp self.duration = duration self.annotations = annotations self.tags = tags self.local_endpoint = local_endpoint self.service_name = service_name self.sa_endpoint = sa_endpoint self.report_timestamp = report_timestamp if not isinstance(kind, Kind): raise ZipkinError( 'Invalid kind value {}. Must be of type Kind.'.format(kind))
def __init__(self, service_name, span_name='span', zipkin_attrs=None, transport_handler=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 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.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 # 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 _get_settings_from_request(request): """Extracts Zipkin attributes and configuration from request attributes. See the `zipkin_span` context in py-zipkin for more detaied information on all the settings. Here are the supported Pyramid registry settings: zipkin.create_zipkin_attr: allows the service to override the creation of Zipkin attributes. For example, if you want to deterministically calculate trace ID from some service-specific attributes. zipkin.transport_handler: how py-zipkin will log the spans it generates. zipkin.stream_name: an additional parameter to be used as the first arg to the transport_handler function. A good example is a Kafka topic. zipkin.add_logging_annotation: if true, the outermost span in this service will have an annotation set when py-zipkin begins its logging. zipkin.report_root_timestamp: if true, the outermost span in this service will set its timestamp and duration attributes. Use this only if this service is not going to have a corresponding client span. See https://github.com/Yelp/pyramid_zipkin/issues/68 zipkin.firehose_handler: [EXPERIMENTAL] this enables "firehose tracing", which will log 100% of the spans to this handler, regardless of sampling decision. This is experimental and may change or be removed at any time without warning. """ settings = request.registry.settings # Creates zipkin_attrs and attaches a zipkin_trace_id attr to the request if 'zipkin.create_zipkin_attr' in settings: zipkin_attrs = settings['zipkin.create_zipkin_attr'](request) else: zipkin_attrs = create_zipkin_attr(request) if 'zipkin.transport_handler' in settings: transport_handler = settings['zipkin.transport_handler'] stream_name = settings.get('zipkin.stream_name', 'zipkin') transport_handler = functools.partial(transport_handler, stream_name) else: raise ZipkinError( "`zipkin.transport_handler` is a required config property, which" " is missing. It is a callback method which takes a log stream" " and a message as params and logs the message via scribe/kafka.") context_stack = _getattr_path(request, settings.get('zipkin.request_context')) if context_stack is None: context_stack = py_zipkin.stack.ThreadLocalStack() service_name = settings.get('service_name', 'unknown') span_name = '{0} {1}'.format(request.method, request.path) add_logging_annotation = settings.get( 'zipkin.add_logging_annotation', False, ) # If the incoming request doesn't have Zipkin headers, this request is # assumed to be the root span of a trace. There's also a configuration # override to allow services to write their own logic for reporting # timestamp/duration. if 'zipkin.report_root_timestamp' in settings: report_root_timestamp = settings['zipkin.report_root_timestamp'] else: report_root_timestamp = 'X-B3-TraceId' not in request.headers zipkin_host = settings.get('zipkin.host') zipkin_port = settings.get('zipkin.port', request.server_port) firehose_handler = settings.get('zipkin.firehose_handler') max_span_batch_size = settings.get('zipkin.max_span_batch_size') return _ZipkinSettings( zipkin_attrs, transport_handler, service_name, span_name, add_logging_annotation, report_root_timestamp, zipkin_host, zipkin_port, context_stack, firehose_handler, max_span_batch_size, )
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 __init__( self, trace_id, name, parent_id, span_id, kind, timestamp, duration, local_endpoint=None, remote_endpoint=None, debug=False, shared=False, annotations=None, tags=None, ): """Creates a new Span. :param trace_id: Trace id. :type trace_id: str :param name: Name of the span. :type name: str :param parent_id: Parent span id. :type parent_id: str :param span_id: Span id. :type span_id: str :param kind: Span type (client, server, local, etc...) :type kind: Kind :param timestamp: start timestamp in seconds. :type timestamp: float :param duration: span duration in seconds. :type duration: float :param local_endpoint: the host that recorded this span. :type local_endpoint: Endpoint :param remote_endpoint: the remote service. :type remote_endpoint: Endpoint :param debug: True is a request to store this span even if it overrides sampling policy. :type debug: bool :param shared: True if we are contributing to a span started by another tracer (ex on a different host). :type shared: bool :param annotations: Optional dict of str -> timestamp annotations. :type annotations: dict :param tags: Optional dict of str -> str span tags. :type tags: dict """ self.trace_id = trace_id self.name = name self.parent_id = parent_id self.span_id = span_id self.kind = kind self.timestamp = timestamp self.duration = duration self.local_endpoint = local_endpoint self.remote_endpoint = remote_endpoint self.debug = debug self.shared = shared self.annotations = annotations or {} self.tags = tags or {} if not isinstance(kind, Kind): raise ZipkinError( "Invalid kind value {}. Must be of type Kind.".format(kind)) if local_endpoint and not isinstance(local_endpoint, Endpoint): raise ZipkinError( "Invalid local_endpoint value. Must be of type Endpoint.") if remote_endpoint and not isinstance(remote_endpoint, Endpoint): raise ZipkinError( "Invalid remote_endpoint value. Must be of type Endpoint.")