diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f367d85eaa..eb6b0b5b93e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.1.0...HEAD) +### Changed +- Include span parent in Jaeger gRPC export as `CHILD_OF` reference + ([#1809])(https://github.com/open-telemetry/opentelemetry-python/pull/1809) + ### Added - Added example for running Django with auto instrumentation. ([#1803](https://github.com/open-telemetry/opentelemetry-python/pull/1803)) +- Added support for OTEL_SERVICE_NAME. + ([#1829](https://github.com/open-telemetry/opentelemetry-python/pull/1829)) ### Changed - Fixed OTLP gRPC exporter silently failing if scheme is not specified in endpoint. ([#1806](https://github.com/open-telemetry/opentelemetry-python/pull/1806)) +- Rename CompositeHTTPPropagator to CompositePropagator as per specification. + ([#1807](https://github.com/open-telemetry/opentelemetry-python/pull/1807)) +- Propagators use the root context as default for `extract` and do not modify + the context if extracting from carrier does not work. + ([#1811](https://github.com/open-telemetry/opentelemetry-python/pull/1811)) +- Improve warning when failing to decode byte attribute + ([#1810](https://github.com/open-telemetry/opentelemetry-python/pull/1810)) +- Fixed inconsistency in parent_id formatting from the ConsoleSpanExporter + ([#1833](https://github.com/open-telemetry/opentelemetry-python/pull/1833)) ### Removed - Moved `opentelemetry-instrumentation` to contrib repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b6918ec30d..9c6e72244de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ This is the main repo for OpenTelemetry Python. Nevertheless, there are other re Please take a look at this list first, your contributions may belong in one of these repos better: 1. [OpenTelemetry Contrib](https://github.com/open-telemetry/opentelemetry-python-contrib): Instrumentations for third-party - libraries and frameworks. There is an ongoing effort to migrate into the Opentelemetry Contrib repo some of the existing + libraries and frameworks. There is an ongoing effort to migrate into the OpenTelemetry Contrib repo some of the existing programmatic instrumentations that are now in the `ext` directory in the main OpenTelemetry repo. Please ask in the Slack channel (see below) for guidance if you want to contribute with these instrumentations. diff --git a/README.md b/README.md index 2e8d6c887ac..e6a174e8848 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,6 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem - [Aaron Abbott](https://github.com/aabmass), Google - [Diego Hurtado](https://github.com/ocelotl) -- [Hector Hernandez](https://github.com/hectorhdzg), Microsoft - [Owais Lone](https://github.com/owais), Splunk - [Srikanth Chekuri](https://github.com/lonewolf3739) diff --git a/dev-requirements.txt b/dev-requirements.txt index bcbc90148c1..2fb15652b4f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,9 +4,9 @@ isort~=5.8 black~=20.8b1 httpretty~=1.0 mypy==0.812 -sphinx~=2.1 -sphinx-rtd-theme~=0.4 -sphinx-autodoc-typehints~=1.10.2 +sphinx~=3.5.4 +sphinx-rtd-theme~=0.5 +sphinx-autodoc-typehints pytest>=6.0 pytest-cov>=2.8 readme-renderer~=24.0 diff --git a/docs-requirements.txt b/docs-requirements.txt index f802674afd0..f60325f00b1 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,6 +1,8 @@ -sphinx~=2.4 -sphinx-rtd-theme~=0.4 +sphinx~=3.5.4 +sphinx-rtd-theme~=0.5 sphinx-autodoc-typehints +# used to generate docs for the website +sphinx-jekyll-builder # Need to install the api/sdk in the venv for autodoc. Modifying sys.path # doesn't work for pkg_resources. diff --git a/docs/conf.py b/docs/conf.py index 38a26e323fd..c1476acf177 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -125,7 +125,15 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + "examples/fork-process-model/flask-gunicorn", + "examples/fork-process-model/flask-uwsgi", + "examples/error_handler/error_handler_0", + "examples/error_handler/error_handler_1", +] autodoc_default_options = { "members": True, diff --git a/docs/examples/auto-instrumentation/README.rst b/docs/examples/auto-instrumentation/README.rst index 9298c9bef2f..23fb47b3964 100644 --- a/docs/examples/auto-instrumentation/README.rst +++ b/docs/examples/auto-instrumentation/README.rst @@ -45,7 +45,7 @@ Manually instrumented server return "served" Server not instrumented manually -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``server_uninstrumented.py`` @@ -57,7 +57,7 @@ Server not instrumented manually return "served" Prepare ------------ +------- Execute the following example in a separate virtual environment. Run the following commands to prepare for auto-instrumentation: @@ -69,7 +69,7 @@ Run the following commands to prepare for auto-instrumentation: $ source auto_instrumentation/bin/activate Install ------------- +------- Run the following commands to install the appropriate packages. The ``opentelemetry-instrumentation`` package provides several @@ -90,7 +90,7 @@ a server as well as the process of executing an automatically instrumented server. Execute a manually instrumented server -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Execute the server in two separate consoles, one to run each of the scripts that make up this example: @@ -145,7 +145,7 @@ similar to the following example: } Execute an automatically instrumented server -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Stop the execution of ``server_instrumented.py`` with ``ctrl + c`` and run the following command instead: @@ -208,7 +208,7 @@ You can see that both outputs are the same because automatic instrumentation doe exactly what manual instrumentation does. Instrumentation while debugging -=============================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The debug mode can be enabled in the Flask app like this: @@ -226,3 +226,11 @@ reloader. To run instrumentation while the debug mode is enabled, set the if __name__ == "__main__": app.run(port=8082, debug=True, use_reloader=False) + + +Additional resources +~~~~~~~~~~~~~~~~~~~~ + +In order to send telemetry to an OpenTelemetry Collector without doing any +additional configuration, read about the `OpenTelemetry Distro <../distro/README.html>`_ +package. diff --git a/docs/examples/basic_context/README.rst b/docs/examples/basic_context/README.rst index 361192b96fc..1499a4bf8e6 100644 --- a/docs/examples/basic_context/README.rst +++ b/docs/examples/basic_context/README.rst @@ -1,14 +1,11 @@ Basic Context ============= -These examples show how context is propagated through Spans in OpenTelemetry. - -There are three different examples: +These examples show how context is propagated through Spans in OpenTelemetry. There are three different +examples: * implicit_context: Shows how starting a span implicitly creates context. - * child_context: Shows how context is propagated through child spans. - * async_context: Shows how context can be shared in another coroutine. The source files of these examples are available :scm_web:`here `. diff --git a/docs/examples/basic_tracer/README.rst b/docs/examples/basic_tracer/README.rst index 618263b7a4f..572b4dc8704 100644 --- a/docs/examples/basic_tracer/README.rst +++ b/docs/examples/basic_tracer/README.rst @@ -1,12 +1,9 @@ Basic Trace =========== -These examples show how to use OpenTelemetry to create and export Spans. - -There are two different examples: +These examples show how to use OpenTelemetry to create and export Spans. There are two different examples: * basic_trace: Shows how to configure a SpanProcessor and Exporter, and how to create a tracer and span. - * resources: Shows how to add resource information to a Provider. The source files of these examples are available :scm_web:`here `. diff --git a/docs/examples/datadog_exporter/README.rst b/docs/examples/datadog_exporter/README.rst index 03f485da159..39e0798da30 100644 --- a/docs/examples/datadog_exporter/README.rst +++ b/docs/examples/datadog_exporter/README.rst @@ -1,5 +1,5 @@ -Datadog Exporter Example -======================== +Datadog Exporter +================ These examples show how to use OpenTelemetry to send tracing data to Datadog. diff --git a/docs/examples/datadog_exporter/server.py b/docs/examples/datadog_exporter/server.py index 662d3ebe97c..f032887a122 100644 --- a/docs/examples/datadog_exporter/server.py +++ b/docs/examples/datadog_exporter/server.py @@ -21,7 +21,7 @@ ) from opentelemetry.exporter.datadog.propagator import DatadogFormat from opentelemetry.propagate import get_global_textmap, set_global_textmap -from opentelemetry.propagators.composite import CompositeHTTPPropagator +from opentelemetry.propagators.composite import CompositePropagator from opentelemetry.sdk.trace import TracerProvider app = Flask(__name__) @@ -38,13 +38,11 @@ # append Datadog format for propagation to and from Datadog instrumented services global_textmap = get_global_textmap() -if isinstance(global_textmap, CompositeHTTPPropagator) and not any( +if isinstance(global_textmap, CompositePropagator) and not any( isinstance(p, DatadogFormat) for p in global_textmap._propagators ): set_global_textmap( - CompositeHTTPPropagator( - global_textmap._propagators + [DatadogFormat()] - ) + CompositePropagator(global_textmap._propagators + [DatadogFormat()]) ) else: set_global_textmap(DatadogFormat()) diff --git a/docs/examples/distro/README.rst b/docs/examples/distro/README.rst new file mode 100644 index 00000000000..f58680609ab --- /dev/null +++ b/docs/examples/distro/README.rst @@ -0,0 +1,104 @@ +OpenTelemetry Distro +==================== + +In order to make using OpenTelemetry and auto-instrumentation as quick as possible without sacrificing flexibility, +OpenTelemetry distros provide a mechanism to automatically configure some of the more common options for users. By +harnessing their power, users of OpenTelemetry can configure the components as they need. The ``opentelemetry-distro`` +package provides some defaults to users looking to get started, it configures: + +- the SDK TracerProvider +- a BatchSpanProcessor +- the OTLP ``SpanExporter`` to send data to an OpenTelemetry collector + +The package also provides a starting point for anyone interested in producing an alternative distro. The +interfaces implemented by the package are loaded by the auto-instrumentation via the ``opentelemetry_distro`` +and ``opentelemetry_configurator`` entry points to configure the application before any other code is +executed. + +In order to automatically export data from OpenTelemetry to the OpenTelemetry collector, installing the +package will setup all the required entry points. + +.. code:: sh + + $ pip install opentelemetry-distro[otlp] opentelemetry-instrumentation + +Start the Collector locally to see data being exported. Write the following file: + +.. code-block:: yaml + + # /tmp/otel-collector-config.yaml + receivers: + otlp: + protocols: + grpc: + http: + exporters: + logging: + loglevel: debug + processors: + batch: + service: + pipelines: + traces: + receivers: [otlp] + exporters: [logging] + processors: [batch] + +Then start the Docker container: + +.. code-block:: sh + + docker run -p 4317:4317 \ + -v /tmp/otel-collector-config.yaml:/etc/otel-collector-config.yaml \ + otel/opentelemetry-collector:latest \ + --config=/etc/otel-collector-config.yaml + +The following code will create a span with no configuration. + +.. code:: python + + # no_configuration.py + from opentelemetry import trace + + with trace.get_tracer(__name__).start_as_current_span("foo"): + with trace.get_tracer(__name__).start_as_current_span("bar"): + print("baz") + +Lastly, run the ``no_configuration.py`` with the auto-instrumentation: + +.. code-block:: sh + + $ opentelemetry-instrument python no_configuration.py + +The resulting span will appear in the output from the collector and look similar to this: + +.. code-block:: sh + + Resource labels: + -> telemetry.sdk.language: STRING(python) + -> telemetry.sdk.name: STRING(opentelemetry) + -> telemetry.sdk.version: STRING(1.1.0) + -> service.name: STRING(unknown_service) + InstrumentationLibrarySpans #0 + InstrumentationLibrary __main__ + Span #0 + Trace ID : db3c99e5bfc50ef8be1773c3765e8845 + Parent ID : 0677126a4d110cb8 + ID : 3163b3022808ed1b + Name : bar + Kind : SPAN_KIND_INTERNAL + Start time : 2021-05-06 22:54:51.23063 +0000 UTC + End time : 2021-05-06 22:54:51.230684 +0000 UTC + Status code : STATUS_CODE_UNSET + Status message : + Span #1 + Trace ID : db3c99e5bfc50ef8be1773c3765e8845 + Parent ID : + ID : 0677126a4d110cb8 + Name : foo + Kind : SPAN_KIND_INTERNAL + Start time : 2021-05-06 22:54:51.230549 +0000 UTC + End time : 2021-05-06 22:54:51.230706 +0000 UTC + Status code : STATUS_CODE_UNSET + Status message : + diff --git a/docs/examples/django/README.rst b/docs/examples/django/README.rst index 42316d01ee2..656a07b6da8 100644 --- a/docs/examples/django/README.rst +++ b/docs/examples/django/README.rst @@ -1,5 +1,5 @@ -OpenTelemetry Django Instrumentation Example -============================================ +Django Instrumentation +====================== This shows how to use ``opentelemetry-instrumentation-django`` to automatically instrument a Django app. @@ -142,4 +142,4 @@ References * `Django `_ * `OpenTelemetry Project `_ -* `OpenTelemetry Django extension `_ +* `OpenTelemetry Django extension `_ diff --git a/docs/examples/opencensus-exporter-tracer/README.rst b/docs/examples/opencensus-exporter-tracer/README.rst index d147f008d49..3047987c2c4 100644 --- a/docs/examples/opencensus-exporter-tracer/README.rst +++ b/docs/examples/opencensus-exporter-tracer/README.rst @@ -1,5 +1,5 @@ -OpenTelemetry Collector Tracer OpenCensus Exporter Example -========================================================== +OpenCensus Exporter +=================== This example shows how to use the OpenCensus Exporter to export traces to the OpenTelemetry collector. diff --git a/docs/examples/opentracing/README.rst b/docs/examples/opentracing/README.rst index da9c70e43b8..0bf5f8dca3d 100644 --- a/docs/examples/opentracing/README.rst +++ b/docs/examples/opentracing/README.rst @@ -1,5 +1,5 @@ -OpenTracing Shim Example -========================== +OpenTracing Shim +================ This example shows how to use the :doc:`opentelemetry-opentracing-shim package <../../shim/opentracing_shim/opentracing_shim>` diff --git a/docs/exporter/jaeger/jaeger.rst b/docs/exporter/jaeger/jaeger.rst index 5a49f72b5c8..1fc02948d8d 100644 --- a/docs/exporter/jaeger/jaeger.rst +++ b/docs/exporter/jaeger/jaeger.rst @@ -1,4 +1,4 @@ -Opentelemetry Jaeger Exporters +OpenTelemetry Jaeger Exporters ============================== .. automodule:: opentelemetry.exporter.jaeger diff --git a/docs/exporter/otlp/otlp.rst b/docs/exporter/otlp/otlp.rst index e4bfdd07a1d..471f2935fb7 100644 --- a/docs/exporter/otlp/otlp.rst +++ b/docs/exporter/otlp/otlp.rst @@ -1,4 +1,4 @@ -Opentelemetry OTLP Exporters +OpenTelemetry OTLP Exporters ============================ .. automodule:: opentelemetry.exporter.otlp diff --git a/docs/getting_started/otlpcollector_example.py b/docs/getting_started/otlpcollector_example.py index 48c0d32a594..71f9ed97541 100644 --- a/docs/getting_started/otlpcollector_example.py +++ b/docs/getting_started/otlpcollector_example.py @@ -24,7 +24,7 @@ span_exporter = OTLPSpanExporter( # optional - # endpoint:="myCollectorURL:4317", + # endpoint="myCollectorURL:4317", # credentials=ChannelCredentials(credentials), # headers=(("metadata", "metadata")), ) diff --git a/docs/index.rst b/docs/index.rst index 42969c058f5..fe1b2a85388 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -72,7 +72,7 @@ install .. toctree:: :maxdepth: 1 - :caption: OpenTelemetry Python Packages + :caption: Core Packages :name: packages api/api @@ -80,7 +80,7 @@ install .. toctree:: :maxdepth: 2 - :caption: OpenTelemetry Exporters + :caption: Exporters :name: exporters :glob: @@ -88,7 +88,7 @@ install .. toctree:: :maxdepth: 2 - :caption: OpenTelemetry Shims + :caption: Shims :name: Shims :glob: @@ -96,7 +96,7 @@ install .. toctree:: :maxdepth: 1 - :caption: OpenTelemetry Python Performance + :caption: Performance :name: performance-tests :glob: diff --git a/exporter/opentelemetry-exporter-jaeger-proto-grpc/src/opentelemetry/exporter/jaeger/proto/grpc/translate/__init__.py b/exporter/opentelemetry-exporter-jaeger-proto-grpc/src/opentelemetry/exporter/jaeger/proto/grpc/translate/__init__.py index 778296a917c..e644e213603 100644 --- a/exporter/opentelemetry-exporter-jaeger-proto-grpc/src/opentelemetry/exporter/jaeger/proto/grpc/translate/__init__.py +++ b/exporter/opentelemetry-exporter-jaeger-proto-grpc/src/opentelemetry/exporter/jaeger/proto/grpc/translate/__init__.py @@ -318,10 +318,18 @@ def _extract_tags( def _extract_refs( self, span: ReadableSpan ) -> Optional[Sequence[model_pb2.SpanRef]]: - if not span.links: - return None refs = [] + if span.parent: + ctx = span.get_span_context() + parent_id = span.parent.span_id + parent_ref = model_pb2.SpanRef( + ref_type=model_pb2.SpanRefType.CHILD_OF, + trace_id=_trace_id_to_bytes(ctx.trace_id), + span_id=_span_id_to_bytes(parent_id), + ) + refs.append(parent_ref) + for link in span.links: trace_id = link.context.trace_id span_id = link.context.span_id diff --git a/exporter/opentelemetry-exporter-jaeger-proto-grpc/tests/test_jaeger_exporter_protobuf.py b/exporter/opentelemetry-exporter-jaeger-proto-grpc/tests/test_jaeger_exporter_protobuf.py index 99b3f093cb8..383e33d0f16 100644 --- a/exporter/opentelemetry-exporter-jaeger-proto-grpc/tests/test_jaeger_exporter_protobuf.py +++ b/exporter/opentelemetry-exporter-jaeger-proto-grpc/tests/test_jaeger_exporter_protobuf.py @@ -287,11 +287,16 @@ def test_translate_to_jaeger(self): ), ], references=[ + model_pb2.SpanRef( + ref_type=model_pb2.SpanRefType.CHILD_OF, + trace_id=pb_translator._trace_id_to_bytes(trace_id), + span_id=pb_translator._span_id_to_bytes(parent_id), + ), model_pb2.SpanRef( ref_type=model_pb2.SpanRefType.FOLLOWS_FROM, trace_id=pb_translator._trace_id_to_bytes(trace_id), span_id=pb_translator._span_id_to_bytes(other_id), - ) + ), ], logs=[ model_pb2.Log( diff --git a/opentelemetry-api/setup.cfg b/opentelemetry-api/setup.cfg index e712564260f..99d52838fb1 100644 --- a/opentelemetry-api/setup.cfg +++ b/opentelemetry-api/setup.cfg @@ -42,6 +42,7 @@ packages=find_namespace: zip_safe = False include_package_data = True install_requires = + Deprecated >= 1.2.6 aiocontextvars; python_version<'3.7' [options.packages.find] diff --git a/opentelemetry-api/src/opentelemetry/propagate/__init__.py b/opentelemetry-api/src/opentelemetry/propagate/__init__.py index 6c63edec3cb..3ad1ad544ab 100644 --- a/opentelemetry-api/src/opentelemetry/propagate/__init__.py +++ b/opentelemetry-api/src/opentelemetry/propagate/__init__.py @@ -16,13 +16,13 @@ API for propagation of context. The propagators for the -``opentelemetry.propagators.composite.CompositeHTTPPropagator`` can be defined +``opentelemetry.propagators.composite.CompositePropagator`` can be defined via configuration in the ``OTEL_PROPAGATORS`` environment variable. This variable should be set to a comma-separated string of names of values for the ``opentelemetry_propagator`` entry point. For example, setting ``OTEL_PROPAGATORS`` to ``tracecontext,baggage`` (which is the default value) would instantiate -``opentelemetry.propagators.composite.CompositeHTTPPropagator`` with 2 +``opentelemetry.propagators.composite.CompositePropagator`` with 2 propagators, one of type ``opentelemetry.trace.propagation.tracecontext.TraceContextTextMapPropagator`` and other of type ``opentelemetry.baggage.propagation.W3CBaggagePropagator``. @@ -96,7 +96,7 @@ def extract( used to construct a Context. This object must be paired with an appropriate getter which understands how to extract a value from it. - context: an optional Context to use. Defaults to current + context: an optional Context to use. Defaults to root context if not set. """ return get_global_textmap().extract(carrier, context, getter=getter) @@ -142,7 +142,7 @@ def inject( logger.exception("Failed to load configured propagators") raise -_HTTP_TEXT_FORMAT = composite.CompositeHTTPPropagator(propagators) # type: ignore +_HTTP_TEXT_FORMAT = composite.CompositePropagator(propagators) # type: ignore def get_global_textmap() -> textmap.TextMapPropagator: diff --git a/opentelemetry-api/src/opentelemetry/propagators/composite.py b/opentelemetry-api/src/opentelemetry/propagators/composite.py index c027f638dcd..b06e385b588 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/composite.py +++ b/opentelemetry-api/src/opentelemetry/propagators/composite.py @@ -14,14 +14,16 @@ import logging import typing +from deprecated import deprecated + from opentelemetry.context.context import Context from opentelemetry.propagators import textmap logger = logging.getLogger(__name__) -class CompositeHTTPPropagator(textmap.TextMapPropagator): - """CompositeHTTPPropagator provides a mechanism for combining multiple +class CompositePropagator(textmap.TextMapPropagator): + """CompositePropagator provides a mechanism for combining multiple propagators into a single one. Args: @@ -80,3 +82,10 @@ def fields(self) -> typing.Set[str]: composite_fields.add(field) return composite_fields + + +@deprecated(version="1.2.0", reason="You should use CompositePropagator") # type: ignore +class CompositeHTTPPropagator(CompositePropagator): + """CompositeHTTPPropagator provides a mechanism for combining multiple + propagators into a single one. + """ diff --git a/opentelemetry-api/src/opentelemetry/propagators/textmap.py b/opentelemetry-api/src/opentelemetry/propagators/textmap.py index 45c2308f661..0011315cf21 100644 --- a/opentelemetry-api/src/opentelemetry/propagators/textmap.py +++ b/opentelemetry-api/src/opentelemetry/propagators/textmap.py @@ -150,7 +150,7 @@ def extract( used to construct a Context. This object must be paired with an appropriate getter which understands how to extract a value from it. - context: an optional Context to use. Defaults to current + context: an optional Context to use. Defaults to root context if not set. Returns: A Context with configuration found in the carrier. diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py index 9fc5cfed242..001db5c7293 100644 --- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py +++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py @@ -43,14 +43,17 @@ def extract( See `opentelemetry.propagators.textmap.TextMapPropagator.extract` """ + if context is None: + context = Context() + header = getter.get(carrier, self._TRACEPARENT_HEADER_NAME) if not header: - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context version = match.group(1) trace_id = match.group(2) @@ -58,13 +61,13 @@ def extract( trace_flags = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context if version == "00": if match.group(5): - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context if version == "ff": - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context tracestate_headers = getter.get(carrier, self._TRACESTATE_HEADER_NAME) if tracestate_headers is None: diff --git a/opentelemetry-api/src/opentelemetry/util/_time.py b/opentelemetry-api/src/opentelemetry/util/_time.py index aa61bc02aa4..0099d3dbb5c 100644 --- a/opentelemetry-api/src/opentelemetry/util/_time.py +++ b/opentelemetry-api/src/opentelemetry/util/_time.py @@ -18,7 +18,7 @@ if version_info.minor < 7: getLogger(__name__).warning( # pylint: disable=logging-not-lazy "You are using Python 3.%s. This version does not support timestamps " - "with nanosecond precision and the Opentelemetry SDK will use " + "with nanosecond precision and the OpenTelemetry SDK will use " "millisecond precision instead. Please refer to PEP 546 for more " "information. Please upgrade to Python 3.7 or newer to use nanosecond " "precision." % version_info.minor diff --git a/opentelemetry-api/tests/propagators/test_composite.py b/opentelemetry-api/tests/propagators/test_composite.py index ef9fae2a1ac..d6bf6960cdf 100644 --- a/opentelemetry-api/tests/propagators/test_composite.py +++ b/opentelemetry-api/tests/propagators/test_composite.py @@ -17,7 +17,7 @@ import unittest from unittest.mock import Mock -from opentelemetry.propagators.composite import CompositeHTTPPropagator +from opentelemetry.propagators.composite import CompositePropagator def get_as_list(dict_object, key): @@ -67,7 +67,7 @@ def setUpClass(cls): ) def test_no_propagators(self): - propagator = CompositeHTTPPropagator([]) + propagator = CompositePropagator([]) new_carrier = {} propagator.inject(new_carrier) self.assertEqual(new_carrier, {}) @@ -78,7 +78,7 @@ def test_no_propagators(self): self.assertEqual(context, {}) def test_single_propagator(self): - propagator = CompositeHTTPPropagator([self.mock_propagator_0]) + propagator = CompositePropagator([self.mock_propagator_0]) new_carrier = {} propagator.inject(new_carrier) @@ -90,7 +90,7 @@ def test_single_propagator(self): self.assertEqual(context, {"mock-0": "context"}) def test_multiple_propagators(self): - propagator = CompositeHTTPPropagator( + propagator = CompositePropagator( [self.mock_propagator_0, self.mock_propagator_1] ) @@ -106,7 +106,7 @@ def test_multiple_propagators(self): def test_multiple_propagators_same_key(self): # test that when multiple propagators extract/inject the same # key, the later propagator values are extracted/injected - propagator = CompositeHTTPPropagator( + propagator = CompositePropagator( [self.mock_propagator_0, self.mock_propagator_2] ) @@ -120,7 +120,7 @@ def test_multiple_propagators_same_key(self): self.assertEqual(context, {"mock-0": "context2"}) def test_fields(self): - propagator = CompositeHTTPPropagator( + propagator = CompositePropagator( [ self.mock_propagator_0, self.mock_propagator_1, diff --git a/opentelemetry-api/tests/propagators/test_propagators.py b/opentelemetry-api/tests/propagators/test_propagators.py index 80299d34904..bcc01094656 100644 --- a/opentelemetry-api/tests/propagators/test_propagators.py +++ b/opentelemetry-api/tests/propagators/test_propagators.py @@ -27,7 +27,7 @@ class TestPropagators(TestCase): - @patch("opentelemetry.propagators.composite.CompositeHTTPPropagator") + @patch("opentelemetry.propagators.composite.CompositePropagator") def test_default_composite_propagators(self, mock_compositehttppropagator): def test_propagators(propagators): @@ -48,7 +48,7 @@ def test_propagators(propagators): reload(opentelemetry.propagate) @patch.dict(environ, {OTEL_PROPAGATORS: "a,b,c"}) - @patch("opentelemetry.propagators.composite.CompositeHTTPPropagator") + @patch("opentelemetry.propagators.composite.CompositePropagator") @patch("pkg_resources.iter_entry_points") def test_non_default_propagators( self, mock_iter_entry_points, mock_compositehttppropagator diff --git a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py index 98ca50610b9..9d9561a4a5e 100644 --- a/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py +++ b/opentelemetry-api/tests/trace/propagation/test_tracecontexthttptextformat.py @@ -19,6 +19,7 @@ from unittest.mock import Mock, patch from opentelemetry import trace +from opentelemetry.context import Context from opentelemetry.trace.propagation import tracecontext from opentelemetry.trace.span import TraceState @@ -270,3 +271,51 @@ def test_fields(self, mock_get_current_span, mock_invalid_span_context): inject_fields.add(mock_call[1][1]) self.assertEqual(inject_fields, FORMAT.fields) + + def test_extract_no_trace_parent_to_explicit_ctx(self): + carrier = {"tracestate": ["foo=1"]} + orig_ctx = Context({"k1": "v1"}) + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_no_trace_parent_to_implicit_ctx(self): + carrier = {"tracestate": ["foo=1"]} + + ctx = FORMAT.extract(carrier) + self.assertDictEqual(Context(), ctx) + + def test_extract_invalid_trace_parent_to_explicit_ctx(self): + trace_parent_headers = [ + "invalid", + "00-00000000000000000000000000000000-1234567890123456-00", + "00-12345678901234567890123456789012-0000000000000000-00", + "00-12345678901234567890123456789012-1234567890123456-00-residue", + ] + for trace_parent in trace_parent_headers: + with self.subTest(trace_parent=trace_parent): + carrier = { + "traceparent": [trace_parent], + "tracestate": ["foo=1"], + } + orig_ctx = Context({"k1": "v1"}) + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_invalid_trace_parent_to_implicit_ctx(self): + trace_parent_headers = [ + "invalid", + "00-00000000000000000000000000000000-1234567890123456-00", + "00-12345678901234567890123456789012-0000000000000000-00", + "00-12345678901234567890123456789012-1234567890123456-00-residue", + ] + for trace_parent in trace_parent_headers: + with self.subTest(trace_parent=trace_parent): + carrier = { + "traceparent": [trace_parent], + "tracestate": ["foo=1"], + } + + ctx = FORMAT.extract(carrier) + self.assertDictEqual(Context(), ctx) diff --git a/opentelemetry-distro/README.rst b/opentelemetry-distro/README.rst index 4189131fc26..80952839104 100644 --- a/opentelemetry-distro/README.rst +++ b/opentelemetry-distro/README.rst @@ -14,9 +14,10 @@ Installation pip install opentelemetry-distro -This package provides entrypoints to configure OpenTelemetry +This package provides entrypoints to configure OpenTelemetry. References ---------- * `OpenTelemetry Project `_ +* `Example using opentelemetry-distro `_ diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py index 9289d1c44ad..0aa5aa515d7 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/environment_variables/__init__.py @@ -218,3 +218,20 @@ """ .. envvar:: OTEL_EXPORTER_JAEGER_AGENT_SPLIT_OVERSIZED_BATCHES """ + +OTEL_SERVICE_NAME = "OTEL_SERVICE_NAME" +""" +.. envvar:: OTEL_SERVICE_NAME + +Convenience environment variable for setting the service name resource attribute. +The following two environment variables have the same effect + +.. code-block:: console + + OTEL_SERVICE_NAME=my-python-service + + OTEL_RESOURCE_ATTRIBUTES=service.name=my-python-service + + +If both are set, :envvar:`OTEL_SERVICE_NAME` takes precedence. +""" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 6cffff42639..77240b8ef8e 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -64,7 +64,10 @@ import pkg_resources -from opentelemetry.sdk.environment_variables import OTEL_RESOURCE_ATTRIBUTES +from opentelemetry.sdk.environment_variables import ( + OTEL_RESOURCE_ATTRIBUTES, + OTEL_SERVICE_NAME, +) from opentelemetry.semconv.resource import ResourceAttributes LabelValue = typing.Union[str, bool, int, float] @@ -231,6 +234,9 @@ def detect(self) -> "Resource": item.split("=") for item in env_resources_items.split(",") ) } + service_name = os.environ.get(OTEL_SERVICE_NAME) + if service_name: + env_resource_map[SERVICE_NAME] = service_name return Resource(env_resource_map) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 625b8519ef5..ece7da6d225 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -483,9 +483,13 @@ def to_json(self, indent=4): if self.parent is not None: if isinstance(self.parent, Span): ctx = self.parent.context - parent_id = trace_api.format_span_id(ctx.span_id) + parent_id = "0x{}".format( + trace_api.format_span_id(ctx.span_id) + ) elif isinstance(self.parent, SpanContext): - parent_id = trace_api.format_span_id(self.parent.span_id) + parent_id = "0x{}".format( + trace_api.format_span_id(self.parent.span_id) + ) start_time = None if self._start_time: @@ -685,7 +689,10 @@ def set_attributes( try: value = value.decode() except ValueError: - logger.warning("Byte attribute could not be decoded.") + logger.warning( + "Byte attribute could not be decoded for key `%s`.", + key, + ) return self._attributes[key] = value diff --git a/opentelemetry-sdk/tests/resources/test_resources.py b/opentelemetry-sdk/tests/resources/test_resources.py index 4b8fd71da27..211187f1ebb 100644 --- a/opentelemetry-sdk/tests/resources/test_resources.py +++ b/opentelemetry-sdk/tests/resources/test_resources.py @@ -232,6 +232,20 @@ def test_env_priority(self): self.assertEqual(resource_env_override.attributes["key1"], "value1") self.assertEqual(resource_env_override.attributes["key2"], "value2") + @mock.patch.dict( + os.environ, + { + resources.OTEL_SERVICE_NAME: "test-srv-name", + resources.OTEL_RESOURCE_ATTRIBUTES: "service.name=svc-name-from-resource", + }, + ) + def test_service_name_env(self): + resource = resources.Resource.create() + self.assertEqual(resource.attributes["service.name"], "test-srv-name") + + resource = resources.Resource.create({"service.name": "from-code"}) + self.assertEqual(resource.attributes["service.name"], "from-code") + class TestOTELResourceDetector(unittest.TestCase): def setUp(self) -> None: @@ -270,3 +284,28 @@ def test_multiple_with_whitespace(self): self.assertEqual( detector.detect(), resources.Resource({"k": "v", "k2": "v2"}) ) + + @mock.patch.dict( + os.environ, + {resources.OTEL_SERVICE_NAME: "test-srv-name"}, + ) + def test_service_name_env(self): + detector = resources.OTELResourceDetector() + self.assertEqual( + detector.detect(), + resources.Resource({"service.name": "test-srv-name"}), + ) + + @mock.patch.dict( + os.environ, + { + resources.OTEL_SERVICE_NAME: "from-service-name", + resources.OTEL_RESOURCE_ATTRIBUTES: "service.name=from-resource-attrs", + }, + ) + def test_service_name_env_precedence(self): + detector = resources.OTELResourceDetector() + self.assertEqual( + detector.detect(), + resources.Resource({"service.name": "from-service-name"}), + ) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index df2454a5215..f12dc7c75cf 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -1221,7 +1221,10 @@ def test_to_json(self): is_remote=False, trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED), ) - span = trace._Span("span-name", context, resource=Resource({})) + parent = trace._Span("parent-name", context, resource=Resource({})) + span = trace._Span( + "span-name", context, resource=Resource({}), parent=parent + ) self.assertEqual( span.to_json(), @@ -1233,7 +1236,7 @@ def test_to_json(self): "trace_state": "[]" }, "kind": "SpanKind.INTERNAL", - "parent_id": null, + "parent_id": "0x00000000deadbef0", "start_time": null, "end_time": null, "status": { @@ -1247,7 +1250,7 @@ def test_to_json(self): ) self.assertEqual( span.to_json(indent=None), - '{"name": "span-name", "context": {"trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "[]"}, "kind": "SpanKind.INTERNAL", "parent_id": null, "start_time": null, "end_time": null, "status": {"status_code": "UNSET"}, "attributes": {}, "events": [], "links": [], "resource": {}}', + '{"name": "span-name", "context": {"trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "[]"}, "kind": "SpanKind.INTERNAL", "parent_id": "0x00000000deadbef0", "start_time": null, "end_time": null, "status": {"status_code": "UNSET"}, "attributes": {}, "events": [], "links": [], "resource": {}}', ) def test_attributes_to_json(self): diff --git a/propagator/opentelemetry-propagator-b3/src/opentelemetry/propagators/b3/__init__.py b/propagator/opentelemetry-propagator-b3/src/opentelemetry/propagators/b3/__init__.py index 6977bc32c64..d0beec401a8 100644 --- a/propagator/opentelemetry-propagator-b3/src/opentelemetry/propagators/b3/__init__.py +++ b/propagator/opentelemetry-propagator-b3/src/opentelemetry/propagators/b3/__init__.py @@ -50,6 +50,8 @@ def extract( context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: + if context is None: + context = Context() trace_id = trace.INVALID_TRACE_ID span_id = trace.INVALID_SPAN_ID sampled = "0" @@ -97,8 +99,6 @@ def extract( or self._trace_id_regex.fullmatch(trace_id) is None or self._span_id_regex.fullmatch(span_id) is None ): - if context is None: - return trace.set_span_in_context(trace.INVALID_SPAN, context) return context trace_id = int(trace_id, 16) diff --git a/propagator/opentelemetry-propagator-b3/tests/test_b3_format.py b/propagator/opentelemetry-propagator-b3/tests/test_b3_format.py index 6ee0be2ce1c..29d8a472ea3 100644 --- a/propagator/opentelemetry-propagator-b3/tests/test_b3_format.py +++ b/propagator/opentelemetry-propagator-b3/tests/test_b3_format.py @@ -19,7 +19,7 @@ import opentelemetry.sdk.trace as trace import opentelemetry.sdk.trace.id_generator as id_generator import opentelemetry.trace as trace_api -from opentelemetry.context import get_current +from opentelemetry.context import Context, get_current from opentelemetry.propagators.textmap import DefaultGetter FORMAT = b3_format.B3Format() @@ -219,7 +219,7 @@ def test_flags_and_sampling(self): def test_derived_ctx_is_returned_for_success(self): """Ensure returned context is derived from the given context.""" - old_ctx = {"k1": "v1"} + old_ctx = Context({"k1": "v1"}) new_ctx = FORMAT.extract( { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, @@ -229,17 +229,19 @@ def test_derived_ctx_is_returned_for_success(self): old_ctx, ) self.assertIn("current-span", new_ctx) - for key, value in old_ctx.items(): + for key, value in old_ctx.items(): # pylint:disable=no-member self.assertIn(key, new_ctx) + # pylint:disable=unsubscriptable-object self.assertEqual(new_ctx[key], value) def test_derived_ctx_is_returned_for_failure(self): """Ensure returned context is derived from the given context.""" - old_ctx = {"k2": "v2"} + old_ctx = Context({"k2": "v2"}) new_ctx = FORMAT.extract({}, old_ctx) self.assertNotIn("current-span", new_ctx) - for key, value in old_ctx.items(): + for key, value in old_ctx.items(): # pylint:disable=no-member self.assertIn(key, new_ctx) + # pylint:disable=unsubscriptable-object self.assertEqual(new_ctx[key], value) def test_64bit_trace_id(self): @@ -258,18 +260,24 @@ def test_64bit_trace_id(self): new_carrier[FORMAT.TRACE_ID_KEY], "0" * 16 + trace_id_64_bit ) - def test_extract_invalid_single_header(self): + def test_extract_invalid_single_header_to_explicit_ctx(self): """Given unparsable header, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} new_ctx = FORMAT.extract(carrier, old_ctx) self.assertDictEqual(new_ctx, old_ctx) - def test_extract_missing_trace_id(self): + def test_extract_invalid_single_header_to_implicit_ctx(self): + carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} + new_ctx = FORMAT.extract(carrier) + + self.assertDictEqual(Context(), new_ctx) + + def test_extract_missing_trace_id_to_explicit_ctx(self): """Given no trace ID, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = { FORMAT.SPAN_ID_KEY: self.serialized_span_id, @@ -279,9 +287,18 @@ def test_extract_missing_trace_id(self): self.assertDictEqual(new_ctx, old_ctx) - def test_extract_invalid_trace_id(self): + def test_extract_missing_trace_id_to_implicit_ctx(self): + carrier = { + FORMAT.SPAN_ID_KEY: self.serialized_span_id, + FORMAT.FLAGS_KEY: "1", + } + new_ctx = FORMAT.extract(carrier) + + self.assertDictEqual(Context(), new_ctx) + + def test_extract_invalid_trace_id_to_explicit_ctx(self): """Given invalid trace ID, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = { FORMAT.TRACE_ID_KEY: "abc123", @@ -292,9 +309,19 @@ def test_extract_invalid_trace_id(self): self.assertDictEqual(new_ctx, old_ctx) - def test_extract_invalid_span_id(self): + def test_extract_invalid_trace_id_to_implicit_ctx(self): + carrier = { + FORMAT.TRACE_ID_KEY: "abc123", + FORMAT.SPAN_ID_KEY: self.serialized_span_id, + FORMAT.FLAGS_KEY: "1", + } + new_ctx = FORMAT.extract(carrier) + + self.assertDictEqual(Context(), new_ctx) + + def test_extract_invalid_span_id_to_explicit_ctx(self): """Given invalid span ID, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, @@ -305,9 +332,19 @@ def test_extract_invalid_span_id(self): self.assertDictEqual(new_ctx, old_ctx) - def test_extract_missing_span_id(self): + def test_extract_invalid_span_id_to_implicit_ctx(self): + carrier = { + FORMAT.TRACE_ID_KEY: self.serialized_trace_id, + FORMAT.SPAN_ID_KEY: "abc123", + FORMAT.FLAGS_KEY: "1", + } + new_ctx = FORMAT.extract(carrier) + + self.assertDictEqual(Context(), new_ctx) + + def test_extract_missing_span_id_to_explicit_ctx(self): """Given no span ID, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, @@ -317,15 +354,28 @@ def test_extract_missing_span_id(self): self.assertDictEqual(new_ctx, old_ctx) - def test_extract_empty_carrier(self): + def test_extract_missing_span_id_to_implicit_ctx(self): + carrier = { + FORMAT.TRACE_ID_KEY: self.serialized_trace_id, + FORMAT.FLAGS_KEY: "1", + } + new_ctx = FORMAT.extract(carrier) + + self.assertDictEqual(Context(), new_ctx) + + def test_extract_empty_carrier_to_explicit_ctx(self): """Given no headers at all, do not modify context""" - old_ctx = {} + old_ctx = Context({"k1": "v1"}) carrier = {} new_ctx = FORMAT.extract(carrier, old_ctx) self.assertDictEqual(new_ctx, old_ctx) + def test_extract_empty_carrier_to_implicit_ctx(self): + new_ctx = FORMAT.extract({}) + self.assertDictEqual(Context(), new_ctx) + @staticmethod def test_inject_empty_context(): """If the current context has no span, don't add headers""" @@ -368,5 +418,4 @@ def test_extract_none_context(self): carrier = {} new_ctx = FORMAT.extract(carrier, old_ctx) - self.assertIsNotNone(new_ctx) - self.assertEqual(new_ctx["current-span"], trace_api.INVALID_SPAN) + self.assertDictEqual(Context(), new_ctx) diff --git a/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py b/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py index 47f438531fb..974b9143a5a 100644 --- a/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py +++ b/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py @@ -47,31 +47,26 @@ def extract( ) -> Context: if context is None: - context = get_current() + context = Context() header = getter.get(carrier, self.TRACE_ID_KEY) if not header: - return trace.set_span_in_context(trace.INVALID_SPAN, context) - fields = _extract_first_element(header).split(":") + return context context = self._extract_baggage(getter, carrier, context) - if len(fields) != 4: - return trace.set_span_in_context(trace.INVALID_SPAN, context) - trace_id, span_id, _parent_id, flags = fields + trace_id, span_id, flags = _parse_trace_id_header(header) if ( trace_id == trace.INVALID_TRACE_ID or span_id == trace.INVALID_SPAN_ID ): - return trace.set_span_in_context(trace.INVALID_SPAN, context) + return context span = trace.NonRecordingSpan( trace.SpanContext( - trace_id=int(trace_id, 16), - span_id=int(span_id, 16), + trace_id=trace_id, + span_id=span_id, is_remote=True, - trace_flags=trace.TraceFlags( - int(flags, 16) & trace.TraceFlags.SAMPLED - ), + trace_flags=trace.TraceFlags(flags & trace.TraceFlags.SAMPLED), ) ) return trace.set_span_in_context(span, context) @@ -147,3 +142,35 @@ def _extract_first_element( if items is None: return None return next(iter(items), None) + + +def _parse_trace_id_header( + items: typing.Iterable[CarrierT], +) -> typing.Tuple[int]: + invalid_header_result = (trace.INVALID_TRACE_ID, trace.INVALID_SPAN_ID, 0) + + header = _extract_first_element(items) + if header is None: + return invalid_header_result + + fields = header.split(":") + if len(fields) != 4: + return invalid_header_result + + trace_id_str, span_id_str, _parent_id_str, flags_str = fields + flags = _int_from_hex_str(flags_str, None) + if flags is None: + return invalid_header_result + + trace_id = _int_from_hex_str(trace_id_str, trace.INVALID_TRACE_ID) + span_id = _int_from_hex_str(span_id_str, trace.INVALID_SPAN_ID) + return trace_id, span_id, flags + + +def _int_from_hex_str( + identifier: str, default: typing.Optional[int] +) -> typing.Optional[int]: + try: + return int(identifier, 16) + except ValueError: + return default diff --git a/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py b/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py index 12a0a028ddf..55e096b0954 100644 --- a/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py +++ b/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py @@ -19,6 +19,7 @@ import opentelemetry.sdk.trace.id_generator as id_generator import opentelemetry.trace as trace_api from opentelemetry import baggage +from opentelemetry.context import Context from opentelemetry.propagators import ( # pylint: disable=no-name-in-module jaeger, ) @@ -186,3 +187,45 @@ def test_fields(self): for call in mock_setter.mock_calls: inject_fields.add(call[1][1]) self.assertEqual(FORMAT.fields, inject_fields) + + def test_extract_no_trace_id_to_explicit_ctx(self): + carrier = {} + orig_ctx = Context({"k1": "v1"}) + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_no_trace_id_to_implicit_ctx(self): + carrier = {} + + ctx = FORMAT.extract(carrier) + self.assertDictEqual(Context(), ctx) + + def test_extract_invalid_uber_trace_id_header_to_explicit_ctx(self): + trace_id_headers = [ + "000000000000000000000000deadbeef:00000000deadbef0:00", + "00000000000000000000000000000000:00000000deadbef0:00:00", + "000000000000000000000000deadbeef:0000000000000000:00:00", + "000000000000000000000000deadbeef:0000000000000000:00:xyz", + ] + for trace_id_header in trace_id_headers: + with self.subTest(trace_id_header=trace_id_header): + carrier = {"uber-trace-id": trace_id_header} + orig_ctx = Context({"k1": "v1"}) + + ctx = FORMAT.extract(carrier, orig_ctx) + self.assertDictEqual(orig_ctx, ctx) + + def test_extract_invalid_uber_trace_id_header_to_implicit_ctx(self): + trace_id_headers = [ + "000000000000000000000000deadbeef:00000000deadbef0:00", + "00000000000000000000000000000000:00000000deadbef0:00:00", + "000000000000000000000000deadbeef:0000000000000000:00:00", + "000000000000000000000000deadbeef:0000000000000000:00:xyz", + ] + for trace_id_header in trace_id_headers: + with self.subTest(trace_id_header=trace_id_header): + carrier = {"uber-trace-id": trace_id_header} + + ctx = FORMAT.extract(carrier) + self.assertDictEqual(Context(), ctx) diff --git a/scripts/eachdist.py b/scripts/eachdist.py index 3a9b2b3b9e6..1cb8033d621 100755 --- a/scripts/eachdist.py +++ b/scripts/eachdist.py @@ -238,6 +238,17 @@ def setup_instparser(instparser): "releaseargs", nargs=argparse.REMAINDER, help=extraargs_help("pytest") ) + fmtparser = subparsers.add_parser( + "format", + help="Formats all source code with black and isort.", + ) + fmtparser.set_defaults(func=format_args) + fmtparser.add_argument( + "--path", + required=False, + help="Format only this path instead of entire repository", + ) + return parser.parse_args(args) @@ -649,6 +660,25 @@ def test_args(args): ) +def format_args(args): + format_dir = str(find_projectroot()) + if args.path: + format_dir = os.path.join(format_dir, args.path) + + runsubprocess( + args.dry_run, + ("black", "."), + cwd=format_dir, + check=True, + ) + runsubprocess( + args.dry_run, + ("isort", "--profile", "black", "."), + cwd=format_dir, + check=True, + ) + + def main(): args = parse_args() args.func(args) diff --git a/tests/util/src/opentelemetry/test/mock_textmap.py b/tests/util/src/opentelemetry/test/mock_textmap.py index 4cdef447d6a..c3e901ee287 100644 --- a/tests/util/src/opentelemetry/test/mock_textmap.py +++ b/tests/util/src/opentelemetry/test/mock_textmap.py @@ -15,7 +15,7 @@ import typing from opentelemetry import trace -from opentelemetry.context import Context, get_current +from opentelemetry.context import Context from opentelemetry.propagators.textmap import ( CarrierT, Getter, @@ -39,7 +39,7 @@ def extract( context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: - return get_current() + return Context() def inject( self, @@ -66,11 +66,13 @@ def extract( context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: + if context is None: + context = Context() trace_id_list = getter.get(carrier, self.TRACE_ID_KEY) span_id_list = getter.get(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: - return trace.set_span_in_context(trace.INVALID_SPAN) + return context return trace.set_span_in_context( trace.NonRecordingSpan( @@ -79,7 +81,8 @@ def extract( span_id=int(span_id_list[0]), is_remote=True, ) - ) + ), + context, ) def inject(