Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CompositeCloudTraceW3CPropagator for W3C + X-Cloud-Trace-Context #140

Closed
wants to merge 1 commit into from
Closed
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
11 changes: 11 additions & 0 deletions docs/cloud_trace_propagator/cloud_trace_propagator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
OpenTelemetry Google Cloud Trace Propagator
===========================================

.. image:: https://badge.fury.io/py/opentelemetry-propagator-gcp.svg
:target: https://badge.fury.io/py/opentelemetry-propagator-trace

.. automodule:: opentelemetry.propagators.cloud_trace_propagator
:members:
:undoc-members:
:show-inheritance:
:noindex:
42 changes: 0 additions & 42 deletions docs/examples/cloud_trace_propagator/README.rst

This file was deleted.

41 changes: 0 additions & 41 deletions docs/examples/cloud_trace_propagator/client.py

This file was deleted.

55 changes: 0 additions & 55 deletions docs/examples/cloud_trace_propagator/server.py

This file was deleted.

2 changes: 2 additions & 0 deletions docs/examples/flask_e2e/README.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _flask-e2e:

=============================
End-to-End Example with Flask
=============================
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/flask_e2e/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.cloud_trace_propagator import (
CloudTraceFormatPropagator,
CompositeCloudTraceW3CPropagator,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

set_global_textmap(CloudTraceFormatPropagator())
set_global_textmap(CompositeCloudTraceW3CPropagator())

tracer_provider = TracerProvider()
cloud_trace_exporter = CloudTraceSpanExporter()
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/flask_e2e/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.cloud_trace_propagator import (
CloudTraceFormatPropagator,
CompositeCloudTraceW3CPropagator,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
Expand All @@ -32,7 +32,7 @@

# [START opentelemetry_flask_setup_propagator]

set_global_textmap(CloudTraceFormatPropagator())
set_global_textmap(CompositeCloudTraceW3CPropagator())

# [END opentelemetry_flask_setup_propagator]

Expand Down
5 changes: 3 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ To install the GCP trace propagator:

.. toctree::
:maxdepth: 1
:caption: Exporters
:name: exporters
:caption: Packages
:name: packages

cloud_monitoring/cloud_monitoring
cloud_trace/cloud_trace
cloud_trace_propagator/cloud_trace_propagator


.. toctree::
Expand Down
2 changes: 2 additions & 0 deletions opentelemetry-propagator-gcp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add CompositeCloudTraceW3CPropagator
([#140](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/pull/140))
- Fix propagator modifying context if failed to extract
([#139](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/pull/139))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,166 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
This module contains OpenTelemetry propagators with support for the Cloud Trace
`X-Cloud-Trace-Context`_ format.

It is recommended to use :class:`CompositeCloudTraceW3CPropagator`, which
combines the default OpenTelemetry supported propagation mechanisms (`W3C
TraceContext <https://www.w3.org/TR/trace-context/>`_ and `Baggage
<https://www.w3.org/TR/baggage/>`_) with :class:`CloudTraceFormatPropagator`.
This way, your application will be able to propagate context to and from Google
and non-Google services.

See :ref:`flask-e2e` for a full example using this propagator.

Usage
-----

.. code-block:: python

from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.cloud_trace_propagator import (
CompositeCloudTraceW3CPropagator,
)

# set as the global OpenTelemetry propagator
set_global_textmap(CompositeCloudTraceW3CPropagator())

.. _X-Cloud-Trace-Context: https://cloud.google.com/trace/docs/setup#force-trace
"""

import re
import typing
import logging

import opentelemetry.trace as trace
from opentelemetry.baggage.propagation import W3CBaggagePropagator
from opentelemetry.context.context import Context
from opentelemetry.propagators import textmap
from opentelemetry.trace.span import SpanContext, TraceFlags, format_trace_id
from opentelemetry.propagators import composite, textmap
from opentelemetry.trace.propagation import (
get_current_span,
set_span_in_context,
)
from opentelemetry.trace.propagation.tracecontext import (
TraceContextTextMapPropagator,
)
from opentelemetry.trace.span import (
INVALID_SPAN,
DEFAULT_TRACE_STATE,
INVALID_SPAN_CONTEXT,
SpanContext,
TraceFlags,
format_trace_id,
)

_TRACE_CONTEXT_HEADER_NAME = "x-cloud-trace-context"
_TRACE_CONTEXT_HEADER_FORMAT = r"(?P<trace_id>[0-9a-f]{32})\/(?P<span_id>[\d]{1,20});o=(?P<trace_flags>\d+)"
_TRACE_CONTEXT_HEADER_RE = re.compile(_TRACE_CONTEXT_HEADER_FORMAT)
_FIELDS = {_TRACE_CONTEXT_HEADER_NAME}

logger = logging.getLogger(__name__)


class CloudTraceW3CPropagator(textmap.TextMapPropagator):
"""Propagator to support both OTel W3C defaults and `X-Cloud-Trace-Context`_
format.

We recommend using this propagator to support a wide range of propagation
scenarios. This propagator combines the output of:

- W3C Trace Context propagator
- W3C Baggage propagator
- Cloud Trace format propagator

If the trace and span IDs output by W3C Trace Context and
`X-Cloud-Trace-Context`_ match, the TraceFlags and TraceState are merged as
well.

.. _X-Cloud-Trace-Context: https://cloud.google.com/trace/docs/setup#force-trace
"""

def __init__(self) -> None:
self._trace_context_propagator = TraceContextTextMapPropagator()
self._baggage_propagator = W3CBaggagePropagator()
self._cloud_trace_propagator = CloudTraceFormatPropagator()

def extract(
self,
carrier: textmap.CarrierT,
context: typing.Optional[Context] = None,
getter: textmap.Getter = textmap.default_getter,
) -> Context:
w3c_context = self._trace_context_propagator.extract(
carrier, context, getter
)
w3c_context = self._baggage_propagator.extract(
carrier, w3c_context, getter
)
cloud_trace_context = self._cloud_trace_propagator.extract(
carrier, w3c_context, getter
)

traceparent_span_context = get_current_span(
w3c_context
).get_span_context()
cloud_trace_span_context = get_current_span(
cloud_trace_context
).get_span_context()

combined_context = cloud_trace_context

# If the cloud trace and w3c span contexts have the same trace and span
# IDs, merge in w3c trace flags and trace state
if (
traceparent_span_context is not INVALID_SPAN_CONTEXT
and cloud_trace_span_context is not INVALID_SPAN_CONTEXT
):
if (
traceparent_span_context.trace_id
== cloud_trace_span_context.trace_id
and traceparent_span_context.span_id
== cloud_trace_span_context.span_id
):
combined_context = trace.set_span_in_context(
trace.NonRecordingSpan(
SpanContext(
trace_id=cloud_trace_span_context.trace_id,
span_id=cloud_trace_span_context.span_id,
is_remote=True,
trace_flags=TraceFlags(
cloud_trace_span_context.trace_flags
| traceparent_span_context.trace_flags
),
trace_state=traceparent_span_context.trace_state,
)
),
combined_context,
)
else:
logger.warning(
"Trace and span IDs from traceparent and cloud trace propagators do not match. "
"Using the value from cloud trace propagator."
)

return context

def inject(
self,
carrier: textmap.CarrierT,
context: typing.Optional[Context] = None,
setter: textmap.Setter = textmap.default_setter,
) -> None:
for propagator in (
self._trace_context_propagator,
self._baggage_propagator,
self._cloud_trace_propagator,
):
propagator.inject(carrier=carrier, context=context, setter=setter)


class CloudTraceFormatPropagator(textmap.TextMapPropagator):
"""This class is for injecting into a carrier the SpanContext in Google
Expand Down
Loading