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
12 changes: 6 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,9 @@ workflows:
- python311
- python312
- python313
- py39cassandra
- py39couchbase
- py39gevent_starlette
# - py39cassandra
# - py39couchbase
# - py39gevent_starlette
- final_job:
requires:
- python38
Expand All @@ -341,9 +341,9 @@ workflows:
- python311
- python312
- python313
- py39cassandra
- py39couchbase
- py39gevent_starlette
# - py39cassandra
# - py39couchbase
# - py39gevent_starlette
filters:
branches:
only: master
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ log_cli = 1
log_cli_level = WARN
log_cli_format = %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %H:%M:%S
pythonpath = src
1 change: 1 addition & 0 deletions src/instana/collector/helpers/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# (c) Copyright Instana Inc. 2020

""" Collection helper for the Python runtime """
import gc
import importlib.metadata
import os
import platform
Expand Down
3 changes: 2 additions & 1 deletion src/instana/collector/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

from time import time
from typing import DefaultDict, Any

from instana.collector.base import BaseCollector
from instana.collector.helpers.runtime import RuntimeHelper
Expand Down Expand Up @@ -72,7 +73,7 @@ def should_send_snapshot_data(self) -> bool:
return True
return False

def prepare_payload(self) -> DictionaryOfStan:
def prepare_payload(self) -> DefaultDict[Any, Any]:
payload = DictionaryOfStan()
payload["spans"] = []
payload["profiles"] = []
Expand Down
40 changes: 33 additions & 7 deletions src/instana/propagators/base_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@
# (c) Copyright Instana Inc. 2020


import sys
import os
import typing

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

from opentelemetry.trace import (
INVALID_SPAN_ID,
INVALID_TRACE_ID,
NonRecordingSpan,
set_span_in_context,
)
from opentelemetry.context.context import Context

# The carrier can be a dict or a list.
# The carrier, typed here as CarrierT, can be a dict, a list, or a tuple.
# Using the trace header as an example, it can be in the following forms
# for extraction:
# X-Instana-T
Expand All @@ -23,6 +30,7 @@
#
# For injection, we only support the standard format:
# X-Instana-T
CarrierT = typing.TypeVar("CarrierT", typing.Dict, typing.List, typing.Tuple)


class BasePropagator(object):
Expand Down Expand Up @@ -154,7 +162,7 @@ def __determine_span_context(self, trace_id, span_id, level, synthetic, tracepar
correlation = False
disable_traceparent = os.environ.get("INSTANA_DISABLE_W3C_TRACE_CORRELATION", "")
instana_ancestor = None
ctx = SpanContext()
ctx = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
if level and "correlationType" in level:
trace_id, span_id = [None] * 2
correlation = True
Expand All @@ -166,7 +174,12 @@ def __determine_span_context(self, trace_id, span_id, level, synthetic, tracepar
ctx.correlation_type = None
ctx.correlation_id = None

if trace_id and span_id:
if (
trace_id
and span_id
and trace_id != INVALID_TRACE_ID
and span_id != INVALID_SPAN_ID
):
ctx.trace_id = trace_id[-16:] # only the last 16 chars
ctx.span_id = span_id[-16:] # only the last 16 chars
ctx.synthetic = synthetic is not None
Expand Down Expand Up @@ -290,9 +303,22 @@ def extract(self, carrier, disable_w3c_trace_context=False):
if traceparent:
traceparent = self._tp.validate(traceparent)

ctx = self.__determine_span_context(trace_id, span_id, level, synthetic, traceparent, tracestate,
disable_w3c_trace_context)

if trace_id is None:
trace_id = INVALID_TRACE_ID
if span_id is None:
span_id = INVALID_SPAN_ID

span_context = self.__determine_span_context(
trace_id,
span_id,
level,
synthetic,
traceparent,
tracestate,
disable_w3c_trace_context,
)
ctx = set_span_in_context(NonRecordingSpan(span_context), Context())
return ctx

except Exception:
logger.debug("extract error:", exc_info=True)
32 changes: 30 additions & 2 deletions src/instana/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
from instana.agent.host import HostAgent
from instana.agent.test import TestAgent
from instana.log import logger
from instana.propagators.base_propagator import CarrierT
from instana.propagators.binary_propagator import BinaryPropagator
from instana.propagators.exceptions import UnsupportedFormatException
from instana.propagators.format import Format
from instana.propagators.http_propagator import HTTPPropagator
from instana.propagators.text_propagator import TextPropagator
Expand Down Expand Up @@ -86,8 +88,9 @@ def __init__(
sampler: Sampler,
recorder: StanRecorder,
span_processor: Union[HostAgent, TestAgent],
propagators:
Mapping[str, Union[BinaryPropagator, HTTPPropagator, TextPropagator]],
propagators: Mapping[
str, Union[BinaryPropagator, HTTPPropagator, TextPropagator]
],
) -> None:
self._tracer_id = generate_id()
self._sampler = sampler
Expand Down Expand Up @@ -239,6 +242,31 @@ def _create_span_context(self, parent_context: SpanContext) -> SpanContext:

return span_context

def inject(
self,
span_context: SpanContext,
format: Union[Format.BINARY, Format.HTTP_HEADERS, Format.TEXT_MAP],
carrier: CarrierT,
disable_w3c_trace_context: bool = False,
) -> Optional[CarrierT]:
if format in self._propagators:
return self._propagators[format].inject(
span_context, carrier, disable_w3c_trace_context
)

raise UnsupportedFormatException()

def extract(
self,
format: Union[Format.BINARY, Format.HTTP_HEADERS, Format.TEXT_MAP],
carrier: CarrierT,
disable_w3c_trace_context: bool = False,
) -> Optional[Context]:
if format in self._propagators:
return self._propagators[format].extract(carrier, disable_w3c_trace_context)

raise UnsupportedFormatException()


# Used by __add_stack
re_tracer_frame = re.compile(r"/instana/.*\.py$")
Expand Down
25 changes: 15 additions & 10 deletions src/instana/util/traceutils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2021

from ..singletons import agent, tracer, async_tracer, tornado_tracer
from ..log import logger
from typing import Optional, Tuple

from instana.log import logger
from instana.singletons import agent, tracer, async_tracer, tornado_tracer
from instana.span import InstanaSpan, get_current_span
from instana.tracer import InstanaTracer


def extract_custom_headers(tracing_span, headers):
Expand All @@ -16,14 +20,12 @@ def extract_custom_headers(tracing_span, headers):
logger.debug("extract_custom_headers: ", exc_info=True)


def get_active_tracer():
def get_active_tracer() -> Optional[InstanaTracer]:
try:
if tracer.active_span:
# ToDo: Might have to add additional stuff when testing with async and tornado tracer
current_span = get_current_span()
if current_span and current_span.is_recording():
return tracer
elif async_tracer.active_span:
return async_tracer
elif tornado_tracer.active_span:
return tornado_tracer
else:
return None
except Exception:
Expand All @@ -32,10 +34,13 @@ def get_active_tracer():
return None


def get_tracer_tuple():
def get_tracer_tuple() -> (
Tuple[Optional[InstanaTracer], Optional[InstanaSpan], Optional[str]]
):
active_tracer = get_active_tracer()
current_span = get_current_span()
if active_tracer:
return (active_tracer, active_tracer.active_span, active_tracer.active_span.operation_name)
return (active_tracer, current_span, current_span.name)
elif agent.options.allow_exit_as_root:
return (tracer, None, None)
return (None, None, None)
Expand Down
24 changes: 17 additions & 7 deletions src/instana/w3c_trace_context/traceparent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ..log import logger
import re
from typing import Optional

# See https://www.w3.org/TR/trace-context-2/#trace-flags for details on the bitmasks.
SAMPLED_BITMASK = 0b1;
Expand Down Expand Up @@ -45,7 +46,13 @@ def get_traceparent_fields(traceparent):
logger.debug("Parsing the traceparent failed: {}".format(err))
return None, None, None, None

def update_traceparent(self, traceparent, in_trace_id, in_span_id, level):
def update_traceparent(
self,
traceparent: Optional[str],
in_trace_id: int,
in_span_id: int,
level: int,
) -> str:
"""
This method updates the traceparent header or generates one if there was no traceparent incoming header or it
was invalid
Expand All @@ -56,7 +63,11 @@ def update_traceparent(self, traceparent, in_trace_id, in_span_id, level):
:return: the updated traceparent header
"""
if traceparent is None: # modify the trace_id part only when it was not present at all
trace_id = in_trace_id.zfill(32)
trace_id = (
in_trace_id.zfill(32)
if not isinstance(in_trace_id, int)
else in_trace_id
)
else:
# - We do not need the incoming upstream parent span ID for the header we sent downstream.
# - We also do not care about the incoming version: The version field we sent downstream needs to match the
Expand All @@ -67,12 +78,11 @@ def update_traceparent(self, traceparent, in_trace_id, in_span_id, level):
# downstream.
_, trace_id, _, _ = self.get_traceparent_fields(traceparent)

parent_id = in_span_id.zfill(16)
parent_id = (
in_span_id.zfill(16) if not isinstance(in_span_id, int) else in_span_id
)
flags = level & SAMPLED_BITMASK
flags = format(flags, '0>2x')

traceparent = "{version}-{traceid}-{parentid}-{flags}".format(version=self.SPECIFICATION_VERSION,
traceid=trace_id,
parentid=parent_id,
flags=flags)
traceparent = f"{self.SPECIFICATION_VERSION}-{trace_id}-{parent_id}-{flags}"
return traceparent
6 changes: 4 additions & 2 deletions src/instana/w3c_trace_context/tracestate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ def update_tracestate(self, tracestate, in_trace_id, in_span_id):
:return: tracestate updated
"""
try:
span_id = in_span_id.zfill(16) # if span_id is shorter than 16 characters we prepend zeros
instana_tracestate = "in={};{}".format(in_trace_id, span_id)
span_id = (
in_span_id.zfill(16) if not isinstance(in_span_id, int) else in_span_id
)
instana_tracestate = f"in={in_trace_id};{span_id}"
if tracestate is None or tracestate == "":
tracestate = instana_tracestate
else:
Expand Down
8 changes: 7 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys

import pytest
from opentelemetry.context.context import Context
from opentelemetry.trace import set_span_in_context

if importlib.util.find_spec('celery'):
pytest_plugins = ("celery.contrib.pytest", )
Expand All @@ -20,7 +22,6 @@
from instana.span import BaseSpan, InstanaSpan # noqa: E402
from instana.span_context import SpanContext # noqa: E402


collect_ignore_glob = [
"*autoprofile*",
"*clients*",
Expand Down Expand Up @@ -123,3 +124,8 @@ def span(span_context: SpanContext) -> InstanaSpan:
@pytest.fixture
def base_span(span: InstanaSpan) -> BaseSpan:
return BaseSpan(span, None, "test")


@pytest.fixture
def context(span: InstanaSpan) -> Context:
return set_span_in_context(span)
1 change: 1 addition & 0 deletions tests/requirements-310.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protobuf<4.0.0
pymongo>=3.11.4
pyramid>=2.0.1
pytest>=6.2.4
pytest-mock>=3.12.0
redis>=3.5.3
requests-mock
responses<=0.17.0
Expand Down
1 change: 1 addition & 0 deletions tests/requirements-312.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protobuf<4.0.0
pymongo>=3.11.4
pyramid>=2.0.1
pytest>=6.2.4
pytest-mock>=3.12.0
redis>=3.5.3
requests-mock
responses<=0.17.0
Expand Down
1 change: 1 addition & 0 deletions tests/requirements-313.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ protobuf<4.0.0
pymongo>=3.11.4
pyramid>=2.0.1
pytest>=6.2.4
pytest-mock>=3.12.0
redis>=3.5.3
requests-mock
responses<=0.17.0
Expand Down
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ protobuf<4.0.0
pymongo>=3.11.4
pyramid>=2.0.1
pytest>=6.2.4
pytest-mock>=3.12.0
redis>=3.5.3
requests-mock
responses<=0.17.0
Expand Down
8 changes: 4 additions & 4 deletions tests/test_span.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_span_get_span_context(
trace_id: int,
span_id: int,
) -> None:

span_name = "test-span"
span = InstanaSpan(span_name, span_context)

Expand Down Expand Up @@ -714,10 +715,9 @@ def test_span_assure_errored_exception(span_context: SpanContext) -> None:
assert not span.attributes


def test_get_current_span(span_context) -> None:
# span = get_current_span(span_context)
# assert span
pass
def test_get_current_span(context) -> None:
span = get_current_span(context)
assert isinstance(span, InstanaSpan)


def test_get_current_span_INVALID_SPAN() -> None:
Expand Down
1 change: 0 additions & 1 deletion tests/test_tracer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# (c) Copyright IBM Corp. 2024

from unittest.mock import patch
from opentelemetry.trace import set_span_in_context
from opentelemetry.trace.span import _SPAN_ID_MAX_VALUE, INVALID_SPAN_ID
import pytest
Expand Down