-
Notifications
You must be signed in to change notification settings - Fork 631
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
Adding a working propagator, adding to integrations and example #137
Changes from all commits
384496c
4e9f075
d856f9d
1781bcd
9789e58
1056545
1a3dc75
607a794
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# 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. | ||
# | ||
|
||
import typing | ||
|
||
import opentelemetry.trace as trace | ||
from opentelemetry.context.propagation import httptextformat | ||
|
||
_T = typing.TypeVar("_T") | ||
|
||
|
||
class TraceContextHTTPTextFormat(httptextformat.HTTPTextFormat): | ||
"""TODO: extracts and injects using w3c TraceContext's headers. | ||
""" | ||
|
||
def extract( | ||
self, _get_from_carrier: httptextformat.Getter[_T], _carrier: _T | ||
) -> trace.SpanContext: | ||
return trace.INVALID_SPAN_CONTEXT | ||
|
||
def inject( | ||
self, | ||
context: trace.SpanContext, | ||
set_in_carrier: httptextformat.Setter[_T], | ||
carrier: _T, | ||
) -> None: | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import typing | ||
|
||
import opentelemetry.context.propagation.httptextformat as httptextformat | ||
import opentelemetry.trace as trace | ||
from opentelemetry.context.propagation.tracecontexthttptextformat import ( | ||
TraceContextHTTPTextFormat, | ||
) | ||
|
||
_T = typing.TypeVar("_T") | ||
|
||
|
||
def extract( | ||
get_from_carrier: httptextformat.Getter[_T], carrier: _T | ||
) -> trace.SpanContext: | ||
"""Load the parent SpanContext from values in the carrier. | ||
|
||
Using the specified HTTPTextFormatter, the propagator will | ||
extract a SpanContext from the carrier. If one is found, | ||
it will be set as the parent context of the current span. | ||
|
||
Args: | ||
get_from_carrier: a function that can retrieve zero | ||
or more values from the carrier. In the case that | ||
the value does not exist, return an empty list. | ||
carrier: and object which contains values that are | ||
used to construct a SpanContext. This object | ||
must be paired with an appropriate get_from_carrier | ||
which understands how to extract a value from it. | ||
""" | ||
return get_global_httptextformat().extract(get_from_carrier, carrier) | ||
|
||
|
||
def inject( | ||
tracer: trace.Tracer, | ||
set_in_carrier: httptextformat.Setter[_T], | ||
carrier: _T, | ||
) -> None: | ||
"""Inject values from the current context into the carrier. | ||
|
||
inject enables the propagation of values into HTTP clients or | ||
other objects which perform an HTTP request. Implementations | ||
should use the set_in_carrier method to set values on the | ||
carrier. | ||
|
||
Args: | ||
set_in_carrier: A setter function that can set values | ||
on the carrier. | ||
carrier: An object that contains a representation of HTTP | ||
headers. Should be paired with set_in_carrier, which | ||
should know how to set header values on the carrier. | ||
""" | ||
get_global_httptextformat().inject( | ||
tracer.get_current_span().get_context(), set_in_carrier, carrier | ||
) | ||
|
||
|
||
_HTTP_TEXT_FORMAT = ( | ||
TraceContextHTTPTextFormat() | ||
) # type: httptextformat.HTTPTextFormat | ||
|
||
|
||
def get_global_httptextformat() -> httptextformat.HTTPTextFormat: | ||
return _HTTP_TEXT_FORMAT | ||
|
||
|
||
def set_global_httptextformat( | ||
http_text_format: httptextformat.HTTPTextFormat | ||
) -> None: | ||
global _HTTP_TEXT_FORMAT # pylint:disable=global-statement | ||
_HTTP_TEXT_FORMAT = http_text_format |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,59 @@ | ||
import unittest | ||
from unittest import mock | ||
|
||
import requests | ||
from werkzeug.test import Client | ||
from werkzeug.wrappers import BaseResponse | ||
|
||
import opentelemetry_example_app.flask_example as flask_example | ||
from opentelemetry.sdk import trace | ||
from opentelemetry.sdk.context.propagation import b3_format | ||
|
||
|
||
class TestFlaskExample(unittest.TestCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
cls.app = flask_example.app | ||
|
||
def setUp(self): | ||
mocked_response = requests.models.Response() | ||
mocked_response.status_code = 200 | ||
mocked_response.reason = "Roger that!" | ||
self.send_patcher = mock.patch.object( | ||
requests.Session, | ||
"send", | ||
autospec=True, | ||
spec_set=True, | ||
return_value=mocked_response, | ||
) | ||
self.send = self.send_patcher.start() | ||
|
||
def tearDown(self): | ||
self.send_patcher.stop() | ||
|
||
def test_full_path(self): | ||
with self.app.test_client() as client: | ||
response = client.get("/") | ||
assert response.data.decode() == "hello" | ||
trace_id = trace.generate_trace_id() | ||
# We need to use the Werkzeug test app because | ||
# The headers are injected at the wsgi layer. | ||
# The flask test app will not include these, and | ||
# result in the values not propagated. | ||
client = Client(self.app.wsgi_app, BaseResponse) | ||
# emulate b3 headers | ||
client.get( | ||
"/", | ||
headers={ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could use the B3 formatter's inject directly on the dict here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that might be a little tricky: it would require that I have direct access to the SpanContext for the app, which may not occur in situations where the app lives in a different thread or context than the test code itself. I feel like this is a more thorough test of the defined behavior, although I definitely see the merit of not effectively redefining the b3 interface. |
||
"x-b3-traceid": b3_format.format_trace_id(trace_id), | ||
"x-b3-spanid": b3_format.format_span_id( | ||
trace.generate_span_id() | ||
), | ||
"x-b3-sampled": "1", | ||
}, | ||
) | ||
# assert the http request header was propagated through. | ||
prepared_request = self.send.call_args[0][1] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to be sure: This does not test the headers that were "sent" by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's not an easy way to differentiate between headers that were directly set by a user vs the headers that were set in the propagator: both are setting the headers keyword that is passed in as part of the request. Theoretically someone could modify the examples to send the same headers that the propagator is responsible for, but that's the not case today. Also the way that the integration is written, propagator headers will override any user-defined headers. |
||
headers = prepared_request.headers | ||
for required_header in {"x-b3-traceid", "x-b3-spanid", "x-b3-sampled"}: | ||
self.assertIn(required_header, headers) | ||
self.assertEqual( | ||
headers["x-b3-traceid"], b3_format.format_trace_id(trace_id) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is now a full-fledged propagation test. We could probably build on this once we have stuff like exporters.
@c24t I think this will work for one of the test cases we discussed, although not as comprehensive as bringing up a full server.