Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/instana/collector/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from opentelemetry.trace.span import format_span_id
from opentelemetry.trace import SpanKind

from instana.util.ids import hex_id
if TYPE_CHECKING:
from instana.span.base_span import BaseSpan

Expand All @@ -22,6 +23,7 @@ def format_span(
span.t = format_span_id(span.t)
span.s = format_span_id(span.s)
span.p = format_span_id(span.p) if span.p else None
span.lt = hex_id(span.lt) if hasattr(span, "lt") else None
if isinstance(span.k, SpanKind):
span.k = span.k.value if not span.k is SpanKind.INTERNAL else 3
spans.append(span)
Expand Down
22 changes: 17 additions & 5 deletions src/instana/propagators/base_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from instana.log import logger
from instana.span_context import SpanContext
from instana.util.ids import header_to_id, header_to_long_id, hex_id
from instana.util.ids import header_to_id, header_to_long_id, hex_id, internal_id, internal_id_limited, hex_id_limited
from instana.w3c_trace_context.traceparent import Traceparent
from instana.w3c_trace_context.tracestate import Tracestate

Expand Down Expand Up @@ -149,7 +149,7 @@ def _get_participating_trace_context(self, span_context: SpanContext):
if span_context.suppression:
return traceparent, tracestate

tracestate = self._ts.update_tracestate(tracestate, hex_id(span_context.trace_id), hex_id(span_context.span_id))
tracestate = self._ts.update_tracestate(tracestate, hex_id_limited(span_context.trace_id), hex_id(span_context.span_id))
return traceparent, tracestate

def __determine_span_context(
Expand Down Expand Up @@ -219,7 +219,7 @@ def __determine_span_context(
instana_ancestor = self._ts.get_instana_ancestor(tracestate)

if disable_traceparent == "":
ctx_trace_id = tp_trace_id
ctx_trace_id = hex_id_limited(tp_trace_id)
ctx_span_id = tp_parent_id
ctx_synthetic = synthetic
ctx_trace_parent = True
Expand All @@ -241,9 +241,21 @@ def __determine_span_context(
ctx_traceparent = traceparent
ctx_tracestate = tracestate

if ctx_trace_id:
if isinstance(ctx_trace_id, int):
# check if ctx_trace_id is a valid internal trace id
if (ctx_trace_id <= 2**64 - 1):
trace_id = ctx_trace_id
else:
trace_id = internal_id(hex_id_limited(ctx_trace_id))
else:
trace_id = internal_id(ctx_trace_id)
else:
trace_id = INVALID_TRACE_ID

return SpanContext(
trace_id=int(ctx_trace_id) if ctx_trace_id else INVALID_TRACE_ID,
span_id=int(ctx_span_id) if ctx_span_id else INVALID_SPAN_ID,
trace_id=trace_id,
span_id=internal_id_limited(ctx_span_id) if ctx_span_id else INVALID_SPAN_ID,
is_remote=False,
level=ctx_level,
synthetic=ctx_synthetic,
Expand Down
4 changes: 2 additions & 2 deletions src/instana/propagators/http_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from instana.log import logger
from instana.propagators.base_propagator import BasePropagator
from instana.util.ids import define_server_timing
from instana.util.ids import define_server_timing, hex_id_limited

from opentelemetry.trace.span import format_span_id

Expand Down Expand Up @@ -55,7 +55,7 @@ def inject_key_value(carrier, key, value):
if span_context.suppression:
return

inject_key_value(carrier, self.HEADER_KEY_T, format_span_id(trace_id))
inject_key_value(carrier, self.HEADER_KEY_T, hex_id_limited(trace_id))
inject_key_value(carrier, self.HEADER_KEY_S, format_span_id(span_id))
inject_key_value(
carrier, self.HEADER_KEY_SERVER_TIMING, define_server_timing(trace_id)
Expand Down
3 changes: 2 additions & 1 deletion src/instana/span/base_span.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from instana.log import logger
from instana.util import DictionaryOfStan
from instana.span.kind import ENTRY_SPANS

if TYPE_CHECKING:
from opentelemetry.trace import Span
Expand All @@ -31,7 +32,7 @@ def __init__(self, span: Type["Span"], source, **kwargs) -> None:
self.data = DictionaryOfStan()
self.stack = span.stack

if span.synthetic is True:
if span.synthetic is True and span.name in ENTRY_SPANS:
self.sy = span.synthetic

self.__dict__.update(kwargs)
Expand Down
23 changes: 7 additions & 16 deletions src/instana/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ def start_span(
if parent_context and not isinstance(parent_context, SpanContext):
raise TypeError("parent_context must be an Instana SpanContext or None.")

if parent_context and not parent_context.is_valid and not parent_context.suppression:
# We probably have an INVALID_SPAN_CONTEXT.
parent_context = None

span_context = self._create_span_context(parent_context)
span = InstanaSpan(
name,
Expand All @@ -138,9 +134,6 @@ def start_span(
# events: Sequence[Event] = None,
)

if parent_context is not None:
span.synthetic = parent_context.synthetic

if name in EXIT_SPANS:
self._add_stack(span)

Expand Down Expand Up @@ -230,17 +223,15 @@ def _create_span_context(self, parent_context: SpanContext) -> SpanContext:
is_remote=is_remote,
level=(parent_context.level if parent_context else 1),
synthetic=(parent_context.synthetic if parent_context else False),
trace_parent=(parent_context.trace_parent if parent_context else None),
instana_ancestor=(parent_context.instana_ancestor if parent_context else None),
long_trace_id=(parent_context.long_trace_id if parent_context else None),
correlation_type=(parent_context.correlation_type if parent_context else None),
correlation_id=(parent_context.correlation_id if parent_context else None),
traceparent=(parent_context.traceparent if parent_context else None),
tracestate=(parent_context.tracestate if parent_context else None),
)

if parent_context is not None:
span_context.long_trace_id = parent_context.long_trace_id
span_context.trace_parent = parent_context.trace_parent
span_context.instana_ancestor = parent_context.instana_ancestor
span_context.correlation_type = parent_context.correlation_type
span_context.correlation_id = parent_context.correlation_id
span_context.traceparent = parent_context.traceparent
span_context.tracestate = parent_context.tracestate

return span_context

def inject(
Expand Down
90 changes: 80 additions & 10 deletions src/instana/util/ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import random
from typing import Union

from opentelemetry.trace.span import _SPAN_ID_MAX_VALUE, INVALID_SPAN_ID
from opentelemetry.trace.span import _SPAN_ID_MAX_VALUE, INVALID_SPAN_ID, INVALID_TRACE_ID

_rnd = random.Random()
_current_pid = 0
Expand Down Expand Up @@ -42,7 +42,7 @@ def header_to_long_id(header: Union[bytes, str]) -> int:
header = header.decode('utf-8')

if not isinstance(header, str):
return INVALID_SPAN_ID
return INVALID_TRACE_ID

if header.isdecimal():
return header
Expand All @@ -54,7 +54,7 @@ def header_to_long_id(header: Union[bytes, str]) -> int:

return int(header, 16)
except ValueError:
return INVALID_SPAN_ID
return INVALID_TRACE_ID


def header_to_id(header: Union[bytes, str]) -> int:
Expand Down Expand Up @@ -92,14 +92,84 @@ def header_to_id(header: Union[bytes, str]) -> int:
def hex_id(id: Union[int, str]) -> str:
"""
Returns the hexadecimal representation of the given ID.
Left pad with zeros when the length is not equal to 16
"""

hex_id = hex(int(id))[2:]
if len(hex_id) < 16:
hex_id = hex_id.zfill(16)
return hex_id

try:
hex_id = hex(int(id))[2:]
length = len(hex_id)
# Left pad ID with zeros
if length < 16:
hex_id = hex_id.zfill(16)
elif length > 16 and length < 32:
hex_id = hex_id.zfill(32)
return hex_id
except ValueError: # Handles ValueError: invalid literal for int() with base 10:
return id

def hex_id_limited(id: Union[int, str]) -> str:
"""
Returns the hexadecimal representation of the given ID.
Limit longer IDs to 16 characters
"""
try:
hex_id = hex(int(id))[2:]
length = len(hex_id)
if length < 16:
# Left pad ID with zeros
hex_id = hex_id.zfill(16)
elif length > 16:
# Phase 0: Discard everything but the last 16byte
hex_id = hex_id[-16:]
return hex_id
except ValueError: # Handles ValueError: invalid literal for int() with base 10:
return id

def define_server_timing(trace_id: Union[int, str]) -> str:
# Note: The key `intid` is short for Instana Trace ID.
return f"intid;desc={hex_id(trace_id)}"
return f"intid;desc={hex_id_limited(trace_id)}"


def internal_id(id: Union[int, str]) -> int:
"""
Returns a valid id to be used internally. Handles both str and int types.
"""
if isinstance(id, int):
return id

if isinstance(id, str) and id.isdigit():
return int(id)

try:
if len(id) < 16:
# Left pad ID with zeros
id = id.zfill(16)

# hex string -> int
return int(id, 16)
except ValueError:
return INVALID_TRACE_ID

def internal_id_limited(id: Union[int, str]) -> int:
"""
Returns a valid id to be used internally. Handles both str and int types.
Note: Limits the hex string to 16 chars before conversion.
"""
if isinstance(id, int):
return id

if isinstance(id, str) and id.isdigit():
return int(id)

try:
length = len(id)
if length < 16:
# Left pad ID with zeros
id = id.zfill(16)
elif length > 16:
# Phase 0: Discard everything but the last 16byte
id = id[-16:]

# hex string -> int
return int(id, 16)
except ValueError:
return INVALID_SPAN_ID
11 changes: 8 additions & 3 deletions src/instana/w3c_trace_context/traceparent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
)

from instana.log import logger
from instana.util.ids import header_to_id
from instana.util.ids import header_to_id, header_to_long_id

# See https://www.w3.org/TR/trace-context-2/#trace-flags for details on the bitmasks.
SAMPLED_BITMASK = 0b1;
Expand Down Expand Up @@ -48,7 +48,7 @@ def get_traceparent_fields(traceparent: str) -> Tuple[Optional[str], Optional[in
try:
traceparent_properties = traceparent.split("-")
version = traceparent_properties[0]
trace_id = header_to_id(traceparent_properties[1])
trace_id = header_to_long_id(traceparent_properties[1])
parent_id = header_to_id(traceparent_properties[2])
flags = int(traceparent_properties[3], 16)
sampled_flag = (flags & SAMPLED_BITMASK) == SAMPLED_BITMASK
Expand Down Expand Up @@ -99,5 +99,10 @@ def update_traceparent(
flags = level & SAMPLED_BITMASK
flags = format(flags, "0>2x")

traceparent = f"{self.SPECIFICATION_VERSION}-{format_trace_id(trace_id)}-{format_span_id(parent_id)}-{flags}"
if isinstance(trace_id, str):
trace_id_out = trace_id
else:
trace_id_out = format_trace_id(trace_id)

traceparent = f"{self.SPECIFICATION_VERSION}-{trace_id_out}-{format_span_id(parent_id)}-{flags}"
return traceparent
7 changes: 6 additions & 1 deletion tests/frameworks/test_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ def test_get_request_with_suppression(self) -> None:
# Assert that there are no spans in the recorded list
assert spans == []

@unittest.skip("Handled when type of trace and span ids are modified to str")
def test_get_request_with_suppression_and_w3c(self) -> None:
"""Incoming Level 0 Plus W3C Trace Context Specification Headers"""
headers = {
'X-INSTANA-L':'0',
'traceparent': '00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01',
Expand All @@ -223,7 +223,12 @@ def test_get_request_with_suppression_and_w3c(self) -> None:
spans = self.recorder.queued_spans()

assert response.headers.get("X-INSTANA-L", None) == "0"
# if X-INSTANA-L=0 then both X-INSTANA-T and X-INSTANA-S should not be present
assert not response.headers.get("X-INSTANA-T", None)
assert not response.headers.get("X-INSTANA-S", None)

assert response.headers.get("traceparent", None) is not None
assert response.headers["traceparent"].startswith("00-0af7651916cd43dd8448eb211c80319c")
assert response.headers["traceparent"][-1] == "0"
# The tracestate has to be present
assert response.headers.get("tracestate", None) is not None
Expand Down
Loading