diff --git a/README.md b/README.md index 2761648..86257f9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,19 @@ All modules define their logger via `logging.getLogger(__name__)` So in order to define specific logging format or level for this library use `getLogger('haystack')` or configure the root logger. +### B3 propagation +B3 propagation is supported by enabling the flag use_b3_propagation in HaystackTracer +```python +import opentracing +from haystack import HaystackAgentRecorder +from haystack import HaystackTracer +use_b3_propagation = True +tracer = HaystackTracer("a_service", HaystackAgentRecorder(), use_b3_propagation=use_b3_propagation) +opentracing.set_global_tracer(tracer) +``` +In order to set the appropriate headers with "X-B3-" format, please see above section "Custom propagation headers" for +more information. + ## How to configure build environment Create a python3 virtual environment, activate it and then `make bootstrap` diff --git a/haystack/id_generator.py b/haystack/id_generator.py new file mode 100644 index 0000000..bf0c6d2 --- /dev/null +++ b/haystack/id_generator.py @@ -0,0 +1,30 @@ +import uuid + + +class IdGenerator: + def __init__(self): + pass + + def generate(self): + """ + Generates a UUID + """ + return format(uuid.uuid4()) + + def generate_trace_id(self): + """" + Generates a b3 format compatible trace id + """ + return self.__next_long() + + def generate_span_id(self): + """" + Generates a b3 format compatible span id + """ + return self.__next_long() + + def __next_long(self): + random_number = uuid.uuid4() + while random_number.int == 0: + random_number = uuid.uuid4() + return random_number.hex diff --git a/haystack/tracer.py b/haystack/tracer.py index 56b1ab5..9224ea8 100644 --- a/haystack/tracer.py +++ b/haystack/tracer.py @@ -1,7 +1,8 @@ import time -import uuid from opentracing import Format, Tracer, UnsupportedFormatException from opentracing.scope_managers import ThreadLocalScopeManager + +from .id_generator import IdGenerator from .text_propagator import TextPropagator from .span import Span, SpanContext @@ -13,7 +14,8 @@ def __init__(self, recorder, scope_manager=None, common_tags=None, - use_shared_spans=False): + use_shared_spans=False, + use_b3_propagation=False): """ Initialize a Haystack Tracer instance. :param service_name: The service name to which all spans will belong. @@ -26,6 +28,8 @@ def __init__(self, :param use_shared_spans: A boolean indicating whether or not to use shared spans. This is when client/server spans share the same span id. Default is to use unique span ids. + :param use_b3_propagation: A boolean indicating whether or not tu use + 128 bit hex char ids instead of UUIDs. """ scope_manager = ThreadLocalScopeManager() if scope_manager is None \ @@ -36,6 +40,7 @@ def __init__(self, self.service_name = service_name self.recorder = recorder self.use_shared_spans = use_shared_spans + self.use_b3_propagation = use_b3_propagation self.register_propagator(Format.TEXT_MAP, TextPropagator()) self.register_propagator(Format.HTTP_HEADERS, TextPropagator()) @@ -90,7 +95,14 @@ def start_span(self, if scope is not None: parent_ctx = scope.span.context - new_ctx = SpanContext(span_id=format(uuid.uuid4())) + id_generator = IdGenerator() + generated_span_id = id_generator.generate() + generated_trace_id = id_generator.generate() + if self.use_b3_propagation is not None and self.use_b3_propagation: + generated_span_id = id_generator.generate_span_id() + generated_trace_id = id_generator.generate_trace_id() + + new_ctx = SpanContext(span_id=generated_span_id) if parent_ctx is not None: new_ctx.trace_id = parent_ctx.trace_id if parent_ctx.baggage is not None: @@ -101,7 +113,7 @@ def start_span(self, else: new_ctx.parent_id = parent_ctx.span_id else: - new_ctx.trace_id = format(uuid.uuid4()) + new_ctx.trace_id = generated_trace_id # Set common tags if self._common_tags: diff --git a/tests/unit/test_tracer.py b/tests/unit/test_tracer.py index 96ef1d4..91afe22 100644 --- a/tests/unit/test_tracer.py +++ b/tests/unit/test_tracer.py @@ -59,6 +59,22 @@ def test_shared_spans_are_created_when_enabled(self): self.assertEqual(span.context.span_id, span_id) self.assertEqual(span.context.parent_id, parent_id) + def test_b3_format_ids_are_used_when_enabled(self): + tracer = HaystackTracer("any_service", NoopRecorder, use_b3_propagation=True) + span = tracer.start_span("any_operation") + + self.assertTrue("-" not in span.context.trace_id) + self.assertTrue(len(span.context.trace_id) == 32) + self.assertTrue("-" not in span.context.span_id) + self.assertTrue(len(span.context.span_id) == 32) + + def test_b3_format_ids_are_not_used_when_disabled(self): + tracer = HaystackTracer("any_service", NoopRecorder, use_b3_propagation=False) + span = tracer.start_span("any_operation") + + self.assertTrue("-" in span.context.trace_id) + self.assertTrue("-" in span.context.span_id) + if __name__ == "__main__": unittest.main()