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

[WIP] PR to start the discussion on context propagation. #278

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2019, OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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 serves as an example for baggage, which exists
to pass application-defined key-value pairs from service to service.
"""
import flask
import requests

import opentelemetry.ext.http_requests
from opentelemetry import propagators, trace
from opentelemetry import baggage
from opnetelemetry.context import Context
from opentelemetry.ext.wsgi import OpenTelemetryMiddleware
from opentelemetry.sdk.context.propagation.b3_format import B3Format
from opentelemetry.sdk.trace import Tracer


def configure_opentelemetry(flask_app: flask.Flask):
"""Configure a flask application to use OpenTelemetry.

This activates the specific components:

* sets tracer to the SDK's Tracer
* enables requests integration on the Tracer
* uses a WSGI middleware to enable configuration

TODO:

* processors?
* exporters?
"""
# Start by configuring all objects required to ensure
# a complete end to end workflow.
# the preferred implementation of these objects must be set,
# as the opentelemetry-api defines the interface with a no-op
# implementation.
trace.set_preferred_tracer_implementation(lambda _: Tracer())
# extractors and injectors are now separate, as it could be possible
# to want different behavior for those (e.g. don't propagate because of external services)
#
# the current configuration will only propagate w3c/correlationcontext
# and baggage. One would have to add other propagators to handle
# things such as w3c/tracecontext
propagator_list = [CorrelationContextFormat(), BaggageFormat()]

propagators.set_http_extractors(propagator_list)
propagators.set_http_injectors(propagator_list)

# Integrations are the glue that binds the OpenTelemetry API
# and the frameworks and libraries that are used together, automatically
# creating Spans and propagating context as appropriate.
opentelemetry.ext.http_requests.enable(trace.tracer())
flask_app.wsgi_app = OpenTelemetryMiddleware(flask_app.wsgi_app)


app = flask.Flask(__name__)


@app.route("/")
def hello():
# extract a baggage header
original_service = baggage.get(Context, "original-service")
# add a new one
baggage.set(Context, "environment", "foo")
return "hello"


configure_opentelemetry(app)
2 changes: 2 additions & 0 deletions opentelemetry-api/src/opentelemetry/baggage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Baggage:
""""""
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import typing

from opentelemetry.trace import SpanContext
from opentelemetry.context import BaseRuntimeContext

_T = typing.TypeVar("_T")

Expand Down Expand Up @@ -72,7 +73,7 @@ def example_route():

@abc.abstractmethod
def extract(
self, get_from_carrier: Getter[_T], carrier: _T
context: BaseRuntimeContext, get_from_carrier: Getter[_T], carrier: _T
) -> SpanContext:
"""Create a SpanContext from values in the carrier.

Expand All @@ -95,7 +96,10 @@ def extract(

@abc.abstractmethod
def inject(
self, context: SpanContext, set_in_carrier: Setter[_T], carrier: _T
self,
context: BaseRuntimeContext,
set_in_carrier: Setter[_T],
carrier: _T,
) -> None:
"""Inject values from a SpanContext into a carrier.

Expand Down
56 changes: 23 additions & 33 deletions opentelemetry-api/src/opentelemetry/distributedcontext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import itertools
import string
import typing
from contextlib import contextmanager
from opentelemetry.context import BaseRuntimeContext

PRINTABLE = frozenset(
itertools.chain(
Expand Down Expand Up @@ -81,45 +81,35 @@ def __init__(
class DistributedContext:
"""A container for distributed context entries"""

KEY = "DistributedContext"

def __init__(self, entries: typing.Iterable[Entry]) -> None:
self._container = {entry.key: entry for entry in entries}

def get_entries(self) -> typing.Iterable[Entry]:
@classmethod
def set_value(
cls, context: BaseRuntimeContext, entry_list: typing.Iterable[Entry]
):
distributed_context = getattr(context, cls.KEY, {})
for entry in entry_list:
distributed_context[entry.key] = entry

@classmethod
def get_entries(
cls, context: BaseRuntimeContext
) -> typing.Iterable[Entry]:
"""Returns an immutable iterator to entries."""
return self._container.values()
return getattr(context, cls.KEY, {}).values()

def get_entry_value(self, key: EntryKey) -> typing.Optional[EntryValue]:
@classmethod
def get_entry_value(
cls, context: BaseRuntimeContext, key: EntryKey
) -> typing.Optional[EntryValue]:
"""Returns the entry associated with a key or None

Args:
key: the key with which to perform a lookup
"""
if key in self._container:
return self._container[key].value
return None


class DistributedContextManager:
def get_current_context(self) -> typing.Optional[DistributedContext]:
"""Gets the current DistributedContext.

Returns:
A DistributedContext instance representing the current context.
"""

@contextmanager # type: ignore
def use_context(
self, context: DistributedContext
) -> typing.Iterator[DistributedContext]:
"""Context manager for controlling a DistributedContext lifetime.

Set the context as the active DistributedContext.

On exiting, the context manager will restore the parent
DistributedContext.

Args:
context: A DistributedContext instance to make current.
"""
# pylint: disable=no-self-use
yield context
container = getattr(context, cls.KEY, {})
if key in container:
return container[key].value
26 changes: 14 additions & 12 deletions opentelemetry-api/src/opentelemetry/propagators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
from opentelemetry.context.propagation.tracecontexthttptextformat import (
TraceContextHTTPTextFormat,
)
from opentelemetry.context import BaseRuntimeContext

_T = typing.TypeVar("_T")


def extract(
get_from_carrier: httptextformat.Getter[_T], carrier: _T
) -> trace.SpanContext:
) -> BaseRuntimeContext:
"""Load the parent SpanContext from values in the carrier.

Using the specified HTTPTextFormatter, the propagator will
Expand All @@ -45,7 +46,7 @@ def extract(


def inject(
tracer: trace.Tracer,
context: BaseRuntimeContext
set_in_carrier: httptextformat.Setter[_T],
carrier: _T,
) -> None:
Expand All @@ -67,18 +68,19 @@ def inject(
tracer.get_current_span().get_context(), set_in_carrier, carrier
)


_HTTP_TEXT_FORMAT = (
_HTTP_TEXT_INJECTORS = [
TraceContextHTTPTextFormat()
) # type: httptextformat.HTTPTextFormat
] # typing.List[httptextformat.HTTPTextFormat]

_HTTP_TEXT_EXTRACTORS = [
TraceContextHTTPTextFormat()
] # typing.List[httptextformat.HTTPTextFormat]

def get_global_httptextformat() -> httptextformat.HTTPTextFormat:
return _HTTP_TEXT_FORMAT

def set_http_extractors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None:
global _HTTP_TEXT_EXTRACTORS # pylint:disable=global-statement
_HTTP_TEXT_EXTRACTORS = extractor_list

def set_global_httptextformat(
http_text_format: httptextformat.HTTPTextFormat,
) -> None:
global _HTTP_TEXT_FORMAT # pylint:disable=global-statement
_HTTP_TEXT_FORMAT = http_text_format
def set_http_injectors(extractor_list: typing.List[httptextformat.HTTPTextFormat]) -> None:
global _HTTP_TEXT_INJECTORS # pylint:disable=global-statement
_HTTP_TEXT_INJECTORS = injector_list