Skip to content

Commit

Permalink
add CompositeCloudTraceW3CPropagator and update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
aabmass committed May 13, 2021
1 parent 4f2c964 commit 49fb41e
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 151 deletions.
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

0 comments on commit 49fb41e

Please sign in to comment.